diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 7130d62d82..ef5fb7417a 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -7,7 +7,6 @@ env: TMIN_LOG_FNAME: fuzz.tmin.log # File name to redirect the fuzzing input minimization log to. GH_ISSUE_TEMPLATE_RFPATH: .github/ISSUE_TEMPLATE/fuzz_bug_report.md # GitHub issue template rel file path. - TARGET_NAME: compile # Fuzzing target name. Fuzzes the `compile` func of the Q# compiler. ARTIFACTS_RDPATH: fuzz/artifacts # Fuzzing artifacts rel dir path. SEEDS_RDPATH: fuzz/seed_inputs # Fuzzing seed inputs rel dir path. SEEDS_FNAME: list.txt # Fuzzing seed inputs list file name. @@ -31,11 +30,15 @@ jobs: fuzz: name: Fuzzing strategy: + fail-fast: false matrix: os: [ubuntu-latest] # Fuzzing is not supported on Win. The macos is temporarily removed # because of low availability. - runs-on: ${{ matrix.os }} + target_name: [qsharp, qasm] + runs-on: ${{ matrix.os }} + permissions: + issues: write steps: - name: Install and Configure Tools run: | @@ -49,6 +52,7 @@ jobs: submodules: "true" - name: Gather the Seed Inputs + if: matrix.target_name == 'qsharp' run: | cd $OWNER_RDPATH # Enter the dir containing the fuzzing infra. @@ -56,22 +60,22 @@ jobs: REPOS="Quantum Quantum-NC QuantumKatas QuantumLibraries iqsharp qdk-python qsharp-compiler qsharp-runtime" for REPO in $REPOS ; do git clone --depth 1 --single-branch --no-tags --recurse-submodules --shallow-submodules --jobs 4 \ - https://github.com/microsoft/$REPO.git $SEEDS_RDPATH/$TARGET_NAME/$REPO + https://github.com/microsoft/$REPO.git $SEEDS_RDPATH/${{ matrix.target_name }}/$REPO done # Build a comma-separated list of all the .qs files in $SEEDS_FNAME file: - find $SEEDS_RDPATH/$TARGET_NAME -name "*.qs" | tr "\n" "," > \ - $SEEDS_RDPATH/$TARGET_NAME/$SEEDS_FNAME + find $SEEDS_RDPATH/${{ matrix.target_name }} -name "*.qs" | tr "\n" "," > \ + $SEEDS_RDPATH/${{ matrix.target_name }}/$SEEDS_FNAME - name: Build and Run the Fuzz Target run: | cd $OWNER_RDPATH # Enter the dir containing the fuzzing infra. - cargo fuzz build --release --sanitizer=none --features do_fuzz $TARGET_NAME # Build the fuzz target. + cargo fuzz build --release --sanitizer=none --features do_fuzz ${{ matrix.target_name }} # Build the fuzz target. # Run fuzzing for specified number of seconds and redirect the `stderr` to a file # whose name is specified by the STDERR_LOG_FNAME env var: - RUST_BACKTRACE=1 cargo fuzz run --release --sanitizer=none --features do_fuzz $TARGET_NAME -- \ - -seed_inputs=@$SEEDS_RDPATH/$TARGET_NAME/$SEEDS_FNAME \ + RUST_BACKTRACE=1 cargo fuzz run --release --sanitizer=none --features do_fuzz ${{ matrix.target_name }} -- \ + -seed_inputs=@$SEEDS_RDPATH/${{ matrix.target_name }}/$SEEDS_FNAME \ -max_total_time=$DURATION_SEC \ -rss_limit_mb=4096 \ -max_len=20000 \ @@ -116,20 +120,20 @@ jobs: # the subsequent `run:` and `uses:` steps. # Determine the name of a file containing the input of interest (that triggers the panic/crash): - if [ -e $ARTIFACTS_RDPATH/$TARGET_NAME/crash-* ]; then # Panic and Stack Overflow Cases. + if [ -e $ARTIFACTS_RDPATH/${{ matrix.target_name }}/crash-* ]; then # Panic and Stack Overflow Cases. TO_MINIMIZE_FNAME=crash-*; - elif [ -e $ARTIFACTS_RDPATH/$TARGET_NAME/oom-* ]; then # Out-of-Memory Case. + elif [ -e $ARTIFACTS_RDPATH/${{ matrix.target_name }}/oom-* ]; then # Out-of-Memory Case. TO_MINIMIZE_FNAME=oom-*; else - echo -e "File to minimize not found.\nContents of artifacts dir \"$ARTIFACTS_RDPATH/$TARGET_NAME/\":" - ls $ARTIFACTS_RDPATH/$TARGET_NAME/ + echo -e "File to minimize not found.\nContents of artifacts dir \"$ARTIFACTS_RDPATH/${{ matrix.target_name }}/\":" + ls $ARTIFACTS_RDPATH/${{ matrix.target_name }}/ fi if [ "$TO_MINIMIZE_FNAME" != "" ]; then echo "TO_MINIMIZE_FNAME: $TO_MINIMIZE_FNAME" # Minimize the input: - ( cargo fuzz tmin --release --sanitizer=none --features do_fuzz -r 10000 $TARGET_NAME $ARTIFACTS_RDPATH/$TARGET_NAME/$TO_MINIMIZE_FNAME 2>&1 ) > \ + ( cargo fuzz tmin --release --sanitizer=none --features do_fuzz -r 10000 ${{ matrix.target_name }} $ARTIFACTS_RDPATH/${{ matrix.target_name }}/$TO_MINIMIZE_FNAME 2>&1 ) > \ $TMIN_LOG_FNAME || MINIMIZATION_FAILED=1 # Get the minimized input relative faile path: @@ -137,12 +141,12 @@ jobs: # Minimization failed, get the latest successful minimized input relative faile path: MINIMIZED_INPUT_RFPATH=` cat $TMIN_LOG_FNAME | grep "CRASH_MIN: minimizing crash input: " | tail -n 1 | - sed "s|^.*\($ARTIFACTS_RDPATH/$TARGET_NAME/[^\']*\).*|\1|"` + sed "s|^.*\($ARTIFACTS_RDPATH/${{ matrix.target_name }}/[^\']*\).*|\1|"` else # Minimization Succeeded, get the reported minimized input relative faile path:: MINIMIZED_INPUT_RFPATH=` cat $TMIN_LOG_FNAME | grep "failed to minimize beyond" | - sed "s|.*\($ARTIFACTS_RDPATH/$TARGET_NAME/[^ ]*\).*|\1|" ` + sed "s|.*\($ARTIFACTS_RDPATH/${{ matrix.target_name }}/[^ ]*\).*|\1|" ` fi echo "MINIMIZED_INPUT_RFPATH: $MINIMIZED_INPUT_RFPATH" echo "MINIMIZED_INPUT_RFPATH=$MINIMIZED_INPUT_RFPATH" >> "$GITHUB_ENV" @@ -187,8 +191,8 @@ jobs: path: | ${{ env.OWNER_RDPATH }}/${{ env.STDERR_LOG_FNAME }} ${{ env.OWNER_RDPATH }}/${{ env.TMIN_LOG_FNAME }} - ${{ env.OWNER_RDPATH }}/${{ env.ARTIFACTS_RDPATH }}/${{ env.TARGET_NAME }}/* - ${{ env.OWNER_RDPATH }}/${{ env.SEEDS_RDPATH }}/${{ env.TARGET_NAME }}/${{ env.SEEDS_FNAME }} + ${{ env.OWNER_RDPATH }}/${{ env.ARTIFACTS_RDPATH }}/${{ matrix.target_name }}/* + ${{ env.OWNER_RDPATH }}/${{ env.SEEDS_RDPATH }}/${{ matrix.target_name }}/${{ env.SEEDS_FNAME }} if-no-files-found: error - name: "If Fuzzing Failed: Report GutHub Issue" diff --git a/Cargo.lock b/Cargo.lock index 340efdc04c..1597690d71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,17 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -114,17 +103,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" -[[package]] -name = "ariadne" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72fe02fc62033df9ba41cba57ee19acf5e742511a140c7dbc3a873e19a19a1bd" -dependencies = [ - "concolor", - "unicode-width", - "yansi", -] - [[package]] name = "async-trait" version = "0.1.81" @@ -133,7 +111,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -166,29 +144,12 @@ dependencies = [ "backtrace", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" -[[package]] -name = "boolenum" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6c8abd585d7026df20a9ae12982127ba5e81cc7a09397b957e71659da8c5de8" -dependencies = [ - "proc-macro-error", - "quote", - "syn 1.0.109", -] - [[package]] name = "bumpalo" version = "3.16.0" @@ -288,7 +249,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -303,38 +264,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" -[[package]] -name = "concolor" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b946244a988c390a94667ae0e3958411fa40cc46ea496a929b263d883f5f9c3" -dependencies = [ - "bitflags 1.3.2", - "concolor-query", - "is-terminal", -] - -[[package]] -name = "concolor-query" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" -dependencies = [ - "windows-sys 0.45.0", -] - -[[package]] -name = "countme" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" - -[[package]] -name = "cov-mark" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0570650661aa447e7335f1d5e4f499d8e58796e617bedc9267d971e51c8b49d4" - [[package]] name = "criterion" version = "0.5.1" @@ -387,12 +316,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d" -[[package]] -name = "drop_bomb" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1" - [[package]] name = "either" version = "1.13.0" @@ -416,7 +339,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -442,12 +365,6 @@ dependencies = [ "log", ] -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - [[package]] name = "errno" version = "0.3.9" @@ -530,7 +447,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -601,21 +518,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - [[package]] name = "heck" version = "0.5.0" @@ -640,16 +542,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" -[[package]] -name = "indexmap" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" -dependencies = [ - "equivalent", - "hashbrown 0.14.5", -] - [[package]] name = "indoc" version = "2.0.5" @@ -817,7 +709,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -860,7 +752,7 @@ checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -956,71 +848,6 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" -[[package]] -name = "oq3_lexer" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27bbc91e3e9d6193a44aac8f5d62c1507c41669af71a4e7e0ef66fd6470e960" -dependencies = [ - "unicode-properties", - "unicode-xid", -] - -[[package]] -name = "oq3_parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a72022fcb414e8a0912920a1cf46417b6aa95f19d4b38778df7450f8a3c17fa" -dependencies = [ - "drop_bomb", - "oq3_lexer", - "ra_ap_limit", -] - -[[package]] -name = "oq3_semantics" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72dffd869f3548190c705828d030fbb7fca94e519dcfa6a489227e5c3ffd777" -dependencies = [ - "boolenum", - "hashbrown 0.12.3", - "oq3_source_file", - "oq3_syntax", - "rowan", -] - -[[package]] -name = "oq3_source_file" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8c03f1f92c7a8f0b5249664b526169ceb8f925cb314ff93d3b27d8a4afb78c" -dependencies = [ - "ariadne", - "oq3_syntax", -] - -[[package]] -name = "oq3_syntax" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42c754ce1d9da28d6c0334c212d64b521288fe8c7cf16e9727d45dcf661ff084" -dependencies = [ - "cov-mark", - "either", - "indexmap", - "itertools", - "once_cell", - "oq3_lexer", - "oq3_parser", - "rowan", - "rustc-hash", - "rustversion", - "smol_str", - "triomphe", - "xshell", -] - [[package]] name = "owo-colors" version = "4.0.0" @@ -1070,30 +897,6 @@ dependencies = [ "special", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.86" @@ -1153,7 +956,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -1166,7 +969,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -1208,7 +1011,7 @@ dependencies = [ "qsc_partial_eval", "qsc_passes", "qsc_project", - "qsc_qasm3", + "qsc_qasm", "qsc_rca", "rustc-hash", "thiserror", @@ -1276,7 +1079,7 @@ dependencies = [ name = "qsc_data_structures" version = "0.0.0" dependencies = [ - "bitflags 2.6.0", + "bitflags", "expect-test", "miette", "rustc-hash", @@ -1400,7 +1203,7 @@ dependencies = [ name = "qsc_parse" version = "0.0.0" dependencies = [ - "bitflags 2.6.0", + "bitflags", "enum-iterator", "expect-test", "indoc", @@ -1473,26 +1276,27 @@ dependencies = [ ] [[package]] -name = "qsc_qasm3" +name = "qsc_qasm" version = "0.0.0" dependencies = [ - "bitflags 2.6.0", + "bitflags", + "criterion", "difference", + "enum-iterator", "expect-test", + "indenter", "indoc", "miette", "num-bigint", - "oq3_lexer", - "oq3_parser", - "oq3_semantics", - "oq3_source_file", - "oq3_syntax", + "num-traits", "qsc", "qsc_ast", + "qsc_codegen", "qsc_data_structures", "qsc_frontend", + "qsc_hir", "qsc_parse", - "qsc_qasm3", + "qsc_passes", "rustc-hash", "thiserror", ] @@ -1501,7 +1305,7 @@ dependencies = [ name = "qsc_rca" version = "0.0.0" dependencies = [ - "bitflags 2.6.0", + "bitflags", "expect-test", "indenter", "miette", @@ -1613,12 +1417,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "ra_ap_limit" -version = "0.0.188" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d33758724f997689f84146e5401e28d875a061804f861f113696f44f5232aa" - [[package]] name = "rand" version = "0.8.5" @@ -1716,19 +1514,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "rowan" -version = "0.15.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a58fa8a7ccff2aec4f39cc45bf5f985cec7125ab271cf681c279fd00192b49" -dependencies = [ - "countme", - "hashbrown 0.14.5", - "memoffset", - "rustc-hash", - "text-size", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1747,7 +1532,7 @@ version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ - "bitflags 2.6.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1821,7 +1606,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -1870,15 +1655,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] - [[package]] name = "special" version = "0.10.3" @@ -1915,17 +1691,6 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.87" @@ -1953,12 +1718,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "text-size" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" - [[package]] name = "textwrap" version = "0.16.1" @@ -1987,7 +1746,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -2019,15 +1778,9 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] -[[package]] -name = "triomphe" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" - [[package]] name = "typenum" version = "1.17.0" @@ -2046,24 +1799,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" -[[package]] -name = "unicode-properties" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" - [[package]] name = "unicode-width" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" -[[package]] -name = "unicode-xid" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" - [[package]] name = "unindent" version = "0.2.3" @@ -2076,12 +1817,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "walkdir" version = "2.5.0" @@ -2120,7 +1855,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.87", + "syn", "wasm-bindgen-shared", ] @@ -2154,7 +1889,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2197,15 +1932,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -2233,21 +1959,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -2279,12 +1990,6 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2297,12 +2002,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2315,12 +2014,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2339,12 +2032,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2357,12 +2044,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2375,12 +2056,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -2393,12 +2068,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -2411,27 +2080,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "xshell" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db0ab86eae739efd1b054a8d3d16041914030ac4e01cd1dca0cf252fd8b6437" -dependencies = [ - "xshell-macros", -] - -[[package]] -name = "xshell-macros" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d422e8e38ec76e2f06ee439ccc765e9c6a9638b9e7c9f2e8255e4d41e8bd852" - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - [[package]] name = "zerocopy" version = "0.7.35" @@ -2450,5 +2098,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 0932575714..17336fd13a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ "compiler/qsc_partial_eval", "compiler/qsc_passes", "compiler/qsc_project", - "compiler/qsc_qasm3", + "compiler/qsc_qasm", "compiler/qsc_rir", "fuzz", "katas", @@ -62,11 +62,6 @@ ndarray = "0.15.4" num-bigint = "0.4" num-complex = "0.4" num-traits = "0.2" -oq3_source_file = "0.7.0" -oq3_syntax = "0.7.0" -oq3_parser = "0.7.0" -oq3_lexer = "0.7.0" -oq3_semantics = "0.7.0" probability = "0.20" indenter = "0.3" regex-lite = "0.1" diff --git a/compiler/qsc/Cargo.toml b/compiler/qsc/Cargo.toml index 0ae849b56c..5ed1a26ca3 100644 --- a/compiler/qsc/Cargo.toml +++ b/compiler/qsc/Cargo.toml @@ -31,7 +31,7 @@ qsc_passes = { path = "../qsc_passes" } qsc_parse = { path = "../qsc_parse" } qsc_partial_eval = { path = "../qsc_partial_eval" } qsc_project = { path = "../qsc_project", features = ["fs"] } -qsc_qasm3 = { path = "../qsc_qasm3", features = ["fs"] } +qsc_qasm = { path = "../qsc_qasm" } qsc_rca = { path = "../qsc_rca" } qsc_circuit = { path = "../qsc_circuit" } rustc-hash = { workspace = true } diff --git a/compiler/qsc/src/compile.rs b/compiler/qsc/src/compile.rs index d0e97abe4c..a26252e820 100644 --- a/compiler/qsc/src/compile.rs +++ b/compiler/qsc/src/compile.rs @@ -41,6 +41,10 @@ pub enum ErrorKind { #[error("{0}")] /// `CircuitParse` variant represents errors that occur while parsing circuit files. CircuitParse(String), + + /// `OpenQASM` compilation errors. + #[diagnostic(transparent)] + OpenQasm(#[from] crate::qasm::error::Error), } /// Compiles a package from its AST representation. diff --git a/compiler/qsc/src/lib.rs b/compiler/qsc/src/lib.rs index 09a0c72bfd..abfce34b34 100644 --- a/compiler/qsc/src/lib.rs +++ b/compiler/qsc/src/lib.rs @@ -78,8 +78,4 @@ pub mod partial_eval { pub use qsc_partial_eval::Error; } -pub mod qasm3 { - pub use qsc_qasm3::io::*; - pub use qsc_qasm3::parse::*; - pub use qsc_qasm3::*; -} +pub mod qasm; diff --git a/compiler/qsc/src/qasm.rs b/compiler/qsc/src/qasm.rs new file mode 100644 index 0000000000..a9d5bf1f56 --- /dev/null +++ b/compiler/qsc/src/qasm.rs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::path::Path; + +use qsc_qasm::io::SourceResolver; +pub use qsc_qasm::{ + CompilerConfig, OperationSignature, OutputSemantics, ProgramType, QasmCompileUnit, + QubitSemantics, +}; +pub mod io { + pub use qsc_qasm::io::*; +} +pub mod parser { + pub use qsc_qasm::parser::*; +} +pub mod error { + pub use qsc_qasm::Error; + pub use qsc_qasm::ErrorKind; +} +pub mod completion { + pub use qsc_qasm::parser::completion::*; +} +pub use qsc_qasm::compile_to_qsharp_ast_with_config; +pub use qsc_qasm::package_store_with_qasm; + +#[must_use] +pub fn parse_raw_qasm_as_fragments( + source: S, + path: P, + resolver: Option<&mut R>, +) -> QasmCompileUnit +where + S: AsRef, + P: AsRef, + R: SourceResolver, +{ + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::Fragments, + None, + None, + ); + compile_to_qsharp_ast_with_config(source, path, resolver, config) +} + +#[must_use] +pub fn parse_raw_qasm_as_operation( + source: S, + name: S, + path: P, + resolver: Option<&mut R>, +) -> QasmCompileUnit +where + S: AsRef, + P: AsRef, + R: SourceResolver, +{ + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::Operation, + Some(name.as_ref().into()), + None, + ); + compile_to_qsharp_ast_with_config(source, path, resolver, config) +} diff --git a/compiler/qsc_frontend/src/compile.rs b/compiler/qsc_frontend/src/compile.rs index 570a0a5488..9065ddd8b7 100644 --- a/compiler/qsc_frontend/src/compile.rs +++ b/compiler/qsc_frontend/src/compile.rs @@ -489,7 +489,8 @@ pub fn std(store: &PackageStore, capabilities: TargetCapabilityFlags) -> Compile unit } -fn parse_all( +#[must_use] +pub fn parse_all( sources: &SourceMap, features: LanguageFeatures, ) -> (ast::Package, Vec) { diff --git a/compiler/qsc_qasm3/Cargo.toml b/compiler/qsc_qasm/Cargo.toml similarity index 68% rename from compiler/qsc_qasm3/Cargo.toml rename to compiler/qsc_qasm/Cargo.toml index b7a5d9f294..a1fdbf68dc 100644 --- a/compiler/qsc_qasm3/Cargo.toml +++ b/compiler/qsc_qasm/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "qsc_qasm3" +name = "qsc_qasm" authors.workspace = true homepage.workspace = true repository.workspace = true @@ -9,21 +9,22 @@ version.workspace = true [dependencies] bitflags = { workspace = true } +enum-iterator = { workspace = true } +indenter = { workspace = true } num-bigint = { workspace = true } +num-traits = { workspace = true } miette = { workspace = true } qsc_ast = { path = "../qsc_ast" } qsc_data_structures = { path = "../qsc_data_structures" } qsc_frontend = { path = "../qsc_frontend" } +qsc_hir = { path = "../qsc_hir" } qsc_parse = { path = "../qsc_parse" } +qsc_passes = { path = "../qsc_passes" } rustc-hash = { workspace = true } thiserror = { workspace = true } -oq3_source_file = { workspace = true } -oq3_syntax = { workspace = true } -oq3_parser = { workspace = true } -oq3_lexer = { workspace = true } -oq3_semantics = { workspace = true } [dev-dependencies] +criterion = { workspace = true, features = ["cargo_bench_support"] } difference = { workspace = true } expect-test = { workspace = true } indoc = { workspace = true } @@ -31,10 +32,14 @@ miette = { workspace = true, features = ["fancy"] } # Self import adding fs feature so that we can test # loading qasm from file. qsc = { path = "../qsc" } -qsc_qasm3 = { path = ".", features = ["fs"] } - -[features] -fs = [] +qsc_codegen = { path = "../qsc_codegen" } [lints] workspace = true + +[lib] +bench = false + +[[bench]] +name = "rgqft_multiplier" +harness = false diff --git a/compiler/qsc_qasm3/README.md b/compiler/qsc_qasm/README.md similarity index 94% rename from compiler/qsc_qasm3/README.md rename to compiler/qsc_qasm/README.md index 5850c75884..d03c5cc069 100644 --- a/compiler/qsc_qasm3/README.md +++ b/compiler/qsc_qasm/README.md @@ -1,6 +1,6 @@ -# Q# QASM3 Compiler +# Q# QASM Compiler -This crate implements a semantic transformation from OpenQASM 3 to Q#. At a high level it parses the OpenQASM program (and all includes) into an AST. Once this AST is parsed, it is compiled into Q#'s AST. +This crate implements a semantic transformation from OpenQASM to Q#. At a high level it parses the OpenQASM program (and all includes) into an AST. Once this AST is parsed, it is compiled into Q#'s AST. Once the compiler gets to the AST phase, it no longer cares that it is processing Q#. At this point all input is indistinguishable from having been given Q# as input. This allows us to leverage capability analysis, runtime targeting, residual computation, partial evaluation, and code generation to the input. diff --git a/compiler/qsc_qasm/benches/rgqft_multiplier.rs b/compiler/qsc_qasm/benches/rgqft_multiplier.rs new file mode 100644 index 0000000000..62205d9280 --- /dev/null +++ b/compiler/qsc_qasm/benches/rgqft_multiplier.rs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use qsc_qasm::{ + compile_to_qsharp_ast_with_config, io::InMemorySourceResolver, CompilerConfig, OutputSemantics, + ProgramType, QasmCompileUnit, QubitSemantics, +}; + +fn rgqft_multiplier(source: &str) -> QasmCompileUnit { + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::File, + Some("Test".into()), + None, + ); + compile_to_qsharp_ast_with_config(source, "", None::<&mut InMemorySourceResolver>, config) +} + +pub fn rgqft_multiplier_1q(c: &mut Criterion) { + const SOURCE: &str = include_str!("./rgqft_multiplier_1q.qasm"); + + c.bench_function("rgqft_multiplier_1q sample compilation", |b| { + b.iter(move || black_box(rgqft_multiplier(SOURCE))); + }); +} + +pub fn rgqft_multiplier_4q(c: &mut Criterion) { + const SOURCE: &str = include_str!("./rgqft_multiplier_4q.qasm"); + + c.bench_function("rgqft_multiplier_4q sample compilation", |b| { + b.iter(move || black_box(rgqft_multiplier(SOURCE))); + }); +} + +criterion_group!(benches, rgqft_multiplier_1q, rgqft_multiplier_4q); +criterion_main!(benches); diff --git a/compiler/qsc_qasm/benches/rgqft_multiplier_1q.qasm b/compiler/qsc_qasm/benches/rgqft_multiplier_1q.qasm new file mode 100644 index 0000000000..c186a33a55 --- /dev/null +++ b/compiler/qsc_qasm/benches/rgqft_multiplier_1q.qasm @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] a; +qubit[1] b; +qubit[2] out; +h out[1]; +rz(pi/4) out[1]; +cx out[1], out[0]; +rz(-pi/4) out[0]; +cx out[1], out[0]; +rz(pi/4) out[0]; +h out[0]; +cx a[0], out[0]; +rz(-pi/8) out[0]; +rx(pi/2) out[0]; +rz(pi) out[0]; +rx(pi/2) out[0]; +rz(9.032078879070655) out[0]; +cx b[0], out[0]; +rz(-7*pi/8) out[0]; +rx(pi/2) out[0]; +rz(pi) out[0]; +rx(pi/2) out[0]; +rz(6.675884388878311) out[0]; +cx a[0], out[0]; +rz(-pi/8) out[0]; +rx(pi/2) out[0]; +rz(pi) out[0]; +rx(pi/2) out[0]; +rz(9.032078879070655) out[0]; +cx b[0], out[0]; +rz(pi/4) b[0]; +rx(pi/2) b[0]; +rz(pi) b[0]; +rx(pi/2) b[0]; +rz(3*pi) b[0]; +cx a[0], b[0]; +rz(-pi/4) b[0]; +rx(pi/2) b[0]; +rz(pi) b[0]; +rx(pi/2) b[0]; +rz(3*pi) b[0]; +cx a[0], b[0]; +rz(pi/4) a[0]; +cx a[0], out[1]; +rz(-7*pi/8) out[0]; +rx(pi/2) out[0]; +rz(pi) out[0]; +rx(pi/2) out[0]; +rz(6.675884388878311) out[0]; +h out[0]; +rz(-pi/16) out[1]; +rx(pi/2) out[1]; +rz(pi) out[1]; +rx(pi/2) out[1]; +rz(9.228428419920018) out[1]; +cx b[0], out[1]; +rz(-15*pi/16) out[1]; +rx(pi/2) out[1]; +rz(pi) out[1]; +rx(pi/2) out[1]; +rz(6.4795348480289485) out[1]; +cx a[0], out[1]; +rz(-pi/16) out[1]; +rx(pi/2) out[1]; +rz(pi) out[1]; +rx(pi/2) out[1]; +rz(9.228428419920018) out[1]; +cx b[0], out[1]; +rz(pi/8) b[0]; +rx(pi/2) b[0]; +rz(pi) b[0]; +rx(pi/2) b[0]; +rz(3*pi) b[0]; +cx a[0], b[0]; +rz(-pi/8) b[0]; +rx(pi/2) b[0]; +rz(pi) b[0]; +rx(pi/2) b[0]; +rz(3*pi) b[0]; +cx a[0], b[0]; +rz(pi/8) a[0]; +rz(-15*pi/16) out[1]; +rx(pi/2) out[1]; +rz(pi) out[1]; +rx(pi/2) out[1]; +rz(6.4795348480289485) out[1]; +rz(-pi/4) out[1]; +cx out[1], out[0]; +rz(pi/4) out[0]; +cx out[1], out[0]; +rz(-pi/4) out[0]; +h out[1]; diff --git a/compiler/qsc_qasm/benches/rgqft_multiplier_4q.qasm b/compiler/qsc_qasm/benches/rgqft_multiplier_4q.qasm new file mode 100644 index 0000000000..c1fb7a0a93 --- /dev/null +++ b/compiler/qsc_qasm/benches/rgqft_multiplier_4q.qasm @@ -0,0 +1,3040 @@ +OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] a; +qubit[4] b; +qubit[8] out; +rz(1.4787574795217566) a[0]; +rx(-pi) a[0]; +rz(-0.18407769454627942) a[1]; +rx(-pi) a[1]; +rz(2.773437264497235) a[2]; +rx(-pi) a[2]; +rz(2.405281875404682) a[3]; +rx(-pi) a[3]; +rx(-pi) b[0]; +rx(-pi) b[1]; +rx(-pi) b[2]; +rx(-pi) b[3]; +rz(pi/2) out[0]; +rz(pi/2) out[1]; +rz(pi/2) out[2]; +rz(pi/2) out[3]; +rz(pi/2) out[4]; +rz(pi/2) out[5]; +rz(-pi/2) out[6]; +rx(-pi) out[6]; +rx(1.29567233598587) out[7]; +rzz(pi/2) out[6], out[7]; +ry(pi) out[6]; +rz(-pi) out[7]; +ry(-pi/4) out[7]; +rzz(pi/2) out[6], out[7]; +rz(-pi/4) out[6]; +rx(-pi/2) out[6]; +rx(-1.0605221542064758) out[7]; +rzz(pi/2) out[5], out[7]; +ry(pi) out[5]; +rz(-pi) out[7]; +ry(-pi/8) out[7]; +rzz(pi/2) out[5], out[7]; +rz(-pi/8) out[5]; +rzz(pi/2) out[5], out[6]; +ry(pi) out[5]; +rz(-pi) out[6]; +ry(-pi/4) out[6]; +rzz(pi/2) out[5], out[6]; +rz(-pi/4) out[5]; +rx(-pi/2) out[5]; +rx(3*pi/4) out[6]; +rz(-pi) out[7]; +rx(-pi/8) out[7]; +rzz(pi/2) out[4], out[7]; +ry(pi) out[4]; +rz(-pi) out[7]; +ry(-pi/16) out[7]; +rzz(pi/2) out[4], out[7]; +rz(-pi/16) out[4]; +ry(pi) out[4]; +rzz(pi/2) out[4], out[6]; +ry(pi) out[4]; +rz(-pi) out[6]; +ry(-pi/8) out[6]; +rzz(pi/2) out[4], out[6]; +rz(-pi/8) out[4]; +rzz(pi/2) out[4], out[5]; +ry(pi) out[4]; +rz(-pi) out[5]; +ry(-pi/4) out[5]; +rzz(pi/2) out[4], out[5]; +rz(-pi/4) out[4]; +rx(-pi/2) out[4]; +rx(3*pi/4) out[5]; +rz(-pi) out[6]; +rx(-pi/8) out[6]; +rz(-pi) out[7]; +rx(-pi/16) out[7]; +rzz(pi/2) out[3], out[7]; +ry(pi) out[3]; +rz(-pi) out[7]; +ry(-pi/32) out[7]; +rzz(pi/2) out[3], out[7]; +rz(-pi/32) out[3]; +ry(pi) out[3]; +rzz(pi/2) out[3], out[6]; +ry(pi) out[3]; +rz(-pi) out[6]; +ry(-pi/16) out[6]; +rzz(pi/2) out[3], out[6]; +rz(-pi/16) out[3]; +ry(pi) out[3]; +rzz(pi/2) out[3], out[5]; +ry(pi) out[3]; +rz(-pi) out[5]; +ry(-pi/8) out[5]; +rzz(pi/2) out[3], out[5]; +rz(-pi/8) out[3]; +rzz(pi/2) out[3], out[4]; +ry(pi) out[3]; +rz(-pi) out[4]; +ry(-pi/4) out[4]; +rzz(pi/2) out[3], out[4]; +rz(-pi/4) out[3]; +rx(-pi/2) out[3]; +rx(3*pi/4) out[4]; +rz(-pi) out[5]; +rx(-pi/8) out[5]; +rz(-pi) out[6]; +rx(-pi/16) out[6]; +rz(-pi) out[7]; +rx(-pi/32) out[7]; +rzz(pi/2) out[2], out[7]; +ry(pi) out[2]; +rz(-pi) out[7]; +ry(-pi/64) out[7]; +rzz(pi/2) out[2], out[7]; +rz(-pi/64) out[2]; +ry(pi) out[2]; +rzz(pi/2) out[2], out[6]; +ry(pi) out[2]; +rz(-pi) out[6]; +ry(-pi/32) out[6]; +rzz(pi/2) out[2], out[6]; +rz(-pi/32) out[2]; +ry(pi) out[2]; +rzz(pi/2) out[2], out[5]; +ry(pi) out[2]; +rz(-pi) out[5]; +ry(-pi/16) out[5]; +rzz(pi/2) out[2], out[5]; +rz(-pi/16) out[2]; +ry(pi) out[2]; +rzz(pi/2) out[2], out[4]; +ry(pi) out[2]; +rz(-pi) out[4]; +ry(-pi/8) out[4]; +rzz(pi/2) out[2], out[4]; +rz(-pi/8) out[2]; +rzz(pi/2) out[2], out[3]; +ry(pi) out[2]; +rz(-pi) out[3]; +ry(-pi/4) out[3]; +rzz(pi/2) out[2], out[3]; +rz(-pi/4) out[2]; +rx(-pi/2) out[2]; +rx(3*pi/4) out[3]; +rz(-pi) out[4]; +rx(-pi/8) out[4]; +rz(-pi) out[5]; +rx(-pi/16) out[5]; +rz(-pi) out[6]; +rx(-pi/32) out[6]; +rz(-pi) out[7]; +rx(-pi/64) out[7]; +rzz(pi/2) out[1], out[7]; +ry(pi) out[1]; +rz(-pi) out[7]; +ry(-pi/128) out[7]; +rzz(pi/2) out[1], out[7]; +rz(-pi/128) out[1]; +ry(pi) out[1]; +rzz(pi/2) out[1], out[6]; +ry(pi) out[1]; +rz(-pi) out[6]; +ry(-pi/64) out[6]; +rzz(pi/2) out[1], out[6]; +rz(-pi/64) out[1]; +ry(pi) out[1]; +rzz(pi/2) out[1], out[5]; +ry(pi) out[1]; +rz(-pi) out[5]; +ry(-pi/32) out[5]; +rzz(pi/2) out[1], out[5]; +rz(-pi/32) out[1]; +ry(pi) out[1]; +rzz(pi/2) out[1], out[4]; +ry(pi) out[1]; +rz(-pi) out[4]; +ry(-pi/16) out[4]; +rzz(pi/2) out[1], out[4]; +rz(-pi/16) out[1]; +ry(pi) out[1]; +rzz(pi/2) out[1], out[3]; +ry(pi) out[1]; +rz(-pi) out[3]; +ry(-pi/8) out[3]; +rzz(pi/2) out[1], out[3]; +rz(-pi/8) out[1]; +rzz(pi/2) out[1], out[2]; +ry(pi) out[1]; +rz(-pi) out[2]; +ry(-pi/4) out[2]; +rzz(pi/2) out[1], out[2]; +rz(-pi/4) out[1]; +rx(-pi/2) out[1]; +rx(3*pi/4) out[2]; +rz(-pi) out[3]; +rx(-pi/8) out[3]; +rz(-pi) out[4]; +rx(-pi/16) out[4]; +rz(-pi) out[5]; +rx(-pi/32) out[5]; +rz(-pi) out[6]; +rx(-pi/64) out[6]; +rz(-pi) out[7]; +rx(-pi/128) out[7]; +rzz(pi/2) out[0], out[7]; +ry(pi) out[0]; +rz(-pi) out[7]; +ry(-pi/256) out[7]; +rzz(pi/2) out[0], out[7]; +rz(-pi/256) out[0]; +ry(pi) out[0]; +rzz(pi/2) out[0], out[6]; +ry(pi) out[0]; +rz(-pi) out[6]; +ry(-pi/128) out[6]; +rzz(pi/2) out[0], out[6]; +rz(-pi/128) out[0]; +ry(pi) out[0]; +rzz(pi/2) out[0], out[5]; +ry(pi) out[0]; +rz(-pi) out[5]; +ry(-pi/64) out[5]; +rzz(pi/2) out[0], out[5]; +rz(-pi/64) out[0]; +ry(pi) out[0]; +rzz(pi/2) out[0], out[4]; +ry(pi) out[0]; +rz(-pi) out[4]; +ry(-pi/32) out[4]; +rzz(pi/2) out[0], out[4]; +rz(-pi/32) out[0]; +ry(pi) out[0]; +rzz(pi/2) out[0], out[3]; +ry(pi) out[0]; +rz(-pi) out[3]; +ry(-pi/16) out[3]; +rzz(pi/2) out[0], out[3]; +rz(-pi/16) out[0]; +ry(pi) out[0]; +rzz(pi/2) out[0], out[2]; +ry(pi) out[0]; +rz(-pi) out[2]; +ry(-pi/8) out[2]; +rzz(pi/2) out[0], out[2]; +rz(-pi/8) out[0]; +rzz(pi/2) out[0], out[1]; +ry(pi) out[0]; +rz(-pi) out[1]; +ry(-pi/4) out[1]; +rzz(pi/2) out[0], out[1]; +rz(3*pi/4) out[0]; +ry(pi) out[0]; +rz(-pi) out[1]; +rx(-pi/4) out[1]; +rx(5*pi/8) out[2]; +rx(9*pi/16) out[3]; +rx(1.6689710972195786) out[4]; +rx(1.6198837120072371) out[5]; +rzz(pi/2) a[3], out[5]; +rz(pi/2) a[3]; +ry(-pi/2) out[5]; +rz(3*pi/4) out[5]; +rzz(pi/2) b[3], out[5]; +rz(pi/2) b[3]; +rz(pi/4) out[5]; +rx(-pi/2) out[5]; +rzz(pi/2) a[3], out[5]; +rx(-pi) a[3]; +rz(-pi/2) a[3]; +ry(-pi/2) out[5]; +rz(3*pi/4) out[5]; +rzz(pi/2) b[3], out[5]; +rz(pi/2) b[3]; +rzz(pi/2) a[3], b[3]; +rz(-pi/2) b[3]; +rx(-pi) b[3]; +rz(3*pi/4) out[5]; +ry(pi/2) out[5]; +rz(-2.1072299372197243) out[5]; +rx(-1.5462526341887264) out[6]; +rz(-0.5364336104248277) out[6]; +rzz(pi/2) a[3], out[6]; +rz(-pi/2) a[3]; +rz(-2.6051590431649654) out[6]; +ry(pi/4) out[6]; +rz(-2.5551204732437065) out[6]; +rzz(pi/2) b[3], out[6]; +rz(-pi/2) b[3]; +rz(-2.157268507140982) out[6]; +ry(pi/4) out[6]; +rz(-2.1072299372197243) out[6]; +rzz(pi/2) a[3], out[6]; +rz(-2.6051590431649654) out[6]; +ry(pi/4) out[6]; +rz(-2.5551204732437065) out[6]; +rzz(pi/2) b[3], out[6]; +ry(-pi/2) b[3]; +rzz(pi/2) a[3], b[3]; +ry(pi) a[3]; +rz(-pi) b[3]; +ry(-pi/4) b[3]; +rzz(pi/2) a[3], b[3]; +rz(-pi/2) a[3]; +rx(-pi) a[3]; +ry(pi/2) b[3]; +rz(pi/4) b[3]; +rz(-2.1572685071409827) out[6]; +ry(pi/4) out[6]; +rz(0.3325895879972256) out[6]; +rx(-1.5585244804918112) out[7]; +rz(1.9033859147921213) out[7]; +rzz(pi/2) a[3], out[7]; +rz(-pi/2) a[3]; +rz(-1.903385914792119) out[7]; +ry(7*pi/8) out[7]; +rz(-1.6971534443990168) out[7]; +rzz(pi/2) b[3], out[7]; +rz(pi/2) b[3]; +rz(0.12635711760412294) out[7]; +ry(7*pi/8) out[7]; +rz(-2.8090030655925666) out[7]; +rzz(pi/2) a[3], out[7]; +rz(-1.903385914792119) out[7]; +ry(7*pi/8) out[7]; +rz(-1.6971534443990168) out[7]; +rzz(pi/2) b[3], out[7]; +ry(-pi/2) b[3]; +rz(-pi) b[3]; +rzz(pi/2) a[3], b[3]; +ry(pi) a[3]; +rz(-pi) b[3]; +ry(-pi/8) b[3]; +rzz(pi/2) a[3], b[3]; +rz(pi/2) a[3]; +rzz(pi/2) a[3], out[4]; +rz(pi/2) a[3]; +ry(pi/2) b[3]; +rz(3*pi/8) b[3]; +ry(-pi/2) out[4]; +rz(3*pi/4) out[4]; +rzz(pi/2) b[2], out[4]; +rz(pi/2) b[2]; +rz(pi/4) out[4]; +rx(-pi/2) out[4]; +rzz(pi/2) a[3], out[4]; +rx(-pi) a[3]; +rz(-pi/2) a[3]; +ry(-pi/2) out[4]; +rz(3*pi/4) out[4]; +rzz(pi/2) b[2], out[4]; +rz(pi/2) b[2]; +rzz(pi/2) a[3], b[2]; +rzz(pi/2) a[3], out[5]; +rz(-pi/2) a[3]; +rz(-pi/2) b[2]; +rx(-pi) b[2]; +rz(3*pi/4) out[4]; +ry(pi/2) out[4]; +rz(-2.1072299372197243) out[4]; +rz(-2.6051590431649654) out[5]; +ry(pi/4) out[5]; +rz(-2.5551204732437065) out[5]; +rzz(pi/2) b[2], out[5]; +rz(-pi/2) b[2]; +rz(-2.157268507140982) out[5]; +ry(pi/4) out[5]; +rz(-2.1072299372197243) out[5]; +rzz(pi/2) a[3], out[5]; +rz(-2.6051590431649654) out[5]; +ry(pi/4) out[5]; +rz(-2.5551204732437065) out[5]; +rzz(pi/2) b[2], out[5]; +ry(-pi/2) b[2]; +rzz(pi/2) a[3], b[2]; +ry(pi) a[3]; +rz(-pi) b[2]; +ry(-pi/4) b[2]; +rzz(pi/2) a[3], b[2]; +rz(-pi/2) a[3]; +rx(-pi) a[3]; +rzz(pi/2) a[3], out[6]; +rz(-pi/2) a[3]; +ry(pi/2) b[2]; +rz(pi/4) b[2]; +rz(-2.1572685071409827) out[5]; +ry(pi/4) out[5]; +rz(0.3325895879972256) out[5]; +rz(-1.903385914792119) out[6]; +ry(7*pi/8) out[6]; +rz(-1.6971534443990168) out[6]; +rzz(pi/2) b[2], out[6]; +rz(pi/2) b[2]; +rz(0.12635711760412294) out[6]; +ry(7*pi/8) out[6]; +rz(-2.8090030655925666) out[6]; +rzz(pi/2) a[3], out[6]; +rz(-1.903385914792119) out[6]; +ry(7*pi/8) out[6]; +rz(-1.6971534443990168) out[6]; +rzz(pi/2) b[2], out[6]; +ry(-pi/2) b[2]; +rz(-pi) b[2]; +rzz(pi/2) a[3], b[2]; +ry(pi) a[3]; +rz(-pi) b[2]; +ry(-pi/8) b[2]; +rzz(pi/2) a[3], b[2]; +rz(-pi/2) a[3]; +ry(-pi/2) b[2]; +rz(5*pi/8) b[2]; +rz(-3.0152355359856715) out[6]; +ry(pi/8) out[6]; +rz(1.294861248499081) out[6]; +rz(-3.0152355359856715) out[7]; +ry(pi/8) out[7]; +rz(1.294861248499081) out[7]; +rzz(pi/2) a[3], out[7]; +rz(pi/2) a[3]; +rz(0.2759350782958139) out[7]; +ry(15*pi/16) out[7]; +rz(-0.32190695107235934) out[7]; +rzz(pi/2) b[2], out[7]; +rz(-pi/2) b[2]; +rz(1.892703277867259) out[7]; +ry(15*pi/16) out[7]; +rz(-1.846731405090709) out[7]; +rzz(pi/2) a[3], out[7]; +rx(pi) a[3]; +rz(0.2759350782958139) out[7]; +ry(15*pi/16) out[7]; +rz(-0.32190695107235934) out[7]; +rzz(pi/2) b[2], out[7]; +ry(-pi/2) b[2]; +rzz(pi/2) a[3], b[2]; +ry(pi) a[3]; +rz(-pi) b[2]; +ry(-pi/16) b[2]; +rzz(pi/2) a[3], b[2]; +rz(pi/2) a[3]; +rzz(pi/2) a[3], out[3]; +rz(pi/2) a[3]; +ry(pi/2) b[2]; +rz(7*pi/16) b[2]; +ry(-pi/2) out[3]; +rz(3*pi/4) out[3]; +rzz(pi/2) b[1], out[3]; +rz(pi/2) b[1]; +rz(pi/4) out[3]; +rx(-pi/2) out[3]; +rzz(pi/2) a[3], out[3]; +rx(-pi) a[3]; +rz(-pi/2) a[3]; +ry(-pi/2) out[3]; +rz(3*pi/4) out[3]; +rzz(pi/2) b[1], out[3]; +rz(pi/2) b[1]; +rzz(pi/2) a[3], b[1]; +rzz(pi/2) a[3], out[4]; +rz(-pi/2) a[3]; +rz(-pi/2) b[1]; +rx(-pi) b[1]; +rz(3*pi/4) out[3]; +ry(pi/2) out[3]; +rz(-2.1072299372197243) out[3]; +rz(-2.6051590431649654) out[4]; +ry(pi/4) out[4]; +rz(-2.5551204732437065) out[4]; +rzz(pi/2) b[1], out[4]; +rz(-pi/2) b[1]; +rz(-2.157268507140982) out[4]; +ry(pi/4) out[4]; +rz(-2.1072299372197243) out[4]; +rzz(pi/2) a[3], out[4]; +rz(-2.6051590431649654) out[4]; +ry(pi/4) out[4]; +rz(-2.5551204732437065) out[4]; +rzz(pi/2) b[1], out[4]; +ry(-pi/2) b[1]; +rzz(pi/2) a[3], b[1]; +ry(pi) a[3]; +rz(-pi) b[1]; +ry(-pi/4) b[1]; +rzz(pi/2) a[3], b[1]; +rz(-pi/2) a[3]; +rx(-pi) a[3]; +rzz(pi/2) a[3], out[5]; +rz(-pi/2) a[3]; +ry(pi/2) b[1]; +rz(pi/4) b[1]; +rz(-2.1572685071409827) out[4]; +ry(pi/4) out[4]; +rz(0.3325895879972256) out[4]; +rz(-1.903385914792119) out[5]; +ry(7*pi/8) out[5]; +rz(-1.6971534443990168) out[5]; +rzz(pi/2) b[1], out[5]; +rz(pi/2) b[1]; +rz(0.12635711760412294) out[5]; +ry(7*pi/8) out[5]; +rz(-2.8090030655925666) out[5]; +rzz(pi/2) a[3], out[5]; +rz(-1.903385914792119) out[5]; +ry(7*pi/8) out[5]; +rz(-1.6971534443990168) out[5]; +rzz(pi/2) b[1], out[5]; +ry(-pi/2) b[1]; +rz(-pi) b[1]; +rzz(pi/2) a[3], b[1]; +ry(pi) a[3]; +rz(-pi) b[1]; +ry(-pi/8) b[1]; +rzz(pi/2) a[3], b[1]; +rz(-pi/2) a[3]; +rzz(pi/2) a[3], out[6]; +rz(pi/2) a[3]; +ry(-pi/2) b[1]; +rz(5*pi/8) b[1]; +rz(-3.0152355359856715) out[5]; +ry(pi/8) out[5]; +rz(1.294861248499081) out[5]; +rz(0.2759350782958139) out[6]; +ry(15*pi/16) out[6]; +rz(-0.32190695107235934) out[6]; +rzz(pi/2) b[1], out[6]; +rz(-pi/2) b[1]; +rz(1.892703277867259) out[6]; +ry(15*pi/16) out[6]; +rz(-1.846731405090709) out[6]; +rzz(pi/2) a[3], out[6]; +rx(pi) a[3]; +rz(0.2759350782958139) out[6]; +ry(15*pi/16) out[6]; +rz(-0.32190695107235934) out[6]; +rzz(pi/2) b[1], out[6]; +ry(-pi/2) b[1]; +rzz(pi/2) a[3], b[1]; +ry(pi) a[3]; +rz(-pi) b[1]; +ry(-pi/16) b[1]; +rzz(pi/2) a[3], b[1]; +rz(-pi/2) a[3]; +rx(-pi) a[3]; +ry(-pi/2) b[1]; +rz(9*pi/16) b[1]; +rz(-1.2488893757225379) out[6]; +ry(pi/16) out[6]; +rz(0.9832827023934838) out[6]; +rz(-1.2488893757225379) out[7]; +ry(pi/16) out[7]; +rz(0.9832827023934838) out[7]; +rzz(pi/2) a[3], out[7]; +rz(-pi/2) a[3]; +rz(0.5875136244014145) out[7]; +ry(pi/32) out[7]; +rz(1.230855619167917) out[7]; +rzz(pi/2) b[1], out[7]; +rz(-pi/2) b[1]; +rz(0.3399407076269809) out[7]; +ry(pi/32) out[7]; +rz(0.9832827023934785) out[7]; +rzz(pi/2) a[3], out[7]; +rz(0.5875136244014145) out[7]; +ry(pi/32) out[7]; +rz(1.230855619167917) out[7]; +rzz(pi/2) b[1], out[7]; +ry(-pi/2) b[1]; +rzz(pi/2) a[3], b[1]; +ry(pi) a[3]; +rz(-pi) b[1]; +ry(-pi/32) b[1]; +rzz(pi/2) a[3], b[1]; +rz(pi/2) a[3]; +rzz(pi/2) a[3], out[2]; +rz(pi/2) a[3]; +ry(pi/2) b[1]; +rz(1.472621556370215) b[1]; +ry(-pi/2) out[2]; +rz(3*pi/4) out[2]; +rzz(pi/2) b[0], out[2]; +rz(pi/2) b[0]; +rz(pi/4) out[2]; +rx(-pi/2) out[2]; +rzz(pi/2) a[3], out[2]; +rx(-pi) a[3]; +rz(-pi/2) a[3]; +ry(-pi/2) out[2]; +rz(3*pi/4) out[2]; +rzz(pi/2) b[0], out[2]; +rz(pi/2) b[0]; +rzz(pi/2) a[3], b[0]; +rzz(pi/2) a[3], out[3]; +rz(-pi/2) a[3]; +rz(-pi/2) b[0]; +rx(-pi) b[0]; +rz(pi/4) out[2]; +rx(-pi/2) out[2]; +rz(-2.6051590431649654) out[3]; +ry(pi/4) out[3]; +rz(-2.5551204732437065) out[3]; +rzz(pi/2) b[0], out[3]; +rz(-pi/2) b[0]; +rz(-2.157268507140982) out[3]; +ry(pi/4) out[3]; +rz(-2.1072299372197243) out[3]; +rzz(pi/2) a[3], out[3]; +rz(-2.6051590431649654) out[3]; +ry(pi/4) out[3]; +rz(-2.5551204732437065) out[3]; +rzz(pi/2) b[0], out[3]; +ry(-pi/2) b[0]; +rzz(pi/2) a[3], b[0]; +ry(pi) a[3]; +rz(-pi) b[0]; +ry(-pi/4) b[0]; +rzz(pi/2) a[3], b[0]; +rz(-pi/2) a[3]; +rx(-pi) a[3]; +rzz(pi/2) a[3], out[4]; +rz(-pi/2) a[3]; +ry(pi/2) b[0]; +rz(pi/4) b[0]; +rz(2.555120473243707) out[3]; +rx(-3*pi/4) out[3]; +rz(-1.903385914792119) out[4]; +ry(7*pi/8) out[4]; +rz(-1.6971534443990168) out[4]; +rzz(pi/2) b[0], out[4]; +rz(pi/2) b[0]; +rz(0.12635711760412294) out[4]; +ry(7*pi/8) out[4]; +rz(-2.8090030655925666) out[4]; +rzz(pi/2) a[3], out[4]; +rz(-1.903385914792119) out[4]; +ry(7*pi/8) out[4]; +rz(-1.6971534443990168) out[4]; +rzz(pi/2) b[0], out[4]; +ry(-pi/2) b[0]; +rz(-pi) b[0]; +rzz(pi/2) a[3], b[0]; +ry(pi) a[3]; +rz(-pi) b[0]; +ry(-pi/8) b[0]; +rzz(pi/2) a[3], b[0]; +rz(-pi/2) a[3]; +rzz(pi/2) a[3], out[5]; +rz(pi/2) a[3]; +ry(-pi/2) b[0]; +rz(5*pi/8) b[0]; +rz(1.6971534443990208) out[4]; +rx(pi/8) out[4]; +rzz(pi/2) a[2], out[4]; +rz(pi/2) a[2]; +ry(-pi/2) out[4]; +rz(3*pi/4) out[4]; +rzz(pi/2) b[3], out[4]; +rz(pi/2) b[3]; +rz(pi/4) out[4]; +rx(-pi/2) out[4]; +rzz(pi/2) a[2], out[4]; +rx(-pi) a[2]; +rz(-pi/2) a[2]; +ry(-pi/2) out[4]; +rz(3*pi/4) out[4]; +rzz(pi/2) b[3], out[4]; +rz(pi/2) b[3]; +rzz(pi/2) a[2], b[3]; +rz(-pi/2) b[3]; +rx(-pi) b[3]; +rz(3*pi/4) out[4]; +ry(pi/2) out[4]; +rz(-2.1072299372197243) out[4]; +rz(0.2759350782958139) out[5]; +ry(15*pi/16) out[5]; +rz(-0.32190695107235934) out[5]; +rzz(pi/2) b[0], out[5]; +rz(-pi/2) b[0]; +rz(1.892703277867259) out[5]; +ry(15*pi/16) out[5]; +rz(-1.846731405090709) out[5]; +rzz(pi/2) a[3], out[5]; +rx(pi) a[3]; +rz(0.2759350782958139) out[5]; +ry(15*pi/16) out[5]; +rz(-0.32190695107235934) out[5]; +rzz(pi/2) b[0], out[5]; +ry(-pi/2) b[0]; +rzz(pi/2) a[3], b[0]; +ry(pi) a[3]; +rz(-pi) b[0]; +ry(-pi/16) b[0]; +rzz(pi/2) a[3], b[0]; +rz(-pi/2) a[3]; +rx(-pi) a[3]; +rzz(pi/2) a[3], out[6]; +rz(-pi/2) a[3]; +ry(-pi/2) b[0]; +rz(9*pi/16) b[0]; +rz(-1.2488893757225354) out[5]; +ry(pi/16) out[5]; +rz(-2.107229937219726) out[5]; +rzz(pi/2) a[2], out[5]; +rz(-pi/2) a[2]; +rz(-2.6051590431649654) out[5]; +ry(pi/4) out[5]; +rz(-2.5551204732437065) out[5]; +rzz(pi/2) b[3], out[5]; +rz(-pi/2) b[3]; +rz(-2.157268507140982) out[5]; +ry(pi/4) out[5]; +rz(-2.1072299372197243) out[5]; +rzz(pi/2) a[2], out[5]; +rz(-2.6051590431649654) out[5]; +ry(pi/4) out[5]; +rz(-2.5551204732437065) out[5]; +rzz(pi/2) b[3], out[5]; +ry(-pi/2) b[3]; +rzz(pi/2) a[2], b[3]; +ry(pi) a[2]; +rz(-pi) b[3]; +ry(-pi/4) b[3]; +rzz(pi/2) a[2], b[3]; +rz(-pi/2) a[2]; +rx(-pi) a[2]; +ry(pi/2) b[3]; +rz(pi/4) b[3]; +rz(-2.1572685071409827) out[5]; +ry(pi/4) out[5]; +rz(0.3325895879972256) out[5]; +rz(0.5875136244014145) out[6]; +ry(pi/32) out[6]; +rz(1.230855619167917) out[6]; +rzz(pi/2) b[0], out[6]; +rz(-pi/2) b[0]; +rz(0.3399407076269809) out[6]; +ry(pi/32) out[6]; +rz(0.9832827023934785) out[6]; +rzz(pi/2) a[3], out[6]; +rz(0.5875136244014145) out[6]; +ry(pi/32) out[6]; +rz(1.230855619167917) out[6]; +rzz(pi/2) b[0], out[6]; +ry(-pi/2) b[0]; +rzz(pi/2) a[3], b[0]; +ry(pi) a[3]; +rz(-pi) b[0]; +ry(-pi/32) b[0]; +rzz(pi/2) a[3], b[0]; +rz(-pi/2) a[3]; +ry(pi/2) b[0]; +rz(1.472621556370215) b[0]; +rz(0.3399407076269818) out[6]; +ry(pi/32) out[6]; +rz(0.3325895879972194) out[6]; +rzz(pi/2) a[2], out[6]; +rz(-pi/2) a[2]; +rz(-1.903385914792119) out[6]; +ry(7*pi/8) out[6]; +rz(-1.6971534443990168) out[6]; +rzz(pi/2) b[3], out[6]; +rz(pi/2) b[3]; +rz(0.12635711760412294) out[6]; +ry(7*pi/8) out[6]; +rz(-2.8090030655925666) out[6]; +rzz(pi/2) a[2], out[6]; +rz(-1.903385914792119) out[6]; +ry(7*pi/8) out[6]; +rz(-1.6971534443990168) out[6]; +rzz(pi/2) b[3], out[6]; +ry(-pi/2) b[3]; +rz(-pi) b[3]; +rzz(pi/2) a[2], b[3]; +ry(pi) a[2]; +rz(-pi) b[3]; +ry(-pi/8) b[3]; +rzz(pi/2) a[2], b[3]; +rz(-pi/2) a[2]; +ry(-pi/2) b[3]; +rz(5*pi/8) b[3]; +rz(-3.0152355359856715) out[6]; +ry(pi/8) out[6]; +rz(1.294861248499081) out[6]; +rz(-2.801651945962806) out[7]; +ry(3.043417883165112) out[7]; +rz(-0.8226060790210696) out[7]; +rzz(pi/2) a[3], out[7]; +rz(pi/2) a[3]; +rz(2.3934024058159675) out[7]; +ry(pi/64) out[7]; +rz(2.3982406999461094) out[7]; +rzz(pi/2) b[0], out[7]; +rz(pi/2) b[0]; +rz(2.3141482804385793) out[7]; +ry(pi/64) out[7]; +rz(2.3189865745687213) out[7]; +rzz(pi/2) a[3], out[7]; +rx(pi) a[3]; +rz(2.3934024058159675) out[7]; +ry(pi/64) out[7]; +rz(2.3982406999461094) out[7]; +rzz(pi/2) b[0], out[7]; +ry(-pi/2) b[0]; +rz(-pi) b[0]; +rzz(pi/2) a[3], b[0]; +ry(pi) a[3]; +rz(-pi) b[0]; +ry(-pi/64) b[0]; +rzz(pi/2) a[3], b[0]; +rz(-pi/2) a[3]; +rx(-pi) a[3]; +ry(pi/2) b[0]; +rz(1.5217089415825562) b[0]; +rz(2.3141482804385793) out[7]; +ry(pi/64) out[7]; +rz(1.294861248499089) out[7]; +rzz(pi/2) a[2], out[7]; +rz(pi/2) a[2]; +rz(0.2759350782958139) out[7]; +ry(15*pi/16) out[7]; +rz(-0.32190695107235934) out[7]; +rzz(pi/2) b[3], out[7]; +rz(-pi/2) b[3]; +rz(1.892703277867259) out[7]; +ry(15*pi/16) out[7]; +rz(-1.846731405090709) out[7]; +rzz(pi/2) a[2], out[7]; +rx(pi) a[2]; +rz(0.2759350782958139) out[7]; +ry(15*pi/16) out[7]; +rz(-0.32190695107235934) out[7]; +rzz(pi/2) b[3], out[7]; +ry(-pi/2) b[3]; +rzz(pi/2) a[2], b[3]; +ry(pi) a[2]; +rz(-pi) b[3]; +ry(-pi/16) b[3]; +rzz(pi/2) a[2], b[3]; +rz(pi/2) a[2]; +rzz(pi/2) a[2], out[3]; +rz(pi/2) a[2]; +ry(pi/2) b[3]; +rz(7*pi/16) b[3]; +ry(-pi/2) out[3]; +rz(3*pi/4) out[3]; +rzz(pi/2) b[2], out[3]; +rz(pi/2) b[2]; +rz(pi/4) out[3]; +rx(-pi/2) out[3]; +rzz(pi/2) a[2], out[3]; +rx(-pi) a[2]; +rz(-pi/2) a[2]; +ry(-pi/2) out[3]; +rz(3*pi/4) out[3]; +rzz(pi/2) b[2], out[3]; +rz(pi/2) b[2]; +rzz(pi/2) a[2], b[2]; +rzz(pi/2) a[2], out[4]; +rz(-pi/2) a[2]; +rz(-pi/2) b[2]; +rx(-pi) b[2]; +rz(3*pi/4) out[3]; +ry(pi/2) out[3]; +rz(-2.1072299372197243) out[3]; +rz(-2.6051590431649654) out[4]; +ry(pi/4) out[4]; +rz(-2.5551204732437065) out[4]; +rzz(pi/2) b[2], out[4]; +rz(-pi/2) b[2]; +rz(-2.157268507140982) out[4]; +ry(pi/4) out[4]; +rz(-2.1072299372197243) out[4]; +rzz(pi/2) a[2], out[4]; +rz(-2.6051590431649654) out[4]; +ry(pi/4) out[4]; +rz(-2.5551204732437065) out[4]; +rzz(pi/2) b[2], out[4]; +ry(-pi/2) b[2]; +rzz(pi/2) a[2], b[2]; +ry(pi) a[2]; +rz(-pi) b[2]; +ry(-pi/4) b[2]; +rzz(pi/2) a[2], b[2]; +rz(-pi/2) a[2]; +rx(-pi) a[2]; +rzz(pi/2) a[2], out[5]; +rz(-pi/2) a[2]; +ry(pi/2) b[2]; +rz(pi/4) b[2]; +rz(-2.1572685071409827) out[4]; +ry(pi/4) out[4]; +rz(0.3325895879972256) out[4]; +rz(-1.903385914792119) out[5]; +ry(7*pi/8) out[5]; +rz(-1.6971534443990168) out[5]; +rzz(pi/2) b[2], out[5]; +rz(pi/2) b[2]; +rz(0.12635711760412294) out[5]; +ry(7*pi/8) out[5]; +rz(-2.8090030655925666) out[5]; +rzz(pi/2) a[2], out[5]; +rz(-1.903385914792119) out[5]; +ry(7*pi/8) out[5]; +rz(-1.6971534443990168) out[5]; +rzz(pi/2) b[2], out[5]; +ry(-pi/2) b[2]; +rz(-pi) b[2]; +rzz(pi/2) a[2], b[2]; +ry(pi) a[2]; +rz(-pi) b[2]; +ry(-pi/8) b[2]; +rzz(pi/2) a[2], b[2]; +rz(-pi/2) a[2]; +rzz(pi/2) a[2], out[6]; +rz(pi/2) a[2]; +ry(-pi/2) b[2]; +rz(5*pi/8) b[2]; +rz(-3.0152355359856715) out[5]; +ry(pi/8) out[5]; +rz(1.294861248499081) out[5]; +rz(0.2759350782958139) out[6]; +ry(15*pi/16) out[6]; +rz(-0.32190695107235934) out[6]; +rzz(pi/2) b[2], out[6]; +rz(-pi/2) b[2]; +rz(1.892703277867259) out[6]; +ry(15*pi/16) out[6]; +rz(-1.846731405090709) out[6]; +rzz(pi/2) a[2], out[6]; +rx(pi) a[2]; +rz(0.2759350782958139) out[6]; +ry(15*pi/16) out[6]; +rz(-0.32190695107235934) out[6]; +rzz(pi/2) b[2], out[6]; +ry(-pi/2) b[2]; +rzz(pi/2) a[2], b[2]; +ry(pi) a[2]; +rz(-pi) b[2]; +ry(-pi/16) b[2]; +rzz(pi/2) a[2], b[2]; +rz(-pi/2) a[2]; +rx(-pi) a[2]; +ry(-pi/2) b[2]; +rz(9*pi/16) b[2]; +rz(-1.2488893757225379) out[6]; +ry(pi/16) out[6]; +rz(0.9832827023934838) out[6]; +rz(-1.2488893757225379) out[7]; +ry(pi/16) out[7]; +rz(0.9832827023934838) out[7]; +rzz(pi/2) a[2], out[7]; +rz(-pi/2) a[2]; +rz(0.5875136244014145) out[7]; +ry(pi/32) out[7]; +rz(1.230855619167917) out[7]; +rzz(pi/2) b[2], out[7]; +rz(-pi/2) b[2]; +rz(0.3399407076269809) out[7]; +ry(pi/32) out[7]; +rz(0.9832827023934785) out[7]; +rzz(pi/2) a[2], out[7]; +rz(0.5875136244014145) out[7]; +ry(pi/32) out[7]; +rz(1.230855619167917) out[7]; +rzz(pi/2) b[2], out[7]; +ry(-pi/2) b[2]; +rzz(pi/2) a[2], b[2]; +ry(pi) a[2]; +rz(-pi) b[2]; +ry(-pi/32) b[2]; +rzz(pi/2) a[2], b[2]; +rz(pi/2) a[2]; +rzz(pi/2) a[2], out[2]; +rz(pi/2) a[2]; +ry(pi/2) b[2]; +rz(1.472621556370215) b[2]; +ry(-pi/2) out[2]; +rz(3*pi/4) out[2]; +rzz(pi/2) b[1], out[2]; +rz(pi/2) b[1]; +rz(pi/4) out[2]; +rx(-pi/2) out[2]; +rzz(pi/2) a[2], out[2]; +rx(-pi) a[2]; +rz(-pi/2) a[2]; +ry(-pi/2) out[2]; +rz(3*pi/4) out[2]; +rzz(pi/2) b[1], out[2]; +rz(pi/2) b[1]; +rzz(pi/2) a[2], b[1]; +rzz(pi/2) a[2], out[3]; +rz(-pi/2) a[2]; +rz(-pi/2) b[1]; +rx(-pi) b[1]; +rz(3*pi/4) out[2]; +ry(pi/2) out[2]; +rz(-2.1072299372197243) out[2]; +rz(-2.6051590431649654) out[3]; +ry(pi/4) out[3]; +rz(-2.5551204732437065) out[3]; +rzz(pi/2) b[1], out[3]; +rz(-pi/2) b[1]; +rz(-2.157268507140982) out[3]; +ry(pi/4) out[3]; +rz(-2.1072299372197243) out[3]; +rzz(pi/2) a[2], out[3]; +rz(-2.6051590431649654) out[3]; +ry(pi/4) out[3]; +rz(-2.5551204732437065) out[3]; +rzz(pi/2) b[1], out[3]; +ry(-pi/2) b[1]; +rzz(pi/2) a[2], b[1]; +ry(pi) a[2]; +rz(-pi) b[1]; +ry(-pi/4) b[1]; +rzz(pi/2) a[2], b[1]; +rz(-pi/2) a[2]; +rx(-pi) a[2]; +rzz(pi/2) a[2], out[4]; +rz(-pi/2) a[2]; +ry(pi/2) b[1]; +rz(pi/4) b[1]; +rz(-2.1572685071409827) out[3]; +ry(pi/4) out[3]; +rz(0.3325895879972256) out[3]; +rz(-1.903385914792119) out[4]; +ry(7*pi/8) out[4]; +rz(-1.6971534443990168) out[4]; +rzz(pi/2) b[1], out[4]; +rz(pi/2) b[1]; +rz(0.12635711760412294) out[4]; +ry(7*pi/8) out[4]; +rz(-2.8090030655925666) out[4]; +rzz(pi/2) a[2], out[4]; +rz(-1.903385914792119) out[4]; +ry(7*pi/8) out[4]; +rz(-1.6971534443990168) out[4]; +rzz(pi/2) b[1], out[4]; +ry(-pi/2) b[1]; +rz(-pi) b[1]; +rzz(pi/2) a[2], b[1]; +ry(pi) a[2]; +rz(-pi) b[1]; +ry(-pi/8) b[1]; +rzz(pi/2) a[2], b[1]; +rz(-pi/2) a[2]; +rzz(pi/2) a[2], out[5]; +rz(pi/2) a[2]; +ry(-pi/2) b[1]; +rz(5*pi/8) b[1]; +rz(-3.0152355359856715) out[4]; +ry(pi/8) out[4]; +rz(1.294861248499081) out[4]; +rz(0.2759350782958139) out[5]; +ry(15*pi/16) out[5]; +rz(-0.32190695107235934) out[5]; +rzz(pi/2) b[1], out[5]; +rz(-pi/2) b[1]; +rz(1.892703277867259) out[5]; +ry(15*pi/16) out[5]; +rz(-1.846731405090709) out[5]; +rzz(pi/2) a[2], out[5]; +rx(pi) a[2]; +rz(0.2759350782958139) out[5]; +ry(15*pi/16) out[5]; +rz(-0.32190695107235934) out[5]; +rzz(pi/2) b[1], out[5]; +ry(-pi/2) b[1]; +rzz(pi/2) a[2], b[1]; +ry(pi) a[2]; +rz(-pi) b[1]; +ry(-pi/16) b[1]; +rzz(pi/2) a[2], b[1]; +rz(-pi/2) a[2]; +rx(-pi) a[2]; +rzz(pi/2) a[2], out[6]; +rz(-pi/2) a[2]; +ry(-pi/2) b[1]; +rz(9*pi/16) b[1]; +rz(-1.2488893757225379) out[5]; +ry(pi/16) out[5]; +rz(0.9832827023934838) out[5]; +rz(0.5875136244014145) out[6]; +ry(pi/32) out[6]; +rz(1.230855619167917) out[6]; +rzz(pi/2) b[1], out[6]; +rz(-pi/2) b[1]; +rz(0.3399407076269809) out[6]; +ry(pi/32) out[6]; +rz(0.9832827023934785) out[6]; +rzz(pi/2) a[2], out[6]; +rz(0.5875136244014145) out[6]; +ry(pi/32) out[6]; +rz(1.230855619167917) out[6]; +rzz(pi/2) b[1], out[6]; +ry(-pi/2) b[1]; +rzz(pi/2) a[2], b[1]; +ry(pi) a[2]; +rz(-pi) b[1]; +ry(-pi/32) b[1]; +rzz(pi/2) a[2], b[1]; +rz(-pi/2) a[2]; +ry(pi/2) b[1]; +rz(1.472621556370215) b[1]; +rz(-2.801651945962806) out[6]; +ry(3.043417883165112) out[6]; +rz(-0.8226060790210696) out[6]; +rz(-2.801651945962806) out[7]; +ry(3.043417883165112) out[7]; +rz(-0.8226060790210696) out[7]; +rzz(pi/2) a[2], out[7]; +rz(pi/2) a[2]; +rz(2.3934024058159675) out[7]; +ry(pi/64) out[7]; +rz(2.3982406999461094) out[7]; +rzz(pi/2) b[1], out[7]; +rz(pi/2) b[1]; +rz(2.3141482804385793) out[7]; +ry(pi/64) out[7]; +rz(2.3189865745687213) out[7]; +rzz(pi/2) a[2], out[7]; +rx(pi) a[2]; +rz(2.3934024058159675) out[7]; +ry(pi/64) out[7]; +rz(2.3982406999461094) out[7]; +rzz(pi/2) b[1], out[7]; +ry(-pi/2) b[1]; +rz(-pi) b[1]; +rzz(pi/2) a[2], b[1]; +ry(pi) a[2]; +rz(-pi) b[1]; +ry(-pi/64) b[1]; +rzz(pi/2) a[2], b[1]; +rz(pi/2) a[2]; +rzz(pi/2) a[2], out[1]; +rz(pi/2) a[2]; +ry(pi/2) b[1]; +rz(1.5217089415825562) b[1]; +ry(-pi/2) out[1]; +rz(3*pi/4) out[1]; +rzz(pi/2) b[0], out[1]; +rz(pi/2) b[0]; +rz(pi/4) out[1]; +rx(-pi/2) out[1]; +rzz(pi/2) a[2], out[1]; +rx(-pi) a[2]; +rz(-pi/2) a[2]; +ry(-pi/2) out[1]; +rz(3*pi/4) out[1]; +rzz(pi/2) b[0], out[1]; +rz(pi/2) b[0]; +rzz(pi/2) a[2], b[0]; +rzz(pi/2) a[2], out[2]; +rz(-pi/2) a[2]; +rz(-pi/2) b[0]; +rx(-pi) b[0]; +rz(pi/4) out[1]; +rx(-pi/2) out[1]; +rz(-2.6051590431649654) out[2]; +ry(pi/4) out[2]; +rz(-2.5551204732437065) out[2]; +rzz(pi/2) b[0], out[2]; +rz(-pi/2) b[0]; +rz(-2.157268507140982) out[2]; +ry(pi/4) out[2]; +rz(-2.1072299372197243) out[2]; +rzz(pi/2) a[2], out[2]; +rz(-2.6051590431649654) out[2]; +ry(pi/4) out[2]; +rz(-2.5551204732437065) out[2]; +rzz(pi/2) b[0], out[2]; +ry(-pi/2) b[0]; +rzz(pi/2) a[2], b[0]; +ry(pi) a[2]; +rz(-pi) b[0]; +ry(-pi/4) b[0]; +rzz(pi/2) a[2], b[0]; +rz(-pi/2) a[2]; +rx(-pi) a[2]; +rzz(pi/2) a[2], out[3]; +rz(-pi/2) a[2]; +ry(pi/2) b[0]; +rz(pi/4) b[0]; +rz(2.555120473243707) out[2]; +rx(-3*pi/4) out[2]; +rz(-1.903385914792119) out[3]; +ry(7*pi/8) out[3]; +rz(-1.6971534443990168) out[3]; +rzz(pi/2) b[0], out[3]; +rz(pi/2) b[0]; +rz(0.12635711760412294) out[3]; +ry(7*pi/8) out[3]; +rz(-2.8090030655925666) out[3]; +rzz(pi/2) a[2], out[3]; +rz(-1.903385914792119) out[3]; +ry(7*pi/8) out[3]; +rz(-1.6971534443990168) out[3]; +rzz(pi/2) b[0], out[3]; +ry(-pi/2) b[0]; +rz(-pi) b[0]; +rzz(pi/2) a[2], b[0]; +ry(pi) a[2]; +rz(-pi) b[0]; +ry(-pi/8) b[0]; +rzz(pi/2) a[2], b[0]; +rz(-pi/2) a[2]; +rzz(pi/2) a[2], out[4]; +rz(pi/2) a[2]; +ry(-pi/2) b[0]; +rz(5*pi/8) b[0]; +rz(1.6971534443990208) out[3]; +rx(pi/8) out[3]; +rzz(pi/2) a[1], out[3]; +rz(pi/2) a[1]; +ry(-pi/2) out[3]; +rz(3*pi/4) out[3]; +rzz(pi/2) b[3], out[3]; +rz(pi/2) b[3]; +rz(pi/4) out[3]; +rx(-pi/2) out[3]; +rzz(pi/2) a[1], out[3]; +rx(-pi) a[1]; +rz(-pi/2) a[1]; +ry(-pi/2) out[3]; +rz(3*pi/4) out[3]; +rzz(pi/2) b[3], out[3]; +rz(pi/2) b[3]; +rzz(pi/2) a[1], b[3]; +rz(-pi/2) b[3]; +rx(-pi) b[3]; +rz(3*pi/4) out[3]; +ry(pi/2) out[3]; +rz(-2.1072299372197243) out[3]; +rz(0.2759350782958139) out[4]; +ry(15*pi/16) out[4]; +rz(-0.32190695107235934) out[4]; +rzz(pi/2) b[0], out[4]; +rz(-pi/2) b[0]; +rz(1.892703277867259) out[4]; +ry(15*pi/16) out[4]; +rz(-1.846731405090709) out[4]; +rzz(pi/2) a[2], out[4]; +rx(pi) a[2]; +rz(0.2759350782958139) out[4]; +ry(15*pi/16) out[4]; +rz(-0.32190695107235934) out[4]; +rzz(pi/2) b[0], out[4]; +ry(-pi/2) b[0]; +rzz(pi/2) a[2], b[0]; +ry(pi) a[2]; +rz(-pi) b[0]; +ry(-pi/16) b[0]; +rzz(pi/2) a[2], b[0]; +rz(-pi/2) a[2]; +rx(-pi) a[2]; +rzz(pi/2) a[2], out[5]; +rz(-pi/2) a[2]; +ry(-pi/2) b[0]; +rz(9*pi/16) b[0]; +rz(-1.2488893757225354) out[4]; +ry(pi/16) out[4]; +rz(-2.107229937219726) out[4]; +rzz(pi/2) a[1], out[4]; +rz(-pi/2) a[1]; +rz(-2.6051590431649654) out[4]; +ry(pi/4) out[4]; +rz(-2.5551204732437065) out[4]; +rzz(pi/2) b[3], out[4]; +rz(-pi/2) b[3]; +rz(-2.157268507140982) out[4]; +ry(pi/4) out[4]; +rz(-2.1072299372197243) out[4]; +rzz(pi/2) a[1], out[4]; +rz(-2.6051590431649654) out[4]; +ry(pi/4) out[4]; +rz(-2.5551204732437065) out[4]; +rzz(pi/2) b[3], out[4]; +ry(-pi/2) b[3]; +rzz(pi/2) a[1], b[3]; +ry(pi) a[1]; +rz(-pi) b[3]; +ry(-pi/4) b[3]; +rzz(pi/2) a[1], b[3]; +rz(-pi/2) a[1]; +rx(-pi) a[1]; +ry(pi/2) b[3]; +rz(pi/4) b[3]; +rz(-2.1572685071409827) out[4]; +ry(pi/4) out[4]; +rz(0.3325895879972256) out[4]; +rz(0.5875136244014145) out[5]; +ry(pi/32) out[5]; +rz(1.230855619167917) out[5]; +rzz(pi/2) b[0], out[5]; +rz(-pi/2) b[0]; +rz(0.3399407076269809) out[5]; +ry(pi/32) out[5]; +rz(0.9832827023934785) out[5]; +rzz(pi/2) a[2], out[5]; +rz(0.5875136244014145) out[5]; +ry(pi/32) out[5]; +rz(1.230855619167917) out[5]; +rzz(pi/2) b[0], out[5]; +ry(-pi/2) b[0]; +rzz(pi/2) a[2], b[0]; +ry(pi) a[2]; +rz(-pi) b[0]; +ry(-pi/32) b[0]; +rzz(pi/2) a[2], b[0]; +rz(-pi/2) a[2]; +rzz(pi/2) a[2], out[6]; +rz(pi/2) a[2]; +ry(pi/2) b[0]; +rz(1.472621556370215) b[0]; +rz(0.3399407076269818) out[5]; +ry(pi/32) out[5]; +rz(0.3325895879972194) out[5]; +rzz(pi/2) a[1], out[5]; +rz(-pi/2) a[1]; +rz(-1.903385914792119) out[5]; +ry(7*pi/8) out[5]; +rz(-1.6971534443990168) out[5]; +rzz(pi/2) b[3], out[5]; +rz(pi/2) b[3]; +rz(0.12635711760412294) out[5]; +ry(7*pi/8) out[5]; +rz(-2.8090030655925666) out[5]; +rzz(pi/2) a[1], out[5]; +rz(-1.903385914792119) out[5]; +ry(7*pi/8) out[5]; +rz(-1.6971534443990168) out[5]; +rzz(pi/2) b[3], out[5]; +ry(-pi/2) b[3]; +rz(-pi) b[3]; +rzz(pi/2) a[1], b[3]; +ry(pi) a[1]; +rz(-pi) b[3]; +ry(-pi/8) b[3]; +rzz(pi/2) a[1], b[3]; +rz(-pi/2) a[1]; +ry(-pi/2) b[3]; +rz(5*pi/8) b[3]; +rz(-3.0152355359856715) out[5]; +ry(pi/8) out[5]; +rz(1.294861248499081) out[5]; +rz(2.3934024058159675) out[6]; +ry(pi/64) out[6]; +rz(2.3982406999461094) out[6]; +rzz(pi/2) b[0], out[6]; +rz(pi/2) b[0]; +rz(2.3141482804385793) out[6]; +ry(pi/64) out[6]; +rz(2.3189865745687213) out[6]; +rzz(pi/2) a[2], out[6]; +rx(pi) a[2]; +rz(2.3934024058159675) out[6]; +ry(pi/64) out[6]; +rz(2.3982406999461094) out[6]; +rzz(pi/2) b[0], out[6]; +ry(-pi/2) b[0]; +rz(-pi) b[0]; +rzz(pi/2) a[2], b[0]; +ry(pi) a[2]; +rz(-pi) b[0]; +ry(-pi/64) b[0]; +rzz(pi/2) a[2], b[0]; +rz(-pi/2) a[2]; +rx(-pi) a[2]; +ry(-pi/2) b[0]; +rz(1.619883712007237) b[0]; +rz(2.3141482804385793) out[6]; +ry(pi/64) out[6]; +rz(1.294861248499089) out[6]; +rzz(pi/2) a[1], out[6]; +rz(pi/2) a[1]; +rz(0.2759350782958139) out[6]; +ry(15*pi/16) out[6]; +rz(-0.32190695107235934) out[6]; +rzz(pi/2) b[3], out[6]; +rz(-pi/2) b[3]; +rz(1.892703277867259) out[6]; +ry(15*pi/16) out[6]; +rz(-1.846731405090709) out[6]; +rzz(pi/2) a[1], out[6]; +rx(pi) a[1]; +rz(0.2759350782958139) out[6]; +ry(15*pi/16) out[6]; +rz(-0.32190695107235934) out[6]; +rzz(pi/2) b[3], out[6]; +ry(-pi/2) b[3]; +rzz(pi/2) a[1], b[3]; +ry(pi) a[1]; +rz(-pi) b[3]; +ry(-pi/16) b[3]; +rzz(pi/2) a[1], b[3]; +rz(-pi/2) a[1]; +rx(-pi) a[1]; +ry(-pi/2) b[3]; +rz(9*pi/16) b[3]; +rz(-1.2488893757225379) out[6]; +ry(pi/16) out[6]; +rz(0.9832827023934838) out[6]; +rz(-0.8274443731512049) out[7]; +ry(3.0925052683774528) out[7]; +rz(2.3696099671542044) out[7]; +rzz(pi/2) a[2], out[7]; +rz(-pi/2) a[2]; +rz(2.342779013230494) out[7]; +ry(pi/128) out[7]; +rz(2.4852630457765574) out[7]; +rzz(pi/2) b[0], out[7]; +rz(-pi/2) b[0]; +rz(-0.9144667189816644) out[7]; +ry(pi/128) out[7]; +rz(-0.7719826864355919) out[7]; +rzz(pi/2) a[2], out[7]; +rz(2.342779013230494) out[7]; +ry(pi/128) out[7]; +rz(2.4852630457765574) out[7]; +rzz(pi/2) b[0], out[7]; +ry(-pi/2) b[0]; +rzz(pi/2) a[2], b[0]; +ry(pi) a[2]; +rz(-pi) b[0]; +ry(-pi/128) b[0]; +rzz(pi/2) a[2], b[0]; +rz(-pi/2) a[2]; +rx(-pi) a[2]; +ry(pi/2) b[0]; +rz(1.5462526341887255) b[0]; +rz(-0.9144667189816644) out[7]; +ry(pi/128) out[7]; +rz(0.9832827023934811) out[7]; +rzz(pi/2) a[1], out[7]; +rz(-pi/2) a[1]; +rz(0.5875136244014145) out[7]; +ry(pi/32) out[7]; +rz(1.230855619167917) out[7]; +rzz(pi/2) b[3], out[7]; +rz(-pi/2) b[3]; +rz(0.3399407076269809) out[7]; +ry(pi/32) out[7]; +rz(0.9832827023934785) out[7]; +rzz(pi/2) a[1], out[7]; +rz(0.5875136244014145) out[7]; +ry(pi/32) out[7]; +rz(1.230855619167917) out[7]; +rzz(pi/2) b[3], out[7]; +ry(-pi/2) b[3]; +rzz(pi/2) a[1], b[3]; +ry(pi) a[1]; +rz(-pi) b[3]; +ry(-pi/32) b[3]; +rzz(pi/2) a[1], b[3]; +rz(pi/2) a[1]; +rzz(pi/2) a[1], out[2]; +rz(pi/2) a[1]; +ry(pi/2) b[3]; +rz(1.472621556370215) b[3]; +ry(-pi/2) out[2]; +rz(3*pi/4) out[2]; +rzz(pi/2) b[2], out[2]; +rz(pi/2) b[2]; +rz(pi/4) out[2]; +rx(-pi/2) out[2]; +rzz(pi/2) a[1], out[2]; +rx(-pi) a[1]; +rz(-pi/2) a[1]; +ry(-pi/2) out[2]; +rz(3*pi/4) out[2]; +rzz(pi/2) b[2], out[2]; +rz(pi/2) b[2]; +rzz(pi/2) a[1], b[2]; +rzz(pi/2) a[1], out[3]; +rz(-pi/2) a[1]; +rz(-pi/2) b[2]; +rx(-pi) b[2]; +rz(3*pi/4) out[2]; +ry(pi/2) out[2]; +rz(-2.1072299372197243) out[2]; +rz(-2.6051590431649654) out[3]; +ry(pi/4) out[3]; +rz(-2.5551204732437065) out[3]; +rzz(pi/2) b[2], out[3]; +rz(-pi/2) b[2]; +rz(-2.157268507140982) out[3]; +ry(pi/4) out[3]; +rz(-2.1072299372197243) out[3]; +rzz(pi/2) a[1], out[3]; +rz(-2.6051590431649654) out[3]; +ry(pi/4) out[3]; +rz(-2.5551204732437065) out[3]; +rzz(pi/2) b[2], out[3]; +ry(-pi/2) b[2]; +rzz(pi/2) a[1], b[2]; +ry(pi) a[1]; +rz(-pi) b[2]; +ry(-pi/4) b[2]; +rzz(pi/2) a[1], b[2]; +rz(-pi/2) a[1]; +rx(-pi) a[1]; +rzz(pi/2) a[1], out[4]; +rz(-pi/2) a[1]; +ry(pi/2) b[2]; +rz(pi/4) b[2]; +rz(-2.1572685071409827) out[3]; +ry(pi/4) out[3]; +rz(0.3325895879972256) out[3]; +rz(-1.903385914792119) out[4]; +ry(7*pi/8) out[4]; +rz(-1.6971534443990168) out[4]; +rzz(pi/2) b[2], out[4]; +rz(pi/2) b[2]; +rz(0.12635711760412294) out[4]; +ry(7*pi/8) out[4]; +rz(-2.8090030655925666) out[4]; +rzz(pi/2) a[1], out[4]; +rz(-1.903385914792119) out[4]; +ry(7*pi/8) out[4]; +rz(-1.6971534443990168) out[4]; +rzz(pi/2) b[2], out[4]; +ry(-pi/2) b[2]; +rz(-pi) b[2]; +rzz(pi/2) a[1], b[2]; +ry(pi) a[1]; +rz(-pi) b[2]; +ry(-pi/8) b[2]; +rzz(pi/2) a[1], b[2]; +rz(-pi/2) a[1]; +rzz(pi/2) a[1], out[5]; +rz(pi/2) a[1]; +ry(-pi/2) b[2]; +rz(5*pi/8) b[2]; +rz(-3.0152355359856715) out[4]; +ry(pi/8) out[4]; +rz(1.294861248499081) out[4]; +rz(0.2759350782958139) out[5]; +ry(15*pi/16) out[5]; +rz(-0.32190695107235934) out[5]; +rzz(pi/2) b[2], out[5]; +rz(-pi/2) b[2]; +rz(1.892703277867259) out[5]; +ry(15*pi/16) out[5]; +rz(-1.846731405090709) out[5]; +rzz(pi/2) a[1], out[5]; +rx(pi) a[1]; +rz(0.2759350782958139) out[5]; +ry(15*pi/16) out[5]; +rz(-0.32190695107235934) out[5]; +rzz(pi/2) b[2], out[5]; +ry(-pi/2) b[2]; +rzz(pi/2) a[1], b[2]; +ry(pi) a[1]; +rz(-pi) b[2]; +ry(-pi/16) b[2]; +rzz(pi/2) a[1], b[2]; +rz(-pi/2) a[1]; +rx(-pi) a[1]; +rzz(pi/2) a[1], out[6]; +rz(-pi/2) a[1]; +ry(-pi/2) b[2]; +rz(9*pi/16) b[2]; +rz(-1.2488893757225379) out[5]; +ry(pi/16) out[5]; +rz(0.9832827023934838) out[5]; +rz(0.5875136244014145) out[6]; +ry(pi/32) out[6]; +rz(1.230855619167917) out[6]; +rzz(pi/2) b[2], out[6]; +rz(-pi/2) b[2]; +rz(0.3399407076269809) out[6]; +ry(pi/32) out[6]; +rz(0.9832827023934785) out[6]; +rzz(pi/2) a[1], out[6]; +rz(0.5875136244014145) out[6]; +ry(pi/32) out[6]; +rz(1.230855619167917) out[6]; +rzz(pi/2) b[2], out[6]; +ry(-pi/2) b[2]; +rzz(pi/2) a[1], b[2]; +ry(pi) a[1]; +rz(-pi) b[2]; +ry(-pi/32) b[2]; +rzz(pi/2) a[1], b[2]; +rz(-pi/2) a[1]; +ry(pi/2) b[2]; +rz(1.472621556370215) b[2]; +rz(-2.801651945962806) out[6]; +ry(3.043417883165112) out[6]; +rz(-0.8226060790210696) out[6]; +rz(-2.801651945962806) out[7]; +ry(3.043417883165112) out[7]; +rz(-0.8226060790210696) out[7]; +rzz(pi/2) a[1], out[7]; +rz(pi/2) a[1]; +rz(2.3934024058159675) out[7]; +ry(pi/64) out[7]; +rz(2.3982406999461094) out[7]; +rzz(pi/2) b[2], out[7]; +rz(pi/2) b[2]; +rz(2.3141482804385793) out[7]; +ry(pi/64) out[7]; +rz(2.3189865745687213) out[7]; +rzz(pi/2) a[1], out[7]; +rx(pi) a[1]; +rz(2.3934024058159675) out[7]; +ry(pi/64) out[7]; +rz(2.3982406999461094) out[7]; +rzz(pi/2) b[2], out[7]; +ry(-pi/2) b[2]; +rz(-pi) b[2]; +rzz(pi/2) a[1], b[2]; +ry(pi) a[1]; +rz(-pi) b[2]; +ry(-pi/64) b[2]; +rzz(pi/2) a[1], b[2]; +rz(pi/2) a[1]; +rzz(pi/2) a[1], out[1]; +rz(pi/2) a[1]; +ry(pi/2) b[2]; +rz(1.5217089415825562) b[2]; +ry(-pi/2) out[1]; +rz(3*pi/4) out[1]; +rzz(pi/2) b[1], out[1]; +rz(pi/2) b[1]; +rz(pi/4) out[1]; +rx(-pi/2) out[1]; +rzz(pi/2) a[1], out[1]; +rx(-pi) a[1]; +rz(-pi/2) a[1]; +ry(-pi/2) out[1]; +rz(3*pi/4) out[1]; +rzz(pi/2) b[1], out[1]; +rz(pi/2) b[1]; +rzz(pi/2) a[1], b[1]; +rzz(pi/2) a[1], out[2]; +rz(-pi/2) a[1]; +rz(-pi/2) b[1]; +rx(-pi) b[1]; +rz(3*pi/4) out[1]; +ry(pi/2) out[1]; +rz(-2.1072299372197243) out[1]; +rz(-2.6051590431649654) out[2]; +ry(pi/4) out[2]; +rz(-2.5551204732437065) out[2]; +rzz(pi/2) b[1], out[2]; +rz(-pi/2) b[1]; +rz(-2.157268507140982) out[2]; +ry(pi/4) out[2]; +rz(-2.1072299372197243) out[2]; +rzz(pi/2) a[1], out[2]; +rz(-2.6051590431649654) out[2]; +ry(pi/4) out[2]; +rz(-2.5551204732437065) out[2]; +rzz(pi/2) b[1], out[2]; +ry(-pi/2) b[1]; +rzz(pi/2) a[1], b[1]; +ry(pi) a[1]; +rz(-pi) b[1]; +ry(-pi/4) b[1]; +rzz(pi/2) a[1], b[1]; +rz(-pi/2) a[1]; +rx(-pi) a[1]; +rzz(pi/2) a[1], out[3]; +rz(-pi/2) a[1]; +ry(pi/2) b[1]; +rz(pi/4) b[1]; +rz(-2.1572685071409827) out[2]; +ry(pi/4) out[2]; +rz(0.3325895879972256) out[2]; +rz(-1.903385914792119) out[3]; +ry(7*pi/8) out[3]; +rz(-1.6971534443990168) out[3]; +rzz(pi/2) b[1], out[3]; +rz(pi/2) b[1]; +rz(0.12635711760412294) out[3]; +ry(7*pi/8) out[3]; +rz(-2.8090030655925666) out[3]; +rzz(pi/2) a[1], out[3]; +rz(-1.903385914792119) out[3]; +ry(7*pi/8) out[3]; +rz(-1.6971534443990168) out[3]; +rzz(pi/2) b[1], out[3]; +ry(-pi/2) b[1]; +rz(-pi) b[1]; +rzz(pi/2) a[1], b[1]; +ry(pi) a[1]; +rz(-pi) b[1]; +ry(-pi/8) b[1]; +rzz(pi/2) a[1], b[1]; +rz(-pi/2) a[1]; +rzz(pi/2) a[1], out[4]; +rz(pi/2) a[1]; +ry(-pi/2) b[1]; +rz(5*pi/8) b[1]; +rz(-3.0152355359856715) out[3]; +ry(pi/8) out[3]; +rz(1.294861248499081) out[3]; +rz(0.2759350782958139) out[4]; +ry(15*pi/16) out[4]; +rz(-0.32190695107235934) out[4]; +rzz(pi/2) b[1], out[4]; +rz(-pi/2) b[1]; +rz(1.892703277867259) out[4]; +ry(15*pi/16) out[4]; +rz(-1.846731405090709) out[4]; +rzz(pi/2) a[1], out[4]; +rx(pi) a[1]; +rz(0.2759350782958139) out[4]; +ry(15*pi/16) out[4]; +rz(-0.32190695107235934) out[4]; +rzz(pi/2) b[1], out[4]; +ry(-pi/2) b[1]; +rzz(pi/2) a[1], b[1]; +ry(pi) a[1]; +rz(-pi) b[1]; +ry(-pi/16) b[1]; +rzz(pi/2) a[1], b[1]; +rz(-pi/2) a[1]; +rx(-pi) a[1]; +rzz(pi/2) a[1], out[5]; +rz(-pi/2) a[1]; +ry(-pi/2) b[1]; +rz(9*pi/16) b[1]; +rz(-1.2488893757225379) out[4]; +ry(pi/16) out[4]; +rz(0.9832827023934838) out[4]; +rz(0.5875136244014145) out[5]; +ry(pi/32) out[5]; +rz(1.230855619167917) out[5]; +rzz(pi/2) b[1], out[5]; +rz(-pi/2) b[1]; +rz(0.3399407076269809) out[5]; +ry(pi/32) out[5]; +rz(0.9832827023934785) out[5]; +rzz(pi/2) a[1], out[5]; +rz(0.5875136244014145) out[5]; +ry(pi/32) out[5]; +rz(1.230855619167917) out[5]; +rzz(pi/2) b[1], out[5]; +ry(-pi/2) b[1]; +rzz(pi/2) a[1], b[1]; +ry(pi) a[1]; +rz(-pi) b[1]; +ry(-pi/32) b[1]; +rzz(pi/2) a[1], b[1]; +rz(-pi/2) a[1]; +rzz(pi/2) a[1], out[6]; +rz(pi/2) a[1]; +ry(pi/2) b[1]; +rz(1.472621556370215) b[1]; +rz(-2.801651945962806) out[5]; +ry(3.043417883165112) out[5]; +rz(-0.8226060790210696) out[5]; +rz(2.3934024058159675) out[6]; +ry(pi/64) out[6]; +rz(2.3982406999461094) out[6]; +rzz(pi/2) b[1], out[6]; +rz(pi/2) b[1]; +rz(2.3141482804385793) out[6]; +ry(pi/64) out[6]; +rz(2.3189865745687213) out[6]; +rzz(pi/2) a[1], out[6]; +rx(pi) a[1]; +rz(2.3934024058159675) out[6]; +ry(pi/64) out[6]; +rz(2.3982406999461094) out[6]; +rzz(pi/2) b[1], out[6]; +ry(-pi/2) b[1]; +rz(-pi) b[1]; +rzz(pi/2) a[1], b[1]; +ry(pi) a[1]; +rz(-pi) b[1]; +ry(-pi/64) b[1]; +rzz(pi/2) a[1], b[1]; +rz(-pi/2) a[1]; +rx(-pi) a[1]; +ry(-pi/2) b[1]; +rz(1.619883712007237) b[1]; +rz(-0.8274443731512049) out[6]; +ry(3.0925052683774528) out[6]; +rz(2.3696099671542044) out[6]; +rz(-0.8274443731512049) out[7]; +ry(3.0925052683774528) out[7]; +rz(2.3696099671542044) out[7]; +rzz(pi/2) a[1], out[7]; +rz(-pi/2) a[1]; +rz(2.342779013230494) out[7]; +ry(pi/128) out[7]; +rz(2.4852630457765574) out[7]; +rzz(pi/2) b[1], out[7]; +rz(-pi/2) b[1]; +rz(-0.9144667189816644) out[7]; +ry(pi/128) out[7]; +rz(-0.7719826864355919) out[7]; +rzz(pi/2) a[1], out[7]; +rz(2.342779013230494) out[7]; +ry(pi/128) out[7]; +rz(2.4852630457765574) out[7]; +rzz(pi/2) b[1], out[7]; +ry(-pi/2) b[1]; +rzz(pi/2) a[1], b[1]; +ry(pi) a[1]; +rz(-pi) b[1]; +ry(-pi/128) b[1]; +rzz(pi/2) a[1], b[1]; +rz(-pi/2) a[1]; +rzz(pi/2) a[1], out[0]; +rz(pi/2) a[1]; +ry(pi/2) b[1]; +rz(1.5462526341887255) b[1]; +ry(-pi/2) out[0]; +rz(3*pi/4) out[0]; +rzz(pi/2) b[0], out[0]; +rz(pi/2) b[0]; +rz(pi/4) out[0]; +rx(-pi/2) out[0]; +rzz(pi/2) a[1], out[0]; +rx(-pi) a[1]; +rz(-pi/2) a[1]; +ry(-pi/2) out[0]; +rz(3*pi/4) out[0]; +rzz(pi/2) b[0], out[0]; +rz(pi/2) b[0]; +rzz(pi/2) a[1], b[0]; +rzz(pi/2) a[1], out[1]; +rz(-pi/2) a[1]; +rz(-pi/2) b[0]; +rx(-pi) b[0]; +rz(pi/4) out[0]; +rx(-pi/2) out[0]; +rz(-2.6051590431649654) out[1]; +ry(pi/4) out[1]; +rz(-2.5551204732437065) out[1]; +rzz(pi/2) b[0], out[1]; +rz(-pi/2) b[0]; +rz(-2.157268507140982) out[1]; +ry(pi/4) out[1]; +rz(-2.1072299372197243) out[1]; +rzz(pi/2) a[1], out[1]; +rz(-2.6051590431649654) out[1]; +ry(pi/4) out[1]; +rz(-2.5551204732437065) out[1]; +rzz(pi/2) b[0], out[1]; +ry(-pi/2) b[0]; +rzz(pi/2) a[1], b[0]; +ry(pi) a[1]; +rz(-pi) b[0]; +ry(-pi/4) b[0]; +rzz(pi/2) a[1], b[0]; +rz(-pi/2) a[1]; +rx(-pi) a[1]; +rzz(pi/2) a[1], out[2]; +rz(-pi/2) a[1]; +ry(pi/2) b[0]; +rz(pi/4) b[0]; +rz(2.555120473243707) out[1]; +rx(-3*pi/4) out[1]; +rz(-1.903385914792119) out[2]; +ry(7*pi/8) out[2]; +rz(-1.6971534443990168) out[2]; +rzz(pi/2) b[0], out[2]; +rz(pi/2) b[0]; +rz(0.12635711760412294) out[2]; +ry(7*pi/8) out[2]; +rz(-2.8090030655925666) out[2]; +rzz(pi/2) a[1], out[2]; +rz(-1.903385914792119) out[2]; +ry(7*pi/8) out[2]; +rz(-1.6971534443990168) out[2]; +rzz(pi/2) b[0], out[2]; +ry(-pi/2) b[0]; +rz(-pi) b[0]; +rzz(pi/2) a[1], b[0]; +ry(pi) a[1]; +rz(-pi) b[0]; +ry(-pi/8) b[0]; +rzz(pi/2) a[1], b[0]; +rz(-pi/2) a[1]; +rzz(pi/2) a[1], out[3]; +rz(pi/2) a[1]; +ry(-pi/2) b[0]; +rz(5*pi/8) b[0]; +rz(1.6971534443990208) out[2]; +rx(pi/8) out[2]; +rzz(pi/2) a[0], out[2]; +rz(pi/2) a[0]; +ry(-pi/2) out[2]; +rz(3*pi/4) out[2]; +rzz(pi/2) b[3], out[2]; +rz(pi/2) b[3]; +rz(pi/4) out[2]; +rx(-pi/2) out[2]; +rzz(pi/2) a[0], out[2]; +rx(-pi) a[0]; +rz(-pi/2) a[0]; +ry(-pi/2) out[2]; +rz(3*pi/4) out[2]; +rzz(pi/2) b[3], out[2]; +rz(pi/2) b[3]; +rzz(pi/2) a[0], b[3]; +rz(-pi/2) b[3]; +rx(-pi) b[3]; +rz(3*pi/4) out[2]; +ry(pi/2) out[2]; +rz(-2.1072299372197243) out[2]; +rz(0.2759350782958139) out[3]; +ry(15*pi/16) out[3]; +rz(-0.32190695107235934) out[3]; +rzz(pi/2) b[0], out[3]; +rz(-pi/2) b[0]; +rz(1.892703277867259) out[3]; +ry(15*pi/16) out[3]; +rz(-1.846731405090709) out[3]; +rzz(pi/2) a[1], out[3]; +rx(pi) a[1]; +rz(0.2759350782958139) out[3]; +ry(15*pi/16) out[3]; +rz(-0.32190695107235934) out[3]; +rzz(pi/2) b[0], out[3]; +ry(-pi/2) b[0]; +rzz(pi/2) a[1], b[0]; +ry(pi) a[1]; +rz(-pi) b[0]; +ry(-pi/16) b[0]; +rzz(pi/2) a[1], b[0]; +rz(-pi/2) a[1]; +rx(-pi) a[1]; +rzz(pi/2) a[1], out[4]; +rz(-pi/2) a[1]; +ry(-pi/2) b[0]; +rz(9*pi/16) b[0]; +rz(-1.2488893757225354) out[3]; +ry(pi/16) out[3]; +rz(-2.107229937219726) out[3]; +rzz(pi/2) a[0], out[3]; +rz(-pi/2) a[0]; +rz(-2.6051590431649654) out[3]; +ry(pi/4) out[3]; +rz(-2.5551204732437065) out[3]; +rzz(pi/2) b[3], out[3]; +rz(-pi/2) b[3]; +rz(-2.157268507140982) out[3]; +ry(pi/4) out[3]; +rz(-2.1072299372197243) out[3]; +rzz(pi/2) a[0], out[3]; +rz(-2.6051590431649654) out[3]; +ry(pi/4) out[3]; +rz(-2.5551204732437065) out[3]; +rzz(pi/2) b[3], out[3]; +ry(-pi/2) b[3]; +rzz(pi/2) a[0], b[3]; +ry(pi) a[0]; +rz(-pi) b[3]; +ry(-pi/4) b[3]; +rzz(pi/2) a[0], b[3]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +ry(pi/2) b[3]; +rz(pi/4) b[3]; +rz(-2.1572685071409827) out[3]; +ry(pi/4) out[3]; +rz(0.3325895879972256) out[3]; +rz(0.5875136244014145) out[4]; +ry(pi/32) out[4]; +rz(1.230855619167917) out[4]; +rzz(pi/2) b[0], out[4]; +rz(-pi/2) b[0]; +rz(0.3399407076269809) out[4]; +ry(pi/32) out[4]; +rz(0.9832827023934785) out[4]; +rzz(pi/2) a[1], out[4]; +rz(0.5875136244014145) out[4]; +ry(pi/32) out[4]; +rz(1.230855619167917) out[4]; +rzz(pi/2) b[0], out[4]; +ry(-pi/2) b[0]; +rzz(pi/2) a[1], b[0]; +ry(pi) a[1]; +rz(-pi) b[0]; +ry(-pi/32) b[0]; +rzz(pi/2) a[1], b[0]; +rz(-pi/2) a[1]; +rzz(pi/2) a[1], out[5]; +rz(pi/2) a[1]; +ry(pi/2) b[0]; +rz(1.472621556370215) b[0]; +rz(0.3399407076269818) out[4]; +ry(pi/32) out[4]; +rz(0.3325895879972194) out[4]; +rzz(pi/2) a[0], out[4]; +rz(-pi/2) a[0]; +rz(-1.903385914792119) out[4]; +ry(7*pi/8) out[4]; +rz(-1.6971534443990168) out[4]; +rzz(pi/2) b[3], out[4]; +rz(pi/2) b[3]; +rz(0.12635711760412294) out[4]; +ry(7*pi/8) out[4]; +rz(-2.8090030655925666) out[4]; +rzz(pi/2) a[0], out[4]; +rz(-1.903385914792119) out[4]; +ry(7*pi/8) out[4]; +rz(-1.6971534443990168) out[4]; +rzz(pi/2) b[3], out[4]; +ry(-pi/2) b[3]; +rz(-pi) b[3]; +rzz(pi/2) a[0], b[3]; +ry(pi) a[0]; +rz(-pi) b[3]; +ry(-pi/8) b[3]; +rzz(pi/2) a[0], b[3]; +rz(-pi/2) a[0]; +ry(-pi/2) b[3]; +rz(5*pi/8) b[3]; +rz(-3.0152355359856715) out[4]; +ry(pi/8) out[4]; +rz(1.294861248499081) out[4]; +rz(2.3934024058159675) out[5]; +ry(pi/64) out[5]; +rz(2.3982406999461094) out[5]; +rzz(pi/2) b[0], out[5]; +rz(pi/2) b[0]; +rz(2.3141482804385793) out[5]; +ry(pi/64) out[5]; +rz(2.3189865745687213) out[5]; +rzz(pi/2) a[1], out[5]; +rx(pi) a[1]; +rz(2.3934024058159675) out[5]; +ry(pi/64) out[5]; +rz(2.3982406999461094) out[5]; +rzz(pi/2) b[0], out[5]; +ry(-pi/2) b[0]; +rz(-pi) b[0]; +rzz(pi/2) a[1], b[0]; +ry(pi) a[1]; +rz(-pi) b[0]; +ry(-pi/64) b[0]; +rzz(pi/2) a[1], b[0]; +rz(-pi/2) a[1]; +rx(-pi) a[1]; +rzz(pi/2) a[1], out[6]; +rz(-pi/2) a[1]; +ry(-pi/2) b[0]; +rz(1.619883712007237) b[0]; +rz(2.3141482804385793) out[5]; +ry(pi/64) out[5]; +rz(1.294861248499089) out[5]; +rzz(pi/2) a[0], out[5]; +rz(pi/2) a[0]; +rz(0.2759350782958139) out[5]; +ry(15*pi/16) out[5]; +rz(-0.32190695107235934) out[5]; +rzz(pi/2) b[3], out[5]; +rz(-pi/2) b[3]; +rz(1.892703277867259) out[5]; +ry(15*pi/16) out[5]; +rz(-1.846731405090709) out[5]; +rzz(pi/2) a[0], out[5]; +rx(pi) a[0]; +rz(0.2759350782958139) out[5]; +ry(15*pi/16) out[5]; +rz(-0.32190695107235934) out[5]; +rzz(pi/2) b[3], out[5]; +ry(-pi/2) b[3]; +rzz(pi/2) a[0], b[3]; +ry(pi) a[0]; +rz(-pi) b[3]; +ry(-pi/16) b[3]; +rzz(pi/2) a[0], b[3]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +ry(-pi/2) b[3]; +rz(9*pi/16) b[3]; +rz(-1.2488893757225379) out[5]; +ry(pi/16) out[5]; +rz(0.9832827023934838) out[5]; +rz(2.342779013230494) out[6]; +ry(pi/128) out[6]; +rz(2.4852630457765574) out[6]; +rzz(pi/2) b[0], out[6]; +rz(-pi/2) b[0]; +rz(-0.9144667189816644) out[6]; +ry(pi/128) out[6]; +rz(-0.7719826864355919) out[6]; +rzz(pi/2) a[1], out[6]; +rz(2.342779013230494) out[6]; +ry(pi/128) out[6]; +rz(2.4852630457765574) out[6]; +rzz(pi/2) b[0], out[6]; +ry(-pi/2) b[0]; +rzz(pi/2) a[1], b[0]; +ry(pi) a[1]; +rz(-pi) b[0]; +ry(-pi/128) b[0]; +rzz(pi/2) a[1], b[0]; +rz(-pi/2) a[1]; +ry(-pi/2) b[0]; +rz(1.5953400194010676) b[0]; +rz(-0.9144667189816644) out[6]; +ry(pi/128) out[6]; +rz(0.9832827023934811) out[6]; +rzz(pi/2) a[0], out[6]; +rz(-pi/2) a[0]; +rz(0.5875136244014145) out[6]; +ry(pi/32) out[6]; +rz(1.230855619167917) out[6]; +rzz(pi/2) b[3], out[6]; +rz(-pi/2) b[3]; +rz(0.3399407076269809) out[6]; +ry(pi/32) out[6]; +rz(0.9832827023934785) out[6]; +rzz(pi/2) a[0], out[6]; +rz(0.5875136244014145) out[6]; +ry(pi/32) out[6]; +rz(1.230855619167917) out[6]; +rzz(pi/2) b[3], out[6]; +ry(-pi/2) b[3]; +rzz(pi/2) a[0], b[3]; +ry(pi) a[0]; +rz(-pi) b[3]; +ry(-pi/32) b[3]; +rzz(pi/2) a[0], b[3]; +rz(-pi/2) a[0]; +ry(pi/2) b[3]; +rz(1.472621556370215) b[3]; +rz(-2.801651945962806) out[6]; +ry(3.043417883165112) out[6]; +rz(-0.8226060790210696) out[6]; +rz(2.2271259346081145) out[7]; +ry(3.1170489609836234) out[7]; +rz(2.0614966236345236) out[7]; +rzz(pi/2) a[1], out[7]; +rz(pi/2) a[1]; +rz(2.6508923567501874) out[7]; +ry(3.1293208072867085) out[7]; +rz(3.107697059996571) out[7]; +rzz(pi/2) b[0], out[7]; +rz(-pi/2) b[0]; +rz(-1.536900733201678) out[7]; +ry(3.129320807286707) out[7]; +rz(2.0614966236345014) out[7]; +rzz(pi/2) a[1], out[7]; +rx(pi) a[1]; +rz(2.6508923567501874) out[7]; +ry(3.1293208072867085) out[7]; +rz(3.107697059996571) out[7]; +rzz(pi/2) b[0], out[7]; +ry(-pi/2) b[0]; +rzz(pi/2) a[1], b[0]; +ry(pi) a[1]; +rz(-pi) b[0]; +ry(-pi/256) b[0]; +rzz(pi/2) a[1], b[0]; +rz(-pi/2) a[1]; +rx(-pi) a[1]; +ry(-pi/2) b[0]; +rz(1.5830681730979812) b[0]; +rz(-1.5369007332016145) out[7]; +ry(3.129320807286707) out[7]; +rz(-0.8226060790210514) out[7]; +rzz(pi/2) a[0], out[7]; +rz(pi/2) a[0]; +rz(2.3934024058159675) out[7]; +ry(pi/64) out[7]; +rz(2.3982406999461094) out[7]; +rzz(pi/2) b[3], out[7]; +rz(pi/2) b[3]; +rz(2.3141482804385793) out[7]; +ry(pi/64) out[7]; +rz(2.3189865745687213) out[7]; +rzz(pi/2) a[0], out[7]; +rx(pi) a[0]; +rz(2.3934024058159675) out[7]; +ry(pi/64) out[7]; +rz(2.3982406999461094) out[7]; +rzz(pi/2) b[3], out[7]; +ry(-pi/2) b[3]; +rz(-pi) b[3]; +rzz(pi/2) a[0], b[3]; +ry(pi) a[0]; +rz(-pi) b[3]; +ry(-pi/64) b[3]; +rzz(pi/2) a[0], b[3]; +rz(pi/2) a[0]; +rzz(pi/2) a[0], out[1]; +rz(pi/2) a[0]; +ry(-pi/2) b[3]; +rz(1.619883712007237) b[3]; +ry(-pi/2) out[1]; +rz(3*pi/4) out[1]; +rzz(pi/2) b[2], out[1]; +rz(pi/2) b[2]; +rz(pi/4) out[1]; +rx(-pi/2) out[1]; +rzz(pi/2) a[0], out[1]; +rx(-pi) a[0]; +rz(-pi/2) a[0]; +ry(-pi/2) out[1]; +rz(3*pi/4) out[1]; +rzz(pi/2) b[2], out[1]; +rz(pi/2) b[2]; +rzz(pi/2) a[0], b[2]; +rzz(pi/2) a[0], out[2]; +rz(-pi/2) a[0]; +rz(-pi/2) b[2]; +rx(-pi) b[2]; +rz(3*pi/4) out[1]; +ry(pi/2) out[1]; +rz(-2.1072299372197243) out[1]; +rz(-2.6051590431649654) out[2]; +ry(pi/4) out[2]; +rz(-2.5551204732437065) out[2]; +rzz(pi/2) b[2], out[2]; +rz(-pi/2) b[2]; +rz(-2.157268507140982) out[2]; +ry(pi/4) out[2]; +rz(-2.1072299372197243) out[2]; +rzz(pi/2) a[0], out[2]; +rz(-2.6051590431649654) out[2]; +ry(pi/4) out[2]; +rz(-2.5551204732437065) out[2]; +rzz(pi/2) b[2], out[2]; +ry(-pi/2) b[2]; +rzz(pi/2) a[0], b[2]; +ry(pi) a[0]; +rz(-pi) b[2]; +ry(-pi/4) b[2]; +rzz(pi/2) a[0], b[2]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +rzz(pi/2) a[0], out[3]; +rz(-pi/2) a[0]; +ry(pi/2) b[2]; +rz(pi/4) b[2]; +rz(-2.1572685071409827) out[2]; +ry(pi/4) out[2]; +rz(0.3325895879972256) out[2]; +rz(-1.903385914792119) out[3]; +ry(7*pi/8) out[3]; +rz(-1.6971534443990168) out[3]; +rzz(pi/2) b[2], out[3]; +rz(pi/2) b[2]; +rz(0.12635711760412294) out[3]; +ry(7*pi/8) out[3]; +rz(-2.8090030655925666) out[3]; +rzz(pi/2) a[0], out[3]; +rz(-1.903385914792119) out[3]; +ry(7*pi/8) out[3]; +rz(-1.6971534443990168) out[3]; +rzz(pi/2) b[2], out[3]; +ry(-pi/2) b[2]; +rz(-pi) b[2]; +rzz(pi/2) a[0], b[2]; +ry(pi) a[0]; +rz(-pi) b[2]; +ry(-pi/8) b[2]; +rzz(pi/2) a[0], b[2]; +rz(-pi/2) a[0]; +rzz(pi/2) a[0], out[4]; +rz(pi/2) a[0]; +ry(-pi/2) b[2]; +rz(5*pi/8) b[2]; +rz(-3.0152355359856715) out[3]; +ry(pi/8) out[3]; +rz(1.294861248499081) out[3]; +rz(0.2759350782958139) out[4]; +ry(15*pi/16) out[4]; +rz(-0.32190695107235934) out[4]; +rzz(pi/2) b[2], out[4]; +rz(-pi/2) b[2]; +rz(1.892703277867259) out[4]; +ry(15*pi/16) out[4]; +rz(-1.846731405090709) out[4]; +rzz(pi/2) a[0], out[4]; +rx(pi) a[0]; +rz(0.2759350782958139) out[4]; +ry(15*pi/16) out[4]; +rz(-0.32190695107235934) out[4]; +rzz(pi/2) b[2], out[4]; +ry(-pi/2) b[2]; +rzz(pi/2) a[0], b[2]; +ry(pi) a[0]; +rz(-pi) b[2]; +ry(-pi/16) b[2]; +rzz(pi/2) a[0], b[2]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +rzz(pi/2) a[0], out[5]; +rz(-pi/2) a[0]; +ry(-pi/2) b[2]; +rz(9*pi/16) b[2]; +rz(-1.2488893757225379) out[4]; +ry(pi/16) out[4]; +rz(0.9832827023934838) out[4]; +rz(0.5875136244014145) out[5]; +ry(pi/32) out[5]; +rz(1.230855619167917) out[5]; +rzz(pi/2) b[2], out[5]; +rz(-pi/2) b[2]; +rz(0.3399407076269809) out[5]; +ry(pi/32) out[5]; +rz(0.9832827023934785) out[5]; +rzz(pi/2) a[0], out[5]; +rz(0.5875136244014145) out[5]; +ry(pi/32) out[5]; +rz(1.230855619167917) out[5]; +rzz(pi/2) b[2], out[5]; +ry(-pi/2) b[2]; +rzz(pi/2) a[0], b[2]; +ry(pi) a[0]; +rz(-pi) b[2]; +ry(-pi/32) b[2]; +rzz(pi/2) a[0], b[2]; +rz(-pi/2) a[0]; +rzz(pi/2) a[0], out[6]; +rz(pi/2) a[0]; +ry(pi/2) b[2]; +rz(1.472621556370215) b[2]; +rz(-2.801651945962806) out[5]; +ry(3.043417883165112) out[5]; +rz(-0.8226060790210696) out[5]; +rz(2.3934024058159675) out[6]; +ry(pi/64) out[6]; +rz(2.3982406999461094) out[6]; +rzz(pi/2) b[2], out[6]; +rz(pi/2) b[2]; +rz(2.3141482804385793) out[6]; +ry(pi/64) out[6]; +rz(2.3189865745687213) out[6]; +rzz(pi/2) a[0], out[6]; +rx(pi) a[0]; +rz(2.3934024058159675) out[6]; +ry(pi/64) out[6]; +rz(2.3982406999461094) out[6]; +rzz(pi/2) b[2], out[6]; +ry(-pi/2) b[2]; +rz(-pi) b[2]; +rzz(pi/2) a[0], b[2]; +ry(pi) a[0]; +rz(-pi) b[2]; +ry(-pi/64) b[2]; +rzz(pi/2) a[0], b[2]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +ry(-pi/2) b[2]; +rz(1.619883712007237) b[2]; +rz(-0.8274443731512049) out[6]; +ry(3.0925052683774528) out[6]; +rz(2.3696099671542044) out[6]; +rz(-0.8274443731512049) out[7]; +ry(3.0925052683774528) out[7]; +rz(2.3696099671542044) out[7]; +rzz(pi/2) a[0], out[7]; +rz(-pi/2) a[0]; +rz(2.342779013230494) out[7]; +ry(pi/128) out[7]; +rz(2.4852630457765574) out[7]; +rzz(pi/2) b[2], out[7]; +rz(-pi/2) b[2]; +rz(-0.9144667189816644) out[7]; +ry(pi/128) out[7]; +rz(-0.7719826864355919) out[7]; +rzz(pi/2) a[0], out[7]; +rz(2.342779013230494) out[7]; +ry(pi/128) out[7]; +rz(2.4852630457765574) out[7]; +rzz(pi/2) b[2], out[7]; +ry(-pi/2) b[2]; +rzz(pi/2) a[0], b[2]; +ry(pi) a[0]; +rz(-pi) b[2]; +ry(-pi/128) b[2]; +rzz(pi/2) a[0], b[2]; +rz(-pi/2) a[0]; +rzz(pi/2) a[0], out[0]; +rz(pi/2) a[0]; +ry(-pi/2) b[2]; +rz(1.5953400194010676) b[2]; +ry(-pi/2) out[0]; +rz(3*pi/4) out[0]; +rzz(pi/2) b[1], out[0]; +rz(pi/2) b[1]; +rz(pi/4) out[0]; +rx(-pi/2) out[0]; +rzz(pi/2) a[0], out[0]; +rx(-pi) a[0]; +rz(-pi/2) a[0]; +ry(-pi/2) out[0]; +rz(3*pi/4) out[0]; +rzz(pi/2) b[1], out[0]; +rz(pi/2) b[1]; +rzz(pi/2) a[0], b[1]; +rzz(pi/2) a[0], out[1]; +rz(-pi/2) a[0]; +rz(-pi/2) b[1]; +rx(-pi) b[1]; +rz(3*pi/4) out[0]; +ry(pi/2) out[0]; +rz(-2.1072299372197243) out[0]; +rz(-2.6051590431649654) out[1]; +ry(pi/4) out[1]; +rz(-2.5551204732437065) out[1]; +rzz(pi/2) b[1], out[1]; +rz(-pi/2) b[1]; +rz(-2.157268507140982) out[1]; +ry(pi/4) out[1]; +rz(-2.1072299372197243) out[1]; +rzz(pi/2) a[0], out[1]; +rz(-2.6051590431649654) out[1]; +ry(pi/4) out[1]; +rz(-2.5551204732437065) out[1]; +rzz(pi/2) b[1], out[1]; +ry(-pi/2) b[1]; +rzz(pi/2) a[0], b[1]; +ry(pi) a[0]; +rz(-pi) b[1]; +ry(-pi/4) b[1]; +rzz(pi/2) a[0], b[1]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +rzz(pi/2) a[0], out[2]; +rz(-pi/2) a[0]; +ry(pi/2) b[1]; +rz(pi/4) b[1]; +rz(-2.1572685071409827) out[1]; +ry(pi/4) out[1]; +rz(0.3325895879972256) out[1]; +rz(-1.903385914792119) out[2]; +ry(7*pi/8) out[2]; +rz(-1.6971534443990168) out[2]; +rzz(pi/2) b[1], out[2]; +rz(pi/2) b[1]; +rz(0.12635711760412294) out[2]; +ry(7*pi/8) out[2]; +rz(-2.8090030655925666) out[2]; +rzz(pi/2) a[0], out[2]; +rz(-1.903385914792119) out[2]; +ry(7*pi/8) out[2]; +rz(-1.6971534443990168) out[2]; +rzz(pi/2) b[1], out[2]; +ry(-pi/2) b[1]; +rz(-pi) b[1]; +rzz(pi/2) a[0], b[1]; +ry(pi) a[0]; +rz(-pi) b[1]; +ry(-pi/8) b[1]; +rzz(pi/2) a[0], b[1]; +rz(-pi/2) a[0]; +rzz(pi/2) a[0], out[3]; +rz(pi/2) a[0]; +ry(-pi/2) b[1]; +rz(5*pi/8) b[1]; +rz(-3.0152355359856715) out[2]; +ry(pi/8) out[2]; +rz(1.294861248499081) out[2]; +rz(0.2759350782958139) out[3]; +ry(15*pi/16) out[3]; +rz(-0.32190695107235934) out[3]; +rzz(pi/2) b[1], out[3]; +rz(-pi/2) b[1]; +rz(1.892703277867259) out[3]; +ry(15*pi/16) out[3]; +rz(-1.846731405090709) out[3]; +rzz(pi/2) a[0], out[3]; +rx(pi) a[0]; +rz(0.2759350782958139) out[3]; +ry(15*pi/16) out[3]; +rz(-0.32190695107235934) out[3]; +rzz(pi/2) b[1], out[3]; +ry(-pi/2) b[1]; +rzz(pi/2) a[0], b[1]; +ry(pi) a[0]; +rz(-pi) b[1]; +ry(-pi/16) b[1]; +rzz(pi/2) a[0], b[1]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +rzz(pi/2) a[0], out[4]; +rz(-pi/2) a[0]; +ry(-pi/2) b[1]; +rz(9*pi/16) b[1]; +rz(-1.2488893757225379) out[3]; +ry(pi/16) out[3]; +rz(0.9832827023934838) out[3]; +rz(0.5875136244014145) out[4]; +ry(pi/32) out[4]; +rz(1.230855619167917) out[4]; +rzz(pi/2) b[1], out[4]; +rz(-pi/2) b[1]; +rz(0.3399407076269809) out[4]; +ry(pi/32) out[4]; +rz(0.9832827023934785) out[4]; +rzz(pi/2) a[0], out[4]; +rz(0.5875136244014145) out[4]; +ry(pi/32) out[4]; +rz(1.230855619167917) out[4]; +rzz(pi/2) b[1], out[4]; +ry(-pi/2) b[1]; +rzz(pi/2) a[0], b[1]; +ry(pi) a[0]; +rz(-pi) b[1]; +ry(-pi/32) b[1]; +rzz(pi/2) a[0], b[1]; +rz(-pi/2) a[0]; +rzz(pi/2) a[0], out[5]; +rz(pi/2) a[0]; +ry(pi/2) b[1]; +rz(1.472621556370215) b[1]; +rz(-2.801651945962806) out[4]; +ry(3.043417883165112) out[4]; +rz(-0.8226060790210696) out[4]; +rz(2.3934024058159675) out[5]; +ry(pi/64) out[5]; +rz(2.3982406999461094) out[5]; +rzz(pi/2) b[1], out[5]; +rz(pi/2) b[1]; +rz(2.3141482804385793) out[5]; +ry(pi/64) out[5]; +rz(2.3189865745687213) out[5]; +rzz(pi/2) a[0], out[5]; +rx(pi) a[0]; +rz(2.3934024058159675) out[5]; +ry(pi/64) out[5]; +rz(2.3982406999461094) out[5]; +rzz(pi/2) b[1], out[5]; +ry(-pi/2) b[1]; +rz(-pi) b[1]; +rzz(pi/2) a[0], b[1]; +ry(pi) a[0]; +rz(-pi) b[1]; +ry(-pi/64) b[1]; +rzz(pi/2) a[0], b[1]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +rzz(pi/2) a[0], out[6]; +rz(-pi/2) a[0]; +ry(-pi/2) b[1]; +rz(1.619883712007237) b[1]; +rz(-0.8274443731512049) out[5]; +ry(3.0925052683774528) out[5]; +rz(2.3696099671542044) out[5]; +rz(2.342779013230494) out[6]; +ry(pi/128) out[6]; +rz(2.4852630457765574) out[6]; +rzz(pi/2) b[1], out[6]; +rz(-pi/2) b[1]; +rz(-0.9144667189816644) out[6]; +ry(pi/128) out[6]; +rz(-0.7719826864355919) out[6]; +rzz(pi/2) a[0], out[6]; +rz(2.342779013230494) out[6]; +ry(pi/128) out[6]; +rz(2.4852630457765574) out[6]; +rzz(pi/2) b[1], out[6]; +ry(-pi/2) b[1]; +rzz(pi/2) a[0], b[1]; +ry(pi) a[0]; +rz(-pi) b[1]; +ry(-pi/128) b[1]; +rzz(pi/2) a[0], b[1]; +rz(-pi/2) a[0]; +ry(-pi/2) b[1]; +rz(1.5953400194010676) b[1]; +rz(2.2271259346081145) out[6]; +ry(3.1170489609836234) out[6]; +rz(2.0614966236345236) out[6]; +rz(2.2271259346081145) out[7]; +ry(3.1170489609836234) out[7]; +rz(2.0614966236345236) out[7]; +rzz(pi/2) a[0], out[7]; +rz(pi/2) a[0]; +rz(2.6508923567501874) out[7]; +ry(3.1293208072867085) out[7]; +rz(3.107697059996571) out[7]; +rzz(pi/2) b[1], out[7]; +rz(-pi/2) b[1]; +rz(-1.536900733201678) out[7]; +ry(3.129320807286707) out[7]; +rz(2.0614966236345014) out[7]; +rzz(pi/2) a[0], out[7]; +rx(pi) a[0]; +rz(2.6508923567501874) out[7]; +ry(3.1293208072867085) out[7]; +rz(3.107697059996571) out[7]; +rzz(pi/2) b[1], out[7]; +ry(-pi/2) b[1]; +rzz(pi/2) a[0], b[1]; +ry(pi) a[0]; +rz(-pi) b[1]; +ry(-pi/256) b[1]; +rzz(pi/2) a[0], b[1]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +rzz(pi/2) a[0], out[0]; +rz(-pi/2) a[0]; +ry(-pi/2) b[1]; +rz(1.5830681730979812) b[1]; +rz(-2.6051590431649654) out[0]; +ry(pi/4) out[0]; +rz(-2.5551204732437065) out[0]; +rzz(pi/2) b[0], out[0]; +rz(-pi/2) b[0]; +rx(-pi) b[0]; +rz(-2.157268507140982) out[0]; +ry(pi/4) out[0]; +rz(-2.1072299372197243) out[0]; +rzz(pi/2) a[0], out[0]; +rz(-1.0343627163700688) out[0]; +rx(3*pi/4) out[0]; +rzz(pi/2) b[0], out[0]; +ry(-pi/2) b[0]; +rz(-pi) b[0]; +rzz(pi/2) a[0], b[0]; +ry(pi) a[0]; +rz(-pi) b[0]; +ry(-pi/4) b[0]; +rzz(pi/2) a[0], b[0]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +rzz(pi/2) a[0], out[1]; +rz(-pi/2) a[0]; +ry(pi/2) b[0]; +rz(pi/4) b[0]; +ry(-pi/4) out[0]; +rz(-pi) out[0]; +rz(-1.903385914792119) out[1]; +ry(7*pi/8) out[1]; +rz(-1.6971534443990168) out[1]; +rzz(pi/2) b[0], out[1]; +rz(pi/2) b[0]; +rz(0.12635711760412294) out[1]; +ry(7*pi/8) out[1]; +rz(-2.8090030655925666) out[1]; +rzz(pi/2) a[0], out[1]; +rz(-1.903385914792119) out[1]; +ry(7*pi/8) out[1]; +rz(-1.6971534443990168) out[1]; +rzz(pi/2) b[0], out[1]; +ry(-pi/2) b[0]; +rz(-pi) b[0]; +rzz(pi/2) a[0], b[0]; +ry(pi) a[0]; +rz(-pi) b[0]; +ry(-pi/8) b[0]; +rzz(pi/2) a[0], b[0]; +rz(-pi/2) a[0]; +rzz(pi/2) a[0], out[2]; +rz(pi/2) a[0]; +ry(-pi/2) b[0]; +rz(5*pi/8) b[0]; +rz(-1.4444392091907758) out[1]; +rx(3*pi/8) out[1]; +rzz(pi/2) out[0], out[1]; +ry(pi) out[0]; +rz(-pi) out[1]; +ry(-pi/4) out[1]; +rzz(pi/2) out[0], out[1]; +rz(-pi/4) out[0]; +rz(pi/2) out[1]; +ry(pi/4) out[1]; +rz(0.2759350782958139) out[2]; +ry(15*pi/16) out[2]; +rz(-0.32190695107235934) out[2]; +rzz(pi/2) b[0], out[2]; +rz(-pi/2) b[0]; +rz(1.892703277867259) out[2]; +ry(15*pi/16) out[2]; +rz(-1.846731405090709) out[2]; +rzz(pi/2) a[0], out[2]; +rx(pi) a[0]; +rz(0.2759350782958139) out[2]; +ry(15*pi/16) out[2]; +rz(-0.32190695107235934) out[2]; +rzz(pi/2) b[0], out[2]; +ry(-pi/2) b[0]; +rzz(pi/2) a[0], b[0]; +ry(pi) a[0]; +rz(-pi) b[0]; +ry(-pi/16) b[0]; +rzz(pi/2) a[0], b[0]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +rzz(pi/2) a[0], out[3]; +rz(-pi/2) a[0]; +ry(-pi/2) b[0]; +rz(9*pi/16) b[0]; +rz(-2.8196857025174333) out[2]; +rx(-7*pi/16) out[2]; +rzz(pi/2) out[0], out[2]; +ry(pi) out[0]; +rz(-pi) out[2]; +ry(-pi/8) out[2]; +rzz(pi/2) out[0], out[2]; +rz(pi/8) out[0]; +ry(pi) out[0]; +rx(-7*pi/8) out[2]; +rzz(pi/2) out[1], out[2]; +ry(pi) out[1]; +rz(-pi) out[2]; +ry(-pi/4) out[2]; +rzz(pi/2) out[1], out[2]; +rz(-pi/4) out[1]; +rz(pi/2) out[2]; +ry(pi/4) out[2]; +rz(0.5875136244014145) out[3]; +ry(pi/32) out[3]; +rz(1.230855619167917) out[3]; +rzz(pi/2) b[0], out[3]; +rz(-pi/2) b[0]; +rz(0.3399407076269809) out[3]; +ry(pi/32) out[3]; +rz(0.9832827023934785) out[3]; +rzz(pi/2) a[0], out[3]; +rz(0.5875136244014145) out[3]; +ry(pi/32) out[3]; +rz(1.230855619167917) out[3]; +rzz(pi/2) b[0], out[3]; +ry(-pi/2) b[0]; +rzz(pi/2) a[0], b[0]; +ry(pi) a[0]; +rz(-pi) b[0]; +ry(-pi/32) b[0]; +rzz(pi/2) a[0], b[0]; +rz(-pi/2) a[0]; +rzz(pi/2) a[0], out[4]; +rz(pi/2) a[0]; +ry(pi/2) b[0]; +rz(1.472621556370215) b[0]; +rz(-1.2308556191679187) out[3]; +rx(-1.4726215563702154) out[3]; +rzz(pi/2) out[0], out[3]; +ry(pi) out[0]; +rz(-pi) out[3]; +ry(-pi/16) out[3]; +rzz(pi/2) out[0], out[3]; +rz(pi/16) out[0]; +rz(-pi) out[3]; +rx(-pi/16) out[3]; +rzz(pi/2) out[1], out[3]; +ry(pi) out[1]; +rz(-pi) out[3]; +ry(-pi/8) out[3]; +rzz(pi/2) out[1], out[3]; +rz(pi/8) out[1]; +ry(pi) out[1]; +rx(-7*pi/8) out[3]; +rzz(pi/2) out[2], out[3]; +ry(pi) out[2]; +rz(-pi) out[3]; +ry(-pi/4) out[3]; +rzz(pi/2) out[2], out[3]; +rz(-pi/4) out[2]; +rz(pi/2) out[3]; +ry(pi/4) out[3]; +rz(2.3934024058159675) out[4]; +ry(pi/64) out[4]; +rz(2.3982406999461094) out[4]; +rzz(pi/2) b[0], out[4]; +rz(pi/2) b[0]; +rz(2.3141482804385793) out[4]; +ry(pi/64) out[4]; +rz(2.3189865745687213) out[4]; +rzz(pi/2) a[0], out[4]; +rx(pi) a[0]; +rz(2.3934024058159675) out[4]; +ry(pi/64) out[4]; +rz(2.3982406999461094) out[4]; +rzz(pi/2) b[0], out[4]; +ry(-pi/2) b[0]; +rz(-pi) b[0]; +rzz(pi/2) a[0], b[0]; +ry(pi) a[0]; +rz(-pi) b[0]; +ry(-pi/64) b[0]; +rzz(pi/2) a[0], b[0]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +rzz(pi/2) a[0], out[5]; +rz(-pi/2) a[0]; +ry(-pi/2) b[0]; +rz(1.619883712007237) b[0]; +rz(-2.3982406999461032) out[4]; +rx(1.5217089415825562) out[4]; +rzz(pi/2) out[0], out[4]; +ry(pi) out[0]; +rz(-pi) out[4]; +ry(-pi/32) out[4]; +rzz(pi/2) out[0], out[4]; +rz(-pi/32) out[0]; +ry(pi) out[0]; +rx(3.0434178831651124) out[4]; +rzz(pi/2) out[1], out[4]; +ry(pi) out[1]; +rz(-pi) out[4]; +ry(-pi/16) out[4]; +rzz(pi/2) out[1], out[4]; +rz(pi/16) out[1]; +rz(-pi) out[4]; +rx(-pi/16) out[4]; +rzz(pi/2) out[2], out[4]; +ry(pi) out[2]; +rz(-pi) out[4]; +ry(-pi/8) out[4]; +rzz(pi/2) out[2], out[4]; +rz(pi/8) out[2]; +ry(pi) out[2]; +rx(-7*pi/8) out[4]; +rzz(pi/2) out[3], out[4]; +ry(pi) out[3]; +rz(-pi) out[4]; +ry(-pi/4) out[4]; +rzz(pi/2) out[3], out[4]; +rz(-pi/4) out[3]; +rz(pi/2) out[4]; +ry(pi/4) out[4]; +rz(2.342779013230494) out[5]; +ry(pi/128) out[5]; +rz(2.4852630457765574) out[5]; +rzz(pi/2) b[0], out[5]; +rz(-pi/2) b[0]; +rz(-0.9144667189816644) out[5]; +ry(pi/128) out[5]; +rz(-0.7719826864355919) out[5]; +rzz(pi/2) a[0], out[5]; +rz(2.342779013230494) out[5]; +ry(pi/128) out[5]; +rz(2.4852630457765574) out[5]; +rzz(pi/2) b[0], out[5]; +ry(-pi/2) b[0]; +rzz(pi/2) a[0], b[0]; +ry(pi) a[0]; +rz(-pi) b[0]; +ry(-pi/128) b[0]; +rzz(pi/2) a[0], b[0]; +rz(-pi/2) a[0]; +rzz(pi/2) a[0], out[6]; +rz(pi/2) a[0]; +ry(-pi/2) b[0]; +rz(1.5953400194010676) b[0]; +rz(0.6563296078132304) out[5]; +rx(-1.5953400194010665) out[5]; +rzz(pi/2) out[0], out[5]; +ry(pi) out[0]; +rz(-pi) out[5]; +ry(-pi/64) out[5]; +rzz(pi/2) out[0], out[5]; +rz(-pi/64) out[0]; +rx(-pi/64) out[5]; +rz(-pi) out[5]; +rzz(pi/2) out[1], out[5]; +ry(pi) out[1]; +rz(-pi) out[5]; +ry(-pi/32) out[5]; +rzz(pi/2) out[1], out[5]; +rz(-pi/32) out[1]; +ry(pi) out[1]; +rx(3.0434178831651124) out[5]; +rzz(pi/2) out[2], out[5]; +ry(pi) out[2]; +rz(-pi) out[5]; +ry(-pi/16) out[5]; +rzz(pi/2) out[2], out[5]; +rz(pi/16) out[2]; +rz(-pi) out[5]; +rx(-pi/16) out[5]; +rzz(pi/2) out[3], out[5]; +ry(pi) out[3]; +rz(-pi) out[5]; +ry(-pi/8) out[5]; +rzz(pi/2) out[3], out[5]; +rz(pi/8) out[3]; +ry(pi) out[3]; +rx(-7*pi/8) out[5]; +rzz(pi/2) out[4], out[5]; +ry(pi) out[4]; +rz(-pi) out[5]; +ry(-pi/4) out[5]; +rzz(pi/2) out[4], out[5]; +rz(-pi/4) out[4]; +rz(pi/2) out[5]; +ry(pi/4) out[5]; +rz(2.6508923567501874) out[6]; +ry(3.1293208072867085) out[6]; +rz(3.107697059996571) out[6]; +rzz(pi/2) b[0], out[6]; +rz(-pi/2) b[0]; +rz(-1.536900733201678) out[6]; +ry(3.129320807286707) out[6]; +rz(2.0614966236345014) out[6]; +rzz(pi/2) a[0], out[6]; +rx(pi) a[0]; +rz(2.6508923567501874) out[6]; +ry(3.1293208072867085) out[6]; +rz(3.107697059996571) out[6]; +rzz(pi/2) b[0], out[6]; +ry(-pi/2) b[0]; +rzz(pi/2) a[0], b[0]; +ry(pi) a[0]; +rz(-pi) b[0]; +ry(-pi/256) b[0]; +rzz(pi/2) a[0], b[0]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +ry(-pi/2) b[0]; +rz(1.5830681730979812) b[0]; +rz(0.033895593593253004) out[6]; +rx(-1.5585244804918106) out[6]; +rzz(pi/2) out[0], out[6]; +ry(pi) out[0]; +rz(-pi) out[6]; +ry(-pi/128) out[6]; +rzz(pi/2) out[0], out[6]; +rz(pi/128) out[0]; +ry(pi) out[0]; +rx(-3.117048960983623) out[6]; +rzz(pi/2) out[1], out[6]; +ry(pi) out[1]; +rz(-pi) out[6]; +ry(-pi/64) out[6]; +rzz(pi/2) out[1], out[6]; +rz(-pi/64) out[1]; +rx(-pi/64) out[6]; +rz(-pi) out[6]; +rzz(pi/2) out[2], out[6]; +ry(pi) out[2]; +rz(-pi) out[6]; +ry(-pi/32) out[6]; +rzz(pi/2) out[2], out[6]; +rz(-pi/32) out[2]; +ry(pi) out[2]; +rx(3.0434178831651124) out[6]; +rzz(pi/2) out[3], out[6]; +ry(pi) out[3]; +rz(-pi) out[6]; +ry(-pi/16) out[6]; +rzz(pi/2) out[3], out[6]; +rz(pi/16) out[3]; +rz(-pi) out[6]; +rx(-pi/16) out[6]; +rzz(pi/2) out[4], out[6]; +ry(pi) out[4]; +rz(-pi) out[6]; +ry(-pi/8) out[6]; +rzz(pi/2) out[4], out[6]; +rz(pi/8) out[4]; +ry(pi) out[4]; +rx(-7*pi/8) out[6]; +rzz(pi/2) out[5], out[6]; +ry(pi) out[5]; +rz(-pi) out[6]; +ry(-pi/4) out[6]; +rzz(pi/2) out[5], out[6]; +rz(-pi/4) out[5]; +rz(pi/2) out[6]; +ry(pi/4) out[6]; +rz(1.60469192038813) out[7]; +ry(pi/256) out[7]; +rz(0.0246709403348655) out[7]; +rzz(pi/2) a[0], out[7]; +rz(-pi/2) a[0]; +rz(1.5461253864600328) out[7]; +ry(pi/512) out[7]; +rz(-3.032529328254043) out[7]; +rzz(pi/2) b[0], out[7]; +rz(-pi/2) b[0]; +rz(-1.6798596521305806) out[7]; +ry(pi/512) out[7]; +rz(0.02467094033479711) out[7]; +rzz(pi/2) a[0], out[7]; +rz(1.5461253864600328) out[7]; +ry(pi/512) out[7]; +rz(-3.032529328254043) out[7]; +rzz(pi/2) b[0], out[7]; +ry(-pi/2) b[0]; +rzz(pi/2) a[0], b[0]; +ry(pi) a[0]; +rz(-pi) b[0]; +ry(-pi/512) b[0]; +rzz(pi/2) a[0], b[0]; +rz(-pi/2) a[0]; +rx(-pi) a[0]; +ry(-pi/2) b[0]; +rz(1.5769322499464389) b[0]; +rz(3.0325293282540606) out[7]; +rx(-1.564660403643354) out[7]; +rzz(pi/2) out[0], out[7]; +ry(pi) out[0]; +rz(-pi) out[7]; +ry(-pi/256) out[7]; +rzz(pi/2) out[0], out[7]; +rz(-1.5585244804918115) out[0]; +rx(-pi) out[0]; +rz(-pi) out[7]; +rx(-pi/256) out[7]; +rzz(pi/2) out[1], out[7]; +ry(pi) out[1]; +rz(-pi) out[7]; +ry(-pi/128) out[7]; +rzz(pi/2) out[1], out[7]; +rz(-1.5462526341887266) out[1]; +rx(-pi) out[1]; +rx(-3.117048960983623) out[7]; +rzz(pi/2) out[2], out[7]; +ry(pi) out[2]; +rz(-pi) out[7]; +ry(-pi/64) out[7]; +rzz(pi/2) out[2], out[7]; +rz(-1.6198837120072376) out[2]; +rx(-pi/64) out[7]; +rz(-pi) out[7]; +rzz(pi/2) out[3], out[7]; +ry(pi) out[3]; +rz(-pi) out[7]; +ry(-pi/32) out[7]; +rzz(pi/2) out[3], out[7]; +rz(-1.668971097219578) out[3]; +rx(3.0434178831651124) out[7]; +rzz(pi/2) out[4], out[7]; +ry(pi) out[4]; +rz(-pi) out[7]; +ry(-pi/16) out[7]; +rzz(pi/2) out[4], out[7]; +rz(-7*pi/16) out[4]; +rx(-pi) out[4]; +rz(-pi) out[7]; +rx(-pi/16) out[7]; +rzz(pi/2) out[5], out[7]; +ry(pi) out[5]; +rz(-pi) out[7]; +ry(-pi/8) out[7]; +rzz(pi/2) out[5], out[7]; +rz(-3*pi/8) out[5]; +rx(-pi) out[5]; +rx(-7*pi/8) out[7]; +rzz(pi/2) out[6], out[7]; +ry(pi) out[6]; +rz(-pi) out[7]; +ry(-pi/4) out[7]; +rzz(pi/2) out[6], out[7]; +rz(-3*pi/4) out[6]; +rx(-3*pi/4) out[7]; diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm/src/ast_builder.rs similarity index 81% rename from compiler/qsc_qasm3/src/ast_builder.rs rename to compiler/qsc_qasm/src/ast_builder.rs index 4269334548..f2885456a0 100644 --- a/compiler/qsc_qasm3/src/ast_builder.rs +++ b/compiler/qsc_qasm/src/ast_builder.rs @@ -6,14 +6,16 @@ use std::rc::Rc; use num_bigint::BigInt; use qsc_ast::ast::{ - self, Attr, Block, CallableBody, CallableDecl, CallableKind, Expr, ExprKind, Ident, Item, Lit, - Mutability, NodeId, Pat, PatKind, Path, PathKind, QubitInit, QubitInitKind, QubitSource, Stmt, - StmtKind, TopLevelNode, Ty, TyKind, + self, Attr, Block, CallableBody, CallableDecl, CallableKind, Expr, ExprKind, FieldAssign, + FunctorExpr, FunctorExprKind, Ident, ImportOrExportDecl, ImportOrExportItem, Item, ItemKind, + Lit, Mutability, NodeId, Pat, PatKind, Path, PathKind, QubitInit, QubitInitKind, QubitSource, + Stmt, StmtKind, TopLevelNode, Ty, TyKind, }; use qsc_data_structures::span::Span; use crate::{ - runtime::RuntimeFunctions, + parser::ast::{list_from_iter, List}, + stdlib::angle::Angle, types::{ArrayDimensions, Complex}, }; @@ -246,6 +248,8 @@ pub(crate) fn build_expr_array_expr(values: Vec, span: Span) } } +// This will be used to compile arrays in the near future. +#[allow(dead_code)] pub(crate) fn build_default_result_array_expr(len: usize, span: Span) -> Expr { let exprs: Vec<_> = (0..len) .map(|_| Box::new(build_lit_result_expr(ast::Result::Zero, Span::default()))) @@ -281,6 +285,55 @@ pub(crate) fn build_lit_int_expr(value: i64, span: Span) -> Expr { } } +fn build_ident(name: &str) -> Ident { + Ident { + name: Rc::from(name), + ..Default::default() + } +} + +pub(crate) fn build_lit_angle_expr(angle: Angle, span: Span) -> Expr { + let alloc_ident = build_ident("__Angle__"); + let path_kind = PathKind::Ok(Box::new(Path { + segments: None, + name: Box::new(alloc_ident), + id: NodeId::default(), + span: Span::default(), + })); + let value_expr = Box::new(Expr { + #[allow(clippy::cast_possible_wrap)] + kind: Box::new(ExprKind::Lit(Box::new(Lit::Int(angle.value as i64)))), + ..Default::default() + }); + let size_expr = Box::new(Expr { + kind: Box::new(ExprKind::Lit(Box::new(Lit::Int(i64::from(angle.size))))), + ..Default::default() + }); + + let fields = list_from_iter([ + FieldAssign { + span, + field: Box::new(build_ident("Value")), + value: value_expr, + ..Default::default() + }, + FieldAssign { + span, + field: Box::new(build_ident("Size")), + value: size_expr, + ..Default::default() + }, + ]); + + let kind = Box::new(ExprKind::Struct(path_kind, None, fields)); + + Expr { + id: NodeId::default(), + span, + kind, + } +} + pub(crate) fn build_lit_complex_expr(value: Complex, span: Span) -> Expr { let real = build_lit_double_expr(value.real, Span::default()); let img = build_lit_double_expr(value.imaginary, Span::default()); @@ -312,35 +365,6 @@ pub(crate) fn build_binary_expr( } } -pub(crate) fn is_complex_binop_supported(op: qsc_ast::ast::BinOp) -> bool { - matches!( - op, - ast::BinOp::Add | ast::BinOp::Sub | ast::BinOp::Mul | ast::BinOp::Div | ast::BinOp::Exp - ) -} - -pub(crate) fn build_complex_binary_expr( - is_assignment: bool, - qsop: ast::BinOp, - lhs: ast::Expr, - rhs: ast::Expr, - span: Span, -) -> ast::Expr { - let name = match qsop { - ast::BinOp::Add => "PlusC", - ast::BinOp::Sub => "MinusC", - ast::BinOp::Mul => "TimesC", - ast::BinOp::Div => "DividedByC", - ast::BinOp::Exp => "PowC", - _ => unreachable!("Unsupported complex binary operation"), - }; - - if is_assignment { - unreachable!("Unsupported complex binary operation"); - } - build_math_call_from_exprs(name, vec![lhs, rhs], span) -} - pub(crate) fn build_math_call_from_exprs(name: &str, exprs: Vec, span: Span) -> Expr { let alloc_ident = Ident { name: Rc::from(name), @@ -389,7 +413,7 @@ pub(crate) fn build_path_ident_expr>( }; let path = ast::Path { id: NodeId::default(), - span: Span::default(), + span: name_span, segments: None, name: Box::new(ident), }; @@ -616,38 +640,12 @@ pub(crate) fn build_if_expr_then_block(cond: Expr, then_block: Block, span: Span } } -pub(crate) fn build_cast_call_two_params( - function: RuntimeFunctions, - fst: ast::Expr, - snd: ast::Expr, - name_span: Span, - operand_span: Span, -) -> ast::Expr { - let name = match function { - RuntimeFunctions::IntAsResultArrayBE => "__IntAsResultArrayBE__", - _ => panic!("Unsupported cast function"), - }; - - build_global_call_with_two_params(name, fst, snd, name_span, operand_span) -} - -pub(crate) fn build_cast_call( - function: RuntimeFunctions, +pub(crate) fn build_cast_call_by_name( + name: &str, expr: ast::Expr, name_span: Span, operand_span: Span, ) -> ast::Expr { - let name = match function { - RuntimeFunctions::BoolAsResult => "__BoolAsResult__", - RuntimeFunctions::BoolAsInt => "__BoolAsInt__", - RuntimeFunctions::BoolAsBigInt => "__BoolAsBigInt__", - RuntimeFunctions::BoolAsDouble => "__BoolAsDouble__", - RuntimeFunctions::ResultAsBool => "__ResultAsBool__", - RuntimeFunctions::ResultAsInt => "__ResultAsInt__", - RuntimeFunctions::ResultAsBigInt => "__ResultAsBigInt__", - RuntimeFunctions::ResultArrayAsIntBE => "__ResultArrayAsIntBE__", - _ => panic!("Unsupported cast function"), - }; build_global_call_with_one_param(name, expr, name_span, operand_span) } @@ -677,6 +675,7 @@ pub(crate) fn build_global_call_with_one_param>( name_span: Span, operand_span: Span, ) -> ast::Expr { + let expr_span = expr.span; let ident = ast::Ident { id: NodeId::default(), span: name_span, @@ -687,7 +686,7 @@ pub(crate) fn build_global_call_with_one_param>( span: name_span, kind: Box::new(ast::ExprKind::Path(PathKind::Ok(Box::new(ast::Path { id: NodeId::default(), - span: Span::default(), + span: name_span, segments: None, name: Box::new(ident), })))), @@ -703,6 +702,7 @@ pub(crate) fn build_global_call_with_one_param>( let call_kind = ast::ExprKind::Call(Box::new(callee_expr), Box::new(param_expr)); ast::Expr { kind: Box::new(call_kind), + span: expr_span, ..Default::default() } } @@ -724,7 +724,7 @@ pub(crate) fn build_global_call_with_two_params>( span: name_span, kind: Box::new(ast::ExprKind::Path(PathKind::Ok(Box::new(ast::Path { id: NodeId::default(), - span: Span::default(), + span: name_span, segments: None, name: Box::new(ident), })))), @@ -740,6 +740,7 @@ pub(crate) fn build_global_call_with_two_params>( let call_kind = ast::ExprKind::Call(Box::new(callee_expr), Box::new(param_expr)); ast::Expr { kind: Box::new(call_kind), + span: name_span, ..Default::default() } } @@ -774,10 +775,15 @@ pub fn build_gate_call_param_expr(args: Vec, remaining: usize) -> Expr { } pub(crate) fn build_math_call_no_params(name: &str, span: Span) -> Expr { - build_call_no_params(name, &["Microsoft", "Quantum", "Math"], span) + build_call_no_params(name, &["Microsoft", "Quantum", "Math"], span, span) } -pub(crate) fn build_call_no_params(name: &str, idents: &[&str], span: Span) -> Expr { +pub(crate) fn build_call_no_params( + name: &str, + idents: &[&str], + fn_call_span: Span, + fn_name_span: Span, +) -> Expr { let segments = build_idents(idents); let fn_name = Ident { name: Rc::from(name), @@ -788,8 +794,9 @@ pub(crate) fn build_call_no_params(name: &str, idents: &[&str], span: Span) -> E segments, name: Box::new(fn_name), id: NodeId::default(), - span: Span::default(), + span: fn_name_span, })))), + span: fn_name_span, ..Default::default() }; let call = ExprKind::Call( @@ -799,7 +806,7 @@ pub(crate) fn build_call_no_params(name: &str, idents: &[&str], span: Span) -> E Expr { id: NodeId::default(), - span, + span: fn_call_span, kind: Box::new(call), } } @@ -810,7 +817,7 @@ pub(crate) fn build_call_with_param( operand: Expr, name_span: Span, operand_span: Span, - stmt_span: Span, + call_span: Span, ) -> Expr { let segments = build_idents(idents); let fn_name = Ident { @@ -823,8 +830,9 @@ pub(crate) fn build_call_with_param( segments, name: Box::new(fn_name), id: NodeId::default(), - span: Span::default(), + span: name_span, })))), + span: name_span, ..Default::default() }; let call = ExprKind::Call( @@ -838,7 +846,39 @@ pub(crate) fn build_call_with_param( Expr { id: NodeId::default(), - span: stmt_span, + span: call_span, + kind: Box::new(call), + } +} + +pub(crate) fn build_call_with_params( + name: &str, + idents: &[&str], + operands: Vec, + name_span: Span, + call_span: Span, +) -> Expr { + let segments = build_idents(idents); + let fn_name = Ident { + name: Rc::from(name), + span: name_span, + ..Default::default() + }; + let path_expr = Expr { + kind: Box::new(ExprKind::Path(PathKind::Ok(Box::new(Path { + segments, + name: Box::new(fn_name), + id: NodeId::default(), + span: name_span, + })))), + span: name_span, + ..Default::default() + }; + let call = ExprKind::Call(Box::new(path_expr), Box::new(build_tuple_expr(operands))); + + Expr { + id: NodeId::default(), + span: call_span, kind: Box::new(call), } } @@ -859,19 +899,19 @@ pub(crate) fn build_stmt_semi_from_expr(expr: Expr) -> Stmt { } } -pub(crate) fn build_wrapped_block_expr(block: Block) -> Expr { - Expr { +pub(crate) fn build_stmt_semi_from_expr_with_span(expr: Expr, span: Span) -> Stmt { + Stmt { id: NodeId::default(), - span: block.span, - kind: Box::new(ast::ExprKind::Block(Box::new(block))), + span, + kind: Box::new(StmtKind::Semi(Box::new(expr))), } } -pub(crate) fn build_stmt_wrapped_block_expr(stmt: Stmt) -> Block { - Block { +pub(crate) fn build_wrapped_block_expr(block: Block) -> Expr { + Expr { id: NodeId::default(), - span: stmt.span, - stmts: Box::new([Box::new(stmt)]), + span: block.span, + kind: Box::new(ast::ExprKind::Block(Box::new(block))), } } @@ -887,6 +927,94 @@ pub(crate) fn build_expr_wrapped_block_expr(expr: Expr) -> Block { } } +pub(crate) fn build_qasm_import_decl() -> Vec { + build_qasm_import_items() + .into_iter() + .map(|item| Stmt { + kind: Box::new(StmtKind::Item(Box::new(item))), + span: Span::default(), + id: NodeId::default(), + }) + .collect() +} + +pub(crate) fn build_qasm_import_items() -> Vec { + vec![ + build_qasm_import_decl_angle(), + build_qasm_import_decl_convert(), + build_qasm_import_decl_intrinsic(), + ] +} + +pub(crate) fn build_qasm_import_decl_angle() -> Item { + let path_kind = Path { + segments: Some(Box::new([build_ident("QasmStd")])), + name: Box::new(build_ident("Angle")), + id: NodeId::default(), + span: Span::default(), + }; + let items = vec![ImportOrExportItem { + span: Span::default(), + path: PathKind::Ok(Box::new(path_kind)), + alias: None, + is_glob: true, + }]; + let decl = ImportOrExportDecl::new(Span::default(), items.into_boxed_slice(), false); + Item { + id: NodeId::default(), + span: Span::default(), + kind: Box::new(ItemKind::ImportOrExport(decl)), + doc: "".into(), + attrs: Box::new([]), + } +} + +pub(crate) fn build_qasm_import_decl_convert() -> Item { + let path_kind = Path { + segments: Some(Box::new([build_ident("QasmStd")])), + name: Box::new(build_ident("Convert")), + id: NodeId::default(), + span: Span::default(), + }; + let items = vec![ImportOrExportItem { + span: Span::default(), + path: PathKind::Ok(Box::new(path_kind)), + alias: None, + is_glob: true, + }]; + let decl = ImportOrExportDecl::new(Span::default(), items.into_boxed_slice(), false); + Item { + id: NodeId::default(), + span: Span::default(), + kind: Box::new(ItemKind::ImportOrExport(decl)), + doc: "".into(), + attrs: Box::new([]), + } +} + +pub(crate) fn build_qasm_import_decl_intrinsic() -> Item { + let path_kind = Path { + segments: Some(Box::new([build_ident("QasmStd")])), + name: Box::new(build_ident("Intrinsic")), + id: NodeId::default(), + span: Span::default(), + }; + let items = vec![ImportOrExportItem { + span: Span::default(), + path: PathKind::Ok(Box::new(path_kind)), + alias: None, + is_glob: true, + }]; + let decl = ImportOrExportDecl::new(Span::default(), items.into_boxed_slice(), false); + Item { + id: NodeId::default(), + span: Span::default(), + kind: Box::new(ItemKind::ImportOrExport(decl)), + doc: "".into(), + attrs: Box::new([]), + } +} + pub(crate) fn build_classical_decl( name: S, is_const: bool, @@ -931,6 +1059,7 @@ where let pat = Pat { kind: Box::new(PatKind::Bind(Box::new(ident), tydef)), + span: name_span, ..Default::default() }; let mutability = if is_const { @@ -998,10 +1127,10 @@ pub(crate) fn build_complex_ty_ident() -> Ty { } } -pub(crate) fn build_top_level_ns_with_item>( +pub(crate) fn build_top_level_ns_with_items>( whole_span: Span, ns: S, - entry: ast::Item, + items: Vec, ) -> TopLevelNode { TopLevelNode::Namespace(qsc_ast::ast::Namespace { id: NodeId::default(), @@ -1012,7 +1141,11 @@ pub(crate) fn build_top_level_ns_with_item>( id: NodeId::default(), }] .into(), - items: Box::new([Box::new(entry)]), + items: items + .into_iter() + .map(Box::new) + .collect::>() + .into_boxed_slice(), doc: "".into(), }) } @@ -1023,12 +1156,13 @@ pub(crate) fn build_operation_with_stmts>( output_ty: Ty, stmts: Vec, whole_span: Span, + add_entry_point: bool, ) -> ast::Item { let mut attrs = vec![]; // If there are no input parameters, add an attribute to mark this // as an entry point. We will get a Q# compilation error if we // attribute an operation with EntryPoint and it has input parameters. - if input_pats.is_empty() { + if input_pats.is_empty() && add_entry_point { attrs.push(Box::new(qsc_ast::ast::Attr { id: NodeId::default(), span: Span::default(), @@ -1115,6 +1249,7 @@ pub(crate) fn build_unary_op_expr(op: ast::UnOp, expr: ast::Expr, prefix_span: S pub(crate) fn map_qsharp_type_to_ast_ty(output_ty: &crate::types::Type) -> Ty { match output_ty { + crate::types::Type::Angle(_) => build_path_ident_ty("__Angle__"), crate::types::Type::Result(_) => build_path_ident_ty("Result"), crate::types::Type::Qubit => build_path_ident_ty("Qubit"), crate::types::Type::BigInt(_) => build_path_ident_ty("BigInt"), @@ -1151,6 +1286,7 @@ pub(crate) fn map_qsharp_type_to_ast_ty(output_ty: &crate::types::Type) -> Ty { let ty = map_qsharp_type_to_ast_ty(&crate::types::Type::Tuple(tys.clone())); wrap_array_ty_by_dims(dims, ty) } + crate::types::Type::Err => Ty::default(), } } @@ -1177,8 +1313,10 @@ fn wrap_ty_in_array(ty: Ty) -> Ty { } pub(crate) fn build_for_stmt( - loop_var: &crate::symbols::Symbol, - iter: crate::types::QasmTypedExpr, + loop_var_name: &str, + loop_var_span: Span, + loop_var_qsharp_ty: &crate::types::Type, + iter: Expr, body: Block, stmt_span: Span, ) -> Stmt { @@ -1188,15 +1326,15 @@ pub(crate) fn build_for_stmt( Box::new(Pat { kind: Box::new(PatKind::Bind( Box::new(Ident { - name: loop_var.name.clone().into(), - span: loop_var.span, + name: loop_var_name.into(), + span: loop_var_span, ..Default::default() }), - Some(Box::new(map_qsharp_type_to_ast_ty(&loop_var.qsharp_ty))), + Some(Box::new(map_qsharp_type_to_ast_ty(loop_var_qsharp_ty))), )), ..Default::default() }), - Box::new(iter.expr), + Box::new(iter), Box::new(body), )), span: stmt_span, @@ -1254,15 +1392,14 @@ pub(crate) fn build_end_stmt(span: Span) -> Stmt { }; let kind = ExprKind::Fail(Box::new(message)); - Stmt { - kind: Box::new(StmtKind::Expr(Box::new(Expr { - kind: Box::new(kind), - span, - ..Default::default() - }))), + + let expr = Expr { + kind: Box::new(kind), span, ..Default::default() - } + }; + + build_stmt_semi_from_expr_with_span(expr, span) } pub(crate) fn build_index_expr(expr: Expr, index_expr: Expr, span: Span) -> Expr { @@ -1275,24 +1412,12 @@ pub(crate) fn build_index_expr(expr: Expr, index_expr: Expr, span: Span) -> Expr } pub(crate) fn build_barrier_call(span: Span) -> Stmt { - let expr = build_call_no_params("__quantum__qis__barrier__body", &[], span); + let expr = build_call_no_params("__quantum__qis__barrier__body", &[], span, span); build_stmt_semi_from_expr(expr) } -pub(crate) fn build_attr(text: String, span: Span) -> Attr { - Attr { - id: NodeId::default(), - span, - name: Box::new(Ident { - name: Rc::from(text), - span, - ..Default::default() - }), - arg: Box::new(create_unit_expr(span)), - } -} - -pub(crate) fn build_gate_decl( +#[allow(clippy::too_many_arguments, clippy::too_many_lines)] +pub(crate) fn build_function_or_operation( name: String, cargs: Vec<(String, Ty, Pat)>, qargs: Vec<(String, Ty, Pat)>, @@ -1300,6 +1425,10 @@ pub(crate) fn build_gate_decl( name_span: Span, body_span: Span, gate_span: Span, + return_type: Ty, + kind: CallableKind, + functors: Option, + attrs: List, ) -> Stmt { let args = cargs .into_iter() @@ -1319,10 +1448,10 @@ pub(crate) fn build_gate_decl( .map(|x| x.span.hi) .unwrap_or_default(); - let input_pat_kind = if args.len() > 1 { - PatKind::Tuple(args.into_boxed_slice()) - } else { + let input_pat_kind = if args.len() == 1 { PatKind::Paren(args[0].clone()) + } else { + PatKind::Tuple(args.into_boxed_slice()) }; let input_pat = Pat { @@ -1330,28 +1459,31 @@ pub(crate) fn build_gate_decl( span: Span { lo, hi }, ..Default::default() }; + let body = CallableBody::Block(Box::new(body.unwrap_or_else(|| Block { id: NodeId::default(), span: body_span, stmts: Box::new([]), }))); + let decl = CallableDecl { id: NodeId::default(), span: name_span, - kind: CallableKind::Operation, + kind, name: Box::new(Ident { name: name.into(), ..Default::default() }), generics: Box::new([]), input: Box::new(input_pat), - output: Box::new(build_path_ident_ty("Unit")), - functors: None, + output: Box::new(return_type), + functors: functors.map(Box::new), body: Box::new(body), }; let item = Item { span: gate_span, kind: Box::new(ast::ItemKind::Callable(Box::new(decl))), + attrs, ..Default::default() }; @@ -1362,118 +1494,23 @@ pub(crate) fn build_gate_decl( } } -pub(crate) fn build_gate_decl_lambda>( - name: S, - cargs: Vec<(String, Ty, Pat)>, - qargs: Vec<(String, Ty, Pat)>, - body: Option, - name_span: Span, - body_span: Span, - gate_span: Span, -) -> Stmt { - let args = cargs - .into_iter() - .chain(qargs) - .map(|(name, ty, pat)| (name, ty, pat.span)) - .collect::>(); - - let lo = args - .iter() - .min_by_key(|(_, _, span)| span.lo) - .map(|(_, _, span)| span.lo) - .unwrap_or_default(); +pub(crate) fn build_adj_plus_ctl_functor() -> FunctorExpr { + let adj = Box::new(FunctorExpr { + kind: Box::new(FunctorExprKind::Lit(ast::Functor::Adj)), + id: Default::default(), + span: Default::default(), + }); - let hi = args - .iter() - .max_by_key(|(_, _, span)| span.hi) - .map(|(_, _, span)| span.hi) - .unwrap_or_default(); - - let name_args = args - .iter() - .map(|(name, _, span)| Pat { - kind: Box::new(PatKind::Bind( - Box::new(Ident { - span: *span, - name: Rc::from(name.as_ref()), - ..Default::default() - }), - None, - )), - ..Default::default() - }) - .map(Box::new) - .collect::>(); - let input_pat = if args.len() > 1 { - ast::Pat { - kind: Box::new(PatKind::Tuple(name_args.into_boxed_slice())), - span: Span { lo, hi }, - ..Default::default() - } - } else { - ast::Pat { - kind: Box::new(ast::PatKind::Paren(name_args[0].clone())), - span: Span { lo, hi }, - ..Default::default() - } - }; + let ctl = Box::new(FunctorExpr { + kind: Box::new(FunctorExprKind::Lit(ast::Functor::Ctl)), + id: Default::default(), + span: Default::default(), + }); - let block_expr = build_wrapped_block_expr(body.map_or_else( - || Block { - id: NodeId::default(), - span: body_span, - stmts: Box::new([]), - }, - |block| block, - )); - let lambda_expr = Expr { - id: NodeId::default(), - kind: Box::new(ExprKind::Lambda( - CallableKind::Operation, - Box::new(input_pat), - Box::new(block_expr), - )), - span: gate_span, - }; - let ty_args = args.iter().map(|(_, ty, _)| ty.clone()).collect::>(); - let input_ty = if args.len() > 1 { - ast::Ty { - kind: Box::new(ast::TyKind::Tuple(ty_args.into_boxed_slice())), - ..Default::default() - } - } else { - ast::Ty { - kind: Box::new(ast::TyKind::Paren(Box::new(ty_args[0].clone()))), - ..Default::default() - } - }; - let lambda_ty = ast::Ty { - kind: Box::new(ast::TyKind::Arrow( - CallableKind::Operation, - Box::new(input_ty), - Box::new(build_path_ident_ty("Unit")), - None, - )), - ..Default::default() - }; - Stmt { - span: gate_span, - kind: Box::new(StmtKind::Local( - Mutability::Immutable, - Box::new(Pat { - kind: Box::new(PatKind::Bind( - Box::new(Ident { - span: name_span, - name: Rc::from(name.as_ref()), - ..Default::default() - }), - Some(Box::new(lambda_ty)), - )), - ..Default::default() - }), - Box::new(lambda_expr), - )), - ..Default::default() + FunctorExpr { + kind: Box::new(FunctorExprKind::BinOp(ast::SetOp::Union, adj, ctl)), + id: Default::default(), + span: Default::default(), } } @@ -1491,3 +1528,48 @@ fn build_idents(idents: &[&str]) -> Option> { Some(idents.into()) } } + +pub(crate) fn build_attr(name: S, value: Option, span: Span) -> Attr +where + S: AsRef, +{ + let name = Box::new(Ident { + span, + name: name.as_ref().into(), + ..Default::default() + }); + + let arg = if let Some(value) = value { + Box::new(Expr { + span, + kind: Box::new(ExprKind::Paren(Box::new(Expr { + span, + kind: Box::new(ExprKind::Path(PathKind::Ok(Box::new(Path { + id: Default::default(), + span, + segments: None, + name: Box::new(Ident { + span, + name: value.as_ref().into(), + ..Default::default() + }), + })))), + id: Default::default(), + }))), + id: Default::default(), + }) + } else { + Box::new(Expr { + span, + kind: Box::new(ExprKind::Tuple(Box::default())), + id: Default::default(), + }) + }; + + Attr { + span, + name, + arg, + id: Default::default(), + } +} diff --git a/compiler/qsc_qasm/src/compiler.rs b/compiler/qsc_qasm/src/compiler.rs new file mode 100644 index 0000000000..02201406d6 --- /dev/null +++ b/compiler/qsc_qasm/src/compiler.rs @@ -0,0 +1,1948 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +pub mod error; + +use core::f64; +use std::{path::Path, rc::Rc}; + +use error::CompilerErrorKind; +use num_bigint::BigInt; +use qsc_data_structures::span::Span; +use qsc_frontend::{compile::SourceMap, error::WithSource}; + +use crate::{ + ast_builder::{ + build_adj_plus_ctl_functor, build_arg_pat, build_array_reverse_expr, + build_assignment_statement, build_attr, build_barrier_call, build_binary_expr, + build_call_no_params, build_call_with_param, build_call_with_params, + build_cast_call_by_name, build_classical_decl, build_complex_from_expr, + build_convert_call_expr, build_end_stmt, build_expr_array_expr, build_for_stmt, + build_function_or_operation, build_gate_call_param_expr, + build_gate_call_with_params_and_callee, build_global_call_with_two_params, + build_if_expr_then_block, build_if_expr_then_block_else_block, + build_if_expr_then_block_else_expr, build_if_expr_then_expr_else_expr, + build_implicit_return_stmt, build_index_expr, build_indexed_assignment_statement, + build_lit_angle_expr, build_lit_bigint_expr, build_lit_bool_expr, build_lit_complex_expr, + build_lit_double_expr, build_lit_int_expr, build_lit_result_array_expr_from_bitstring, + build_lit_result_expr, build_managed_qubit_alloc, build_math_call_from_exprs, + build_math_call_no_params, build_measure_call, build_operation_with_stmts, + build_path_ident_expr, build_path_ident_ty, build_qasm_import_decl, + build_qasm_import_items, build_range_expr, build_reset_call, build_return_expr, + build_return_unit, build_stmt_semi_from_expr, build_stmt_semi_from_expr_with_span, + build_top_level_ns_with_items, build_tuple_expr, build_unary_op_expr, + build_unmanaged_qubit_alloc, build_unmanaged_qubit_alloc_array, build_while_stmt, + build_wrapped_block_expr, managed_qubit_alloc_array, map_qsharp_type_to_ast_ty, + wrap_expr_in_parens, + }, + io::SourceResolver, + parser::ast::{list_from_iter, List}, + semantic::{ + ast::{ + BinaryOpExpr, Cast, DiscreteSet, Expr, GateOperand, GateOperandKind, IndexElement, + IndexExpr, IndexSet, IndexedIdent, LiteralKind, MeasureExpr, TimeUnit, UnaryOpExpr, + }, + symbols::{IOKind, Symbol, SymbolId, SymbolTable}, + types::{promote_types, ArrayDimensions, Type}, + }, + CompilerConfig, OperationSignature, OutputSemantics, ProgramType, QasmCompileUnit, + QubitSemantics, +}; + +use crate::semantic::ast as semast; +use qsc_ast::ast::{self as qsast, NodeId, Package}; + +/// Helper to create an error expression. Used when we fail to +/// compile an expression. It is assumed that an error was +/// already reported. +fn err_expr(span: Span) -> qsast::Expr { + qsast::Expr { + span, + ..Default::default() + } +} + +pub fn compile_to_qsharp_ast_with_config( + source: S, + path: P, + resolver: Option<&mut R>, + config: CompilerConfig, +) -> QasmCompileUnit +where + S: AsRef, + P: AsRef, + R: SourceResolver, +{ + let res = if let Some(resolver) = resolver { + crate::semantic::parse_source(source, path, resolver) + } else { + crate::semantic::parse(source, path) + }; + let program = res.program; + + let compiler = crate::compiler::QasmCompiler { + source_map: res.source_map, + config, + stmts: vec![], + symbols: res.symbols, + errors: res.errors, + }; + + compiler.compile(&program) +} + +pub struct QasmCompiler { + /// The source map of QASM sources for error reporting. + pub source_map: SourceMap, + /// The configuration for the compiler. + /// This includes the qubit semantics to follow when compiling to Q# AST. + /// The output semantics to follow when compiling to Q# AST. + /// The program type to compile to. + pub config: CompilerConfig, + /// The compiled statments accumulated during compilation. + pub stmts: Vec, + pub symbols: SymbolTable, + pub errors: Vec>, +} + +impl QasmCompiler { + /// The main entry into compilation. This function will compile the + /// source file and build the appropriate package based on the + /// configuration. + pub fn compile(mut self, program: &crate::semantic::ast::Program) -> QasmCompileUnit { + // in non-file mode we need the runtime imports in the body + let program_ty = self.config.program_ty.clone(); + + // If we are compiling for operation/fragments, we need to + // prepend to the list of statements. + // In file mode we need to add top level imports which are + // handled in the `build_file` method. + if !matches!(program_ty, ProgramType::File) { + self.append_runtime_import_decls(); + } + + self.compile_stmts(&program.statements); + let (package, signature) = match program_ty { + ProgramType::File => self.build_file(), + ProgramType::Operation => self.build_operation(), + ProgramType::Fragments => (self.build_fragments(), None), + }; + + QasmCompileUnit::new(self.source_map, self.errors, Some(package), signature) + } + + /// Build a package with namespace and an operation + /// containing the compiled statements. + fn build_file(&mut self) -> (Package, Option) { + let whole_span = self.whole_span(); + let operation_name = self.config.operation_name(); + let (operation, mut signature) = self.create_entry_operation(operation_name, whole_span); + let ns = self.config.namespace(); + signature.ns = Some(ns.to_string()); + let mut items = build_qasm_import_items(); + items.push(operation); + let top = build_top_level_ns_with_items(whole_span, ns, items); + ( + Package { + nodes: Box::new([top]), + ..Default::default() + }, + Some(signature), + ) + } + + /// Creates an operation with the given name. + fn build_operation(&mut self) -> (qsast::Package, Option) { + let whole_span = self.whole_span(); + let operation_name = self.config.operation_name(); + let (operation, signature) = self.create_entry_operation(operation_name, whole_span); + ( + Package { + nodes: Box::new([qsast::TopLevelNode::Stmt(Box::new(qsast::Stmt { + kind: Box::new(qsast::StmtKind::Item(Box::new(operation))), + span: whole_span, + id: qsast::NodeId::default(), + }))]), + ..Default::default() + }, + Some(signature), + ) + } + + /// Turns the compiled statements into package of top level nodes + fn build_fragments(&mut self) -> qsast::Package { + let nodes = self + .stmts + .drain(..) + .map(Box::new) + .map(qsast::TopLevelNode::Stmt) + .collect::>() + .into_boxed_slice(); + qsast::Package { + nodes, + ..Default::default() + } + } + + /// Returns a span containing all the statements in the program. + fn whole_span(&self) -> Span { + let main_src = self + .source_map + .iter() + .next() + .expect("there is at least one source"); + + #[allow(clippy::cast_possible_truncation)] + Span { + lo: main_src.offset, + hi: main_src.offset + main_src.contents.len() as u32, + } + } + + fn create_entry_operation>( + &mut self, + name: S, + whole_span: Span, + ) -> (qsast::Item, OperationSignature) { + let stmts = self.stmts.drain(..).collect::>(); + let input = self.symbols.get_input(); + + // Analyze input for `Angle` types which we can't support as it would require + // passing a struct from Python. So we need to raise an error saying to use `float` + // which will preserve the angle type semantics via implicit conversion to angle + // in the qasm program. + if let Some(inputs) = &input { + for input in inputs { + if matches!(input.qsharp_ty, crate::types::Type::Angle(..)) { + let message = + "use `float` types for passing input, using `angle` types".to_string(); + let kind = CompilerErrorKind::NotSupported(message, input.span); + self.push_compiler_error(kind); + } + } + } + + let output = self.symbols.get_output(); + self.create_entry_item( + name, + stmts, + input, + output, + whole_span, + self.config.output_semantics, + ) + } + + #[allow(clippy::too_many_lines)] + fn create_entry_item>( + &mut self, + name: S, + stmts: Vec, + input: Option>>, + output: Option>>, + whole_span: Span, + output_semantics: OutputSemantics, + ) -> (qsast::Item, OperationSignature) { + let mut stmts = stmts; + let is_qiskit = matches!(output_semantics, OutputSemantics::Qiskit); + let mut signature = OperationSignature { + input: vec![], + output: String::new(), + name: name.as_ref().to_string(), + ns: None, + }; + let output_ty = self.apply_output_semantics( + output, + whole_span, + output_semantics, + &mut stmts, + is_qiskit, + ); + + let ast_ty = map_qsharp_type_to_ast_ty(&output_ty); + signature.output = format!("{output_ty}"); + // This can create a collision on multiple compiles when interactive + // We also have issues with the new entry point inference logic. + let input_desc = input + .iter() + .flat_map(|s| { + s.iter() + .map(|s| (s.name.to_string(), format!("{}", s.qsharp_ty))) + }) + .collect::>(); + signature.input = input_desc; + let input_pats = input + .into_iter() + .flat_map(|s| { + s.into_iter().map(|s| { + build_arg_pat( + s.name.clone(), + s.span, + map_qsharp_type_to_ast_ty(&s.qsharp_ty), + ) + }) + }) + .collect::>(); + let add_entry_point_attr = matches!(self.config.program_ty, ProgramType::File); + ( + build_operation_with_stmts( + name, + input_pats, + ast_ty, + stmts, + whole_span, + add_entry_point_attr, + ), + signature, + ) + } + + fn apply_output_semantics( + &mut self, + output: Option>>, + whole_span: Span, + output_semantics: OutputSemantics, + stmts: &mut Vec, + is_qiskit: bool, + ) -> crate::types::Type { + let output_ty = if matches!(output_semantics, OutputSemantics::ResourceEstimation) { + // we have no output, but need to set the entry point return type + crate::types::Type::Tuple(vec![]) + } else if let Some(output) = output { + let output_exprs = if is_qiskit { + output + .iter() + .rev() + .filter(|symbol| { + matches!(symbol.ty, crate::semantic::types::Type::BitArray(..)) + }) + .map(|symbol| { + let ident = + build_path_ident_expr(symbol.name.as_str(), symbol.span, symbol.span); + + build_array_reverse_expr(ident) + }) + .collect::>() + } else { + output + .iter() + .map(|symbol| { + let ident = + build_path_ident_expr(symbol.name.as_str(), symbol.span, symbol.span); + if matches!(symbol.ty, Type::Angle(..)) { + // we can't output a struct, so we need to convert it to a double + build_call_with_param( + "__AngleAsDouble__", + &[], + ident, + symbol.span, + symbol.span, + symbol.span, + ) + } else { + ident + } + }) + .collect::>() + }; + // this is the output whether it is inferred or explicitly defined + // map the output symbols into a return statement, add it to the nodes list, + // and get the entry point return type + let output_types = if is_qiskit { + output + .iter() + .rev() + .filter(|symbol| { + matches!(symbol.ty, crate::semantic::types::Type::BitArray(..)) + }) + .map(|symbol| symbol.qsharp_ty.clone()) + .collect::>() + } else { + output + .iter() + .map(|symbol| { + if matches!(symbol.qsharp_ty, crate::types::Type::Angle(..)) { + crate::types::Type::Double(symbol.ty.is_const()) + } else { + symbol.qsharp_ty.clone() + } + }) + .collect::>() + }; + + let (output_ty, output_expr) = if output_types.len() == 1 { + (output_types[0].clone(), output_exprs[0].clone()) + } else { + let output_ty = crate::types::Type::Tuple(output_types); + let output_expr = build_tuple_expr(output_exprs); + (output_ty, output_expr) + }; + + let return_stmt = build_implicit_return_stmt(output_expr); + stmts.push(return_stmt); + output_ty + } else { + if is_qiskit { + let kind = CompilerErrorKind::QiskitEntryPointMissingOutput(whole_span); + self.push_compiler_error(kind); + } + crate::types::Type::Tuple(vec![]) + }; + output_ty + } + + /// Appends the runtime imports to the compiled statements. + fn append_runtime_import_decls(&mut self) { + for stmt in build_qasm_import_decl() { + self.stmts.push(stmt); + } + } + + fn compile_stmts(&mut self, smtms: &[Box]) { + for stmt in smtms { + let compiled_stmt = self.compile_stmt(stmt.as_ref()); + if let Some(stmt) = compiled_stmt { + self.stmts.push(stmt); + } + } + } + + fn compile_stmt(&mut self, stmt: &crate::semantic::ast::Stmt) -> Option { + if !stmt.annotations.is_empty() + && !matches!( + stmt.kind.as_ref(), + semast::StmtKind::QuantumGateDefinition(..) | semast::StmtKind::Def(..) + ) + { + for annotation in &stmt.annotations { + self.push_compiler_error(CompilerErrorKind::InvalidAnnotationTarget( + annotation.span, + )); + } + } + + match stmt.kind.as_ref() { + semast::StmtKind::Alias(stmt) => self.compile_alias_decl_stmt(stmt), + semast::StmtKind::Assign(stmt) => self.compile_assign_stmt(stmt), + semast::StmtKind::IndexedAssign(stmt) => self.compile_indexed_assign_stmt(stmt), + semast::StmtKind::AssignOp(stmt) => self.compile_assign_op_stmt(stmt), + semast::StmtKind::Barrier(stmt) => Self::compile_barrier_stmt(stmt), + semast::StmtKind::Box(stmt) => self.compile_box_stmt(stmt), + semast::StmtKind::Block(stmt) => self.compile_block_stmt(stmt), + semast::StmtKind::Break(stmt) => self.compile_break_stmt(stmt), + semast::StmtKind::CalibrationGrammar(stmt) => { + self.compile_calibration_grammar_stmt(stmt) + } + semast::StmtKind::ClassicalDecl(stmt) => self.compile_classical_decl(stmt), + semast::StmtKind::Continue(stmt) => self.compile_continue_stmt(stmt), + semast::StmtKind::Def(def_stmt) => self.compile_def_stmt(def_stmt, &stmt.annotations), + semast::StmtKind::DefCal(stmt) => self.compile_def_cal_stmt(stmt), + semast::StmtKind::Delay(stmt) => self.compile_delay_stmt(stmt), + semast::StmtKind::End(stmt) => Self::compile_end_stmt(stmt), + semast::StmtKind::ExprStmt(stmt) => self.compile_expr_stmt(stmt), + semast::StmtKind::ExternDecl(stmt) => self.compile_extern_stmt(stmt), + semast::StmtKind::For(stmt) => self.compile_for_stmt(stmt), + semast::StmtKind::If(stmt) => self.compile_if_stmt(stmt), + semast::StmtKind::GateCall(stmt) => self.compile_gate_call_stmt(stmt), + semast::StmtKind::Include(stmt) => self.compile_include_stmt(stmt), + semast::StmtKind::InputDeclaration(stmt) => self.compile_input_decl_stmt(stmt), + semast::StmtKind::OutputDeclaration(stmt) => self.compile_output_decl_stmt(stmt), + semast::StmtKind::MeasureArrow(stmt) => self.compile_measure_stmt(stmt), + semast::StmtKind::Pragma(stmt) => self.compile_pragma_stmt(stmt), + semast::StmtKind::QuantumGateDefinition(gate_stmt) => { + self.compile_gate_decl_stmt(gate_stmt, &stmt.annotations) + } + semast::StmtKind::QubitDecl(stmt) => self.compile_qubit_decl_stmt(stmt), + semast::StmtKind::QubitArrayDecl(stmt) => self.compile_qubit_array_decl_stmt(stmt), + semast::StmtKind::Reset(stmt) => self.compile_reset_stmt(stmt), + semast::StmtKind::Return(stmt) => self.compile_return_stmt(stmt), + semast::StmtKind::Switch(stmt) => self.compile_switch_stmt(stmt), + semast::StmtKind::WhileLoop(stmt) => self.compile_while_stmt(stmt), + semast::StmtKind::Err => { + // todo: determine if we should push an error here + // Are we going to allow trying to compile a program with semantic errors? + None + } + } + } + + fn compile_alias_decl_stmt(&mut self, stmt: &semast::AliasDeclStmt) -> Option { + self.push_unimplemented_error_message("alias statements", stmt.span); + None + } + + fn compile_assign_stmt(&mut self, stmt: &semast::AssignStmt) -> Option { + let symbol = self.symbols[stmt.symbol_id].clone(); + let name = &symbol.name; + + let stmt_span = stmt.span; + let name_span = stmt.lhs_span; + + let rhs = self.compile_expr(&stmt.rhs); + let stmt = build_assignment_statement(name_span, name, rhs, stmt_span); + + Some(stmt) + } + + fn compile_indexed_assign_stmt( + &mut self, + stmt: &semast::IndexedAssignStmt, + ) -> Option { + let symbol = self.symbols[stmt.symbol_id].clone(); + + let indices: Vec<_> = stmt + .indices + .iter() + .map(|elem| self.compile_index_element(elem)) + .collect(); + + let rhs = self.compile_expr(&stmt.rhs); + + if stmt.indices.len() != 1 { + self.push_unimplemented_error_message( + "multi-dimensional array index expressions", + stmt.span, + ); + return None; + } + + let index_expr = indices[0].clone(); + + let stmt = build_indexed_assignment_statement( + stmt.name_span, + symbol.name.clone(), + index_expr, + rhs, + stmt.span, + ); + + Some(stmt) + } + + fn compile_assign_op_stmt(&mut self, stmt: &semast::AssignOpStmt) -> Option { + // If the lhs is of type Angle, we call compile_assign_stmt with the rhs = lhs + rhs. + // This will call compile_binary_expr which handles angle & complex correctly. + if matches!(&stmt.lhs.ty, Type::Angle(..) | Type::Complex(..)) { + if stmt.indices.is_empty() { + let rhs = semast::Expr { + span: stmt.span, + ty: stmt.lhs.ty.clone(), + kind: Box::new(semast::ExprKind::BinaryOp(semast::BinaryOpExpr { + op: stmt.op, + lhs: stmt.lhs.clone(), + rhs: stmt.rhs.clone(), + })), + }; + + let stmt = semast::AssignStmt { + span: stmt.span, + symbol_id: stmt.symbol_id, + lhs_span: stmt.lhs.span, + rhs, + }; + + return self.compile_assign_stmt(&stmt); + } + + if stmt.indices.len() != 1 { + self.push_unimplemented_error_message( + "multi-dimensional array index expressions", + stmt.span, + ); + return None; + } + + let lhs = semast::Expr { + span: stmt.span, + ty: stmt.lhs.ty.clone(), + kind: Box::new(semast::ExprKind::IndexExpr(semast::IndexExpr { + span: stmt.lhs.span, + collection: stmt.lhs.clone(), + index: *stmt.indices[0].clone(), + })), + }; + + let rhs = semast::Expr { + span: stmt.span, + ty: stmt.lhs.ty.clone(), + kind: Box::new(semast::ExprKind::BinaryOp(semast::BinaryOpExpr { + op: stmt.op, + lhs, + rhs: stmt.rhs.clone(), + })), + }; + + let stmt = semast::IndexedAssignStmt { + span: stmt.span, + symbol_id: stmt.symbol_id, + name_span: stmt.lhs.span, + indices: stmt.indices.clone(), + rhs, + }; + + return self.compile_indexed_assign_stmt(&stmt); + } + + let lhs = self.compile_expr(&stmt.lhs); + let rhs = self.compile_expr(&stmt.rhs); + let qsop = Self::map_bin_op(stmt.op); + + let expr = build_binary_expr(true, qsop, lhs, rhs, stmt.span); + Some(build_stmt_semi_from_expr(expr)) + } + + fn compile_barrier_stmt(stmt: &semast::BarrierStmt) -> Option { + Some(build_barrier_call(stmt.span)) + } + + fn compile_box_stmt(&mut self, stmt: &semast::BoxStmt) -> Option { + self.push_unimplemented_error_message("box statements", stmt.span); + None + } + + fn compile_block(&mut self, block: &semast::Block) -> qsast::Block { + let stmts = block + .stmts + .iter() + .filter_map(|stmt| self.compile_stmt(stmt)) + .collect::>(); + qsast::Block { + id: qsast::NodeId::default(), + stmts: list_from_iter(stmts), + span: block.span, + } + } + + fn compile_block_stmt(&mut self, block: &semast::Block) -> Option { + let block = self.compile_block(block); + Some(build_stmt_semi_from_expr(build_wrapped_block_expr(block))) + } + + fn compile_break_stmt(&mut self, stmt: &semast::BreakStmt) -> Option { + self.push_unsupported_error_message("break stmt", stmt.span); + None + } + + fn compile_calibration_grammar_stmt( + &mut self, + stmt: &semast::CalibrationGrammarStmt, + ) -> Option { + self.push_unimplemented_error_message("calibration grammar statements", stmt.span); + None + } + + fn compile_classical_decl( + &mut self, + decl: &semast::ClassicalDeclarationStmt, + ) -> Option { + let symbol = &self.symbols[decl.symbol_id].clone(); + let name = &symbol.name; + let is_const = symbol.ty.is_const(); + let ty_span = decl.ty_span; + let decl_span = decl.span; + let name_span = symbol.span; + let qsharp_ty = &symbol.qsharp_ty; + let expr = decl.init_expr.as_ref(); + + let expr = self.compile_expr(expr); + let stmt = build_classical_decl( + name, is_const, ty_span, decl_span, name_span, qsharp_ty, expr, + ); + + Some(stmt) + } + + fn compile_continue_stmt(&mut self, stmt: &semast::ContinueStmt) -> Option { + self.push_unsupported_error_message("continue stmt", stmt.span); + None + } + + fn compile_def_stmt( + &mut self, + stmt: &semast::DefStmt, + annotations: &List, + ) -> Option { + let symbol = self.symbols[stmt.symbol_id].clone(); + let name = symbol.name.clone(); + + let cargs: Vec<_> = stmt + .params + .iter() + .map(|arg| { + let symbol = self.symbols[*arg].clone(); + let name = symbol.name.clone(); + let ast_type = map_qsharp_type_to_ast_ty(&symbol.qsharp_ty); + ( + name.clone(), + ast_type.clone(), + build_arg_pat(name, symbol.span, ast_type), + ) + }) + .collect(); + + let body = Some(self.compile_block(&stmt.body)); + let return_type = map_qsharp_type_to_ast_ty(&stmt.return_type); + let kind = if stmt.has_qubit_params { + qsast::CallableKind::Operation + } else { + qsast::CallableKind::Function + }; + + let attrs = annotations + .iter() + .filter_map(|annotation| self.compile_annotation(annotation)); + + // We use the same primitives used for declaring gates, because def declarations + // in QASM can take qubits as arguments and call quantum gates. + Some(build_function_or_operation( + name, + cargs, + vec![], + body, + symbol.span, + stmt.body.span, + stmt.span, + return_type, + kind, + None, + list_from_iter(attrs), + )) + } + + fn compile_def_cal_stmt(&mut self, stmt: &semast::DefCalStmt) -> Option { + self.push_unimplemented_error_message("def cal statements", stmt.span); + None + } + + fn compile_delay_stmt(&mut self, stmt: &semast::DelayStmt) -> Option { + self.push_unimplemented_error_message("delay statements", stmt.span); + None + } + + fn compile_end_stmt(stmt: &semast::EndStmt) -> Option { + Some(build_end_stmt(stmt.span)) + } + + fn compile_expr_stmt(&mut self, stmt: &semast::ExprStmt) -> Option { + let expr = self.compile_expr(&stmt.expr); + Some(build_stmt_semi_from_expr_with_span(expr, stmt.span)) + } + + fn compile_extern_stmt(&mut self, stmt: &semast::ExternDecl) -> Option { + self.push_unimplemented_error_message("extern statements", stmt.span); + None + } + + fn compile_for_stmt(&mut self, stmt: &semast::ForStmt) -> Option { + let loop_var = self.symbols[stmt.loop_variable].clone(); + let iterable = self.compile_enumerable_set(&stmt.set_declaration); + let body = self.compile_block(&Self::stmt_as_block(&stmt.body)); + + Some(build_for_stmt( + &loop_var.name, + loop_var.span, + &loop_var.qsharp_ty, + iterable, + body, + stmt.span, + )) + } + + fn compile_if_stmt(&mut self, stmt: &semast::IfStmt) -> Option { + let condition = self.compile_expr(&stmt.condition); + let then_block = self.compile_block(&Self::stmt_as_block(&stmt.if_body)); + let else_block = stmt + .else_body + .as_ref() + .map(|stmt| self.compile_block(&Self::stmt_as_block(stmt))); + + let if_expr = if let Some(else_block) = else_block { + build_if_expr_then_block_else_block(condition, then_block, else_block, stmt.span) + } else { + build_if_expr_then_block(condition, then_block, stmt.span) + }; + + Some(build_stmt_semi_from_expr(if_expr)) + } + + fn stmt_as_block(stmt: &semast::Stmt) -> semast::Block { + match &*stmt.kind { + semast::StmtKind::Block(block) => *block.to_owned(), + _ => semast::Block { + span: stmt.span, + stmts: list_from_iter([stmt.clone()]), + }, + } + } + + fn compile_function_call_expr(&mut self, expr: &semast::FunctionCall) -> qsast::Expr { + let symbol = self.symbols[expr.symbol_id].clone(); + let name = &symbol.name; + let name_span = expr.fn_name_span; + if expr.args.is_empty() { + build_call_no_params(name, &[], expr.span, expr.fn_name_span) + } else { + let args: Vec<_> = expr + .args + .iter() + .map(|expr| self.compile_expr(expr)) + .collect(); + + if args.len() == 1 { + let operand_span = expr.args[0].span; + let operand = args.into_iter().next().expect("there is one argument"); + build_call_with_param(name, &[], operand, name_span, operand_span, expr.span) + } else { + build_call_with_params(name, &[], args, name_span, expr.span) + } + } + } + + fn compile_gate_call_stmt(&mut self, stmt: &semast::GateCall) -> Option { + let symbol = self.symbols[stmt.symbol_id].clone(); + let mut qubits: Vec<_> = stmt + .qubits + .iter() + .map(|q| self.compile_gate_operand(q)) + .collect(); + let args: Vec<_> = stmt.args.iter().map(|arg| self.compile_expr(arg)).collect(); + + // Take the number of qubit args that the gates expects from the source qubits. + let gate_qubits = + qubits.split_off(qubits.len().saturating_sub(stmt.quantum_arity as usize)); + + // Then merge the classical args with the qubit args. This will give + // us the args for the call prior to wrapping in tuples for controls. + let args: Vec<_> = args.into_iter().chain(gate_qubits).collect(); + let mut args = build_gate_call_param_expr(args, qubits.len()); + let mut callee = build_path_ident_expr(&symbol.name, stmt.gate_name_span, stmt.span); + + for modifier in &stmt.modifiers { + match &modifier.kind { + semast::GateModifierKind::Inv => { + callee = build_unary_op_expr( + qsast::UnOp::Functor(qsast::Functor::Adj), + callee, + modifier.modifier_keyword_span, + ); + } + semast::GateModifierKind::Pow(expr) => { + let exponent_expr = self.compile_expr(expr); + args = build_tuple_expr(vec![exponent_expr, callee, args]); + callee = build_path_ident_expr("__Pow__", modifier.span, stmt.span); + } + semast::GateModifierKind::Ctrl(num_ctrls) => { + // remove the last n qubits from the qubit list + if qubits.len() < *num_ctrls as usize { + let kind = CompilerErrorKind::InvalidNumberOfQubitArgs( + *num_ctrls as usize, + qubits.len(), + modifier.span, + ); + self.push_compiler_error(kind); + return None; + } + let ctrl = qubits.split_off(qubits.len().saturating_sub(*num_ctrls as usize)); + let ctrls = build_expr_array_expr(ctrl, modifier.span); + args = build_tuple_expr(vec![ctrls, args]); + callee = build_unary_op_expr( + qsast::UnOp::Functor(qsast::Functor::Ctl), + callee, + modifier.modifier_keyword_span, + ); + } + semast::GateModifierKind::NegCtrl(num_ctrls) => { + // remove the last n qubits from the qubit list + if qubits.len() < *num_ctrls as usize { + let kind = CompilerErrorKind::InvalidNumberOfQubitArgs( + *num_ctrls as usize, + qubits.len(), + modifier.span, + ); + self.push_compiler_error(kind); + return None; + } + let ctrl = qubits.split_off(qubits.len().saturating_sub(*num_ctrls as usize)); + let ctrls = build_expr_array_expr(ctrl, modifier.span); + let lit_0 = build_lit_int_expr(0, Span::default()); + args = build_tuple_expr(vec![lit_0, callee, ctrls, args]); + callee = build_path_ident_expr( + "ApplyControlledOnInt", + modifier.modifier_keyword_span, + stmt.span, + ); + } + } + } + + let expr = build_gate_call_with_params_and_callee(args, callee, stmt.span); + Some(build_stmt_semi_from_expr(expr)) + } + + fn compile_include_stmt(&mut self, stmt: &semast::IncludeStmt) -> Option { + self.push_unimplemented_error_message("include statements", stmt.span); + None + } + + #[allow(clippy::unused_self)] + fn compile_input_decl_stmt(&mut self, _stmt: &semast::InputDeclaration) -> Option { + None + } + + fn compile_output_decl_stmt( + &mut self, + stmt: &semast::OutputDeclaration, + ) -> Option { + let symbol = &self.symbols[stmt.symbol_id]; + + // input decls should have been pushed to symbol table, + // but should not be in the stmts list. + if symbol.io_kind != IOKind::Output { + return None; + } + + let symbol = symbol.clone(); + let name = &symbol.name; + let is_const = symbol.ty.is_const(); + let ty_span = stmt.ty_span; // todo + let decl_span = stmt.span; + let name_span = symbol.span; + let qsharp_ty = &symbol.qsharp_ty; + + let expr = stmt.init_expr.as_ref(); + + let expr = self.compile_expr(expr); + let stmt = build_classical_decl( + name, is_const, ty_span, decl_span, name_span, qsharp_ty, expr, + ); + + Some(stmt) + } + + fn compile_measure_stmt(&mut self, stmt: &semast::MeasureArrowStmt) -> Option { + self.push_unimplemented_error_message("measure statements", stmt.span); + None + } + + fn compile_pragma_stmt(&mut self, stmt: &semast::Pragma) -> Option { + self.push_unimplemented_error_message("pragma statements", stmt.span); + None + } + + fn compile_gate_decl_stmt( + &mut self, + stmt: &semast::QuantumGateDefinition, + annotations: &List, + ) -> Option { + let symbol = self.symbols[stmt.symbol_id].clone(); + let name = symbol.name.clone(); + // if the gate has the name of a qasm or qiskit built-in gate + // it means that the stdgates libraries are not being used. + // we let the user compile their own gates with the same name. + + let cargs: Vec<_> = stmt + .params + .iter() + .map(|arg| { + let symbol = self.symbols[*arg].clone(); + let name = symbol.name.clone(); + let ast_type = map_qsharp_type_to_ast_ty(&symbol.qsharp_ty); + ( + name.clone(), + ast_type.clone(), + build_arg_pat(name, symbol.span, ast_type), + ) + }) + .collect(); + + let qargs: Vec<_> = stmt + .qubits + .iter() + .map(|arg| { + let symbol = self.symbols[*arg].clone(); + let name = symbol.name.clone(); + let ast_type = map_qsharp_type_to_ast_ty(&symbol.qsharp_ty); + ( + name.clone(), + ast_type.clone(), + build_arg_pat(name, symbol.span, ast_type), + ) + }) + .collect(); + + let body = Some(self.compile_block(&stmt.body)); + + let attrs = annotations + .iter() + .filter_map(|annotation| self.compile_annotation(annotation)); + + // Do not compile functors if we have the @SimulatableIntrinsic annotation. + let functors = if annotations + .iter() + .any(|annotation| annotation.identifier.as_ref() == "SimulatableIntrinsic") + { + None + } else { + Some(build_adj_plus_ctl_functor()) + }; + + Some(build_function_or_operation( + name, + cargs, + qargs, + body, + stmt.name_span, + stmt.body.span, + stmt.span, + build_path_ident_ty("Unit"), + qsast::CallableKind::Operation, + functors, + list_from_iter(attrs), + )) + } + + fn compile_annotation(&mut self, annotation: &semast::Annotation) -> Option { + match annotation.identifier.as_ref() { + "SimulatableIntrinsic" | "Config" => Some(build_attr( + &annotation.identifier, + annotation.value.as_ref(), + annotation.span, + )), + _ => { + self.push_compiler_error(CompilerErrorKind::UnknownAnnotation( + format!("@{}", annotation.identifier), + annotation.span, + )); + None + } + } + } + + fn compile_qubit_decl_stmt(&mut self, stmt: &semast::QubitDeclaration) -> Option { + let symbol = self.symbols[stmt.symbol_id].clone(); + let name = &symbol.name; + let name_span = symbol.span; + + let stmt = match self.config.qubit_semantics { + QubitSemantics::QSharp => build_managed_qubit_alloc(name, stmt.span, name_span), + QubitSemantics::Qiskit => build_unmanaged_qubit_alloc(name, stmt.span, name_span), + }; + Some(stmt) + } + + fn compile_qubit_array_decl_stmt( + &mut self, + stmt: &semast::QubitArrayDeclaration, + ) -> Option { + let symbol = self.symbols[stmt.symbol_id].clone(); + let name = &symbol.name; + let name_span = symbol.span; + + let stmt = match self.config.qubit_semantics { + QubitSemantics::QSharp => { + managed_qubit_alloc_array(name, stmt.size, stmt.span, name_span, stmt.size_span) + } + QubitSemantics::Qiskit => build_unmanaged_qubit_alloc_array( + name, + stmt.size, + stmt.span, + name_span, + stmt.size_span, + ), + }; + Some(stmt) + } + + fn compile_reset_stmt(&mut self, stmt: &semast::ResetStmt) -> Option { + let operand = self.compile_gate_operand(&stmt.operand); + let operand_span = operand.span; + let expr = build_reset_call(operand, stmt.reset_token_span, operand_span); + Some(build_stmt_semi_from_expr(expr)) + } + + fn compile_return_stmt(&mut self, stmt: &semast::ReturnStmt) -> Option { + let expr = stmt.expr.as_ref().map(|expr| self.compile_expr(expr)); + + let expr = if let Some(expr) = expr { + build_return_expr(expr, stmt.span) + } else { + build_return_unit(stmt.span) + }; + + Some(build_stmt_semi_from_expr(expr)) + } + + fn compile_switch_stmt(&mut self, stmt: &semast::SwitchStmt) -> Option { + // For each case, convert the lhs into a sequence of equality checks + // and then fold them into a single expression of logical ors for + // the if expr + let control = self.compile_expr(&stmt.target); + let cases: Vec<(qsast::Expr, qsast::Block)> = stmt + .cases + .iter() + .map(|case| { + let block = self.compile_block(&case.block); + + let case = case + .labels + .iter() + .map(|label| { + let lhs = control.clone(); + let rhs = self.compile_expr(label); + build_binary_expr(false, qsast::BinOp::Eq, lhs, rhs, label.span) + }) + .fold(None, |acc, expr| match acc { + None => Some(expr), + Some(acc) => { + let qsop = qsast::BinOp::OrL; + let span = Span { + lo: acc.span.lo, + hi: expr.span.hi, + }; + Some(build_binary_expr(false, qsop, acc, expr, span)) + } + }); + // The type checker doesn't know that we have at least one case + // so we have to unwrap here since the accumulation is guaranteed + // to have Some(value) + let case = case.expect("Case must have at least one expression"); + (case, block) + }) + .collect(); + + let default_block = stmt.default.as_ref().map(|block| self.compile_block(block)); + + let default_expr = default_block.map(build_wrapped_block_expr); + let if_expr = cases + .into_iter() + .rev() + .fold(default_expr, |else_expr, (cond, block)| { + let span = Span { + lo: cond.span.lo, + hi: block.span.hi, + }; + Some(build_if_expr_then_block_else_expr( + cond, block, else_expr, span, + )) + }); + if_expr.map(build_stmt_semi_from_expr) + } + + fn compile_while_stmt(&mut self, stmt: &semast::WhileLoop) -> Option { + let condition = self.compile_expr(&stmt.condition); + match &*stmt.body.kind { + semast::StmtKind::Block(block) => { + let block = self.compile_block(block); + Some(build_while_stmt(condition, block, stmt.span)) + } + semast::StmtKind::Err => Some(qsast::Stmt { + id: NodeId::default(), + span: stmt.body.span, + kind: Box::new(qsast::StmtKind::Err), + }), + _ => { + let block_stmt = self.compile_stmt(&stmt.body)?; + let block = qsast::Block { + id: qsast::NodeId::default(), + stmts: list_from_iter([block_stmt]), + span: stmt.span, + }; + Some(build_while_stmt(condition, block, stmt.span)) + } + } + } + + fn compile_expr(&mut self, expr: &semast::Expr) -> qsast::Expr { + match expr.kind.as_ref() { + semast::ExprKind::Err => qsast::Expr { + span: expr.span, + ..Default::default() + }, + semast::ExprKind::Ident(symbol_id) => self.compile_ident_expr(*symbol_id, expr.span), + semast::ExprKind::IndexedIdentifier(indexed_ident) => { + self.compile_indexed_ident_expr(indexed_ident) + } + semast::ExprKind::UnaryOp(unary_op_expr) => self.compile_unary_op_expr(unary_op_expr), + semast::ExprKind::BinaryOp(binary_op_expr) => { + self.compile_binary_op_expr(binary_op_expr) + } + semast::ExprKind::Lit(literal_kind) => { + self.compile_literal_expr(literal_kind, expr.span) + } + semast::ExprKind::FunctionCall(function_call) => { + self.compile_function_call_expr(function_call) + } + semast::ExprKind::Cast(cast) => self.compile_cast_expr(cast), + semast::ExprKind::IndexExpr(index_expr) => self.compile_index_expr(index_expr), + semast::ExprKind::Paren(pexpr) => self.compile_paren_expr(pexpr, expr.span), + semast::ExprKind::Measure(expr) => self.compile_measure_expr(expr), + } + } + + fn compile_ident_expr(&mut self, symbol_id: SymbolId, span: Span) -> qsast::Expr { + let symbol = &self.symbols[symbol_id]; + match symbol.name.as_str() { + "euler" | "ℇ" => build_math_call_no_params("E", span), + "pi" | "π" => build_math_call_no_params("PI", span), + "tau" | "τ" => { + let expr = build_math_call_no_params("PI", span); + qsast::Expr { + kind: Box::new(qsast::ExprKind::BinOp( + qsast::BinOp::Mul, + Box::new(build_lit_double_expr(2.0, span)), + Box::new(expr), + )), + span, + id: qsast::NodeId::default(), + } + } + _ => build_path_ident_expr(&symbol.name, span, span), + } + } + + /// The lowerer eliminated indexed identifiers with zero indices. + /// So we are safe to assume that the indices are non-empty. + fn compile_indexed_ident_expr(&mut self, indexed_ident: &IndexedIdent) -> qsast::Expr { + let span = indexed_ident.span; + let index: Vec<_> = indexed_ident + .indices + .iter() + .map(|elem| self.compile_index_element(elem)) + .collect(); + + if index.len() != 1 { + self.push_unimplemented_error_message( + "multi-dimensional array index expressions", + span, + ); + return err_expr(indexed_ident.span); + } + + let symbol = &self.symbols[indexed_ident.symbol_id]; + + let ident = + build_path_ident_expr(&symbol.name, indexed_ident.name_span, indexed_ident.span); + build_index_expr(ident, index[0].clone(), span) + } + + fn compile_unary_op_expr(&mut self, unary: &UnaryOpExpr) -> qsast::Expr { + match unary.op { + semast::UnaryOp::Neg => self.compile_neg_expr(&unary.expr, unary.span), + semast::UnaryOp::NotB => self.compile_bitwise_not_expr(&unary.expr, unary.span), + semast::UnaryOp::NotL => self.compile_logical_not_expr(&unary.expr, unary.span), + } + } + fn compile_neg_expr(&mut self, expr: &Expr, span: Span) -> qsast::Expr { + let compiled_expr = self.compile_expr(expr); + + if matches!(expr.ty, Type::Angle(..)) { + build_call_with_param("__NegAngle__", &[], compiled_expr, span, expr.span, span) + } else { + build_unary_op_expr(qsast::UnOp::Neg, compiled_expr, span) + } + } + + fn compile_bitwise_not_expr(&mut self, expr: &Expr, span: Span) -> qsast::Expr { + let compiled_expr = self.compile_expr(expr); + + if matches!(expr.ty, Type::Angle(..)) { + build_call_with_param("__AngleNotB__", &[], compiled_expr, span, expr.span, span) + } else { + build_unary_op_expr(qsast::UnOp::NotB, compiled_expr, span) + } + } + + fn compile_logical_not_expr(&mut self, expr: &Expr, span: Span) -> qsast::Expr { + let expr = self.compile_expr(expr); + build_unary_op_expr(qsast::UnOp::NotL, expr, span) + } + + fn compile_binary_op_expr(&mut self, binary: &BinaryOpExpr) -> qsast::Expr { + let op = Self::map_bin_op(binary.op); + let lhs = self.compile_expr(&binary.lhs); + let rhs = self.compile_expr(&binary.rhs); + + if matches!(&binary.lhs.ty, Type::Angle(..)) || matches!(&binary.rhs.ty, Type::Angle(..)) { + return self.compile_angle_binary_op(op, lhs, rhs, &binary.lhs.ty, &binary.rhs.ty); + } + + if matches!(&binary.lhs.ty, Type::Complex(..)) + || matches!(&binary.rhs.ty, Type::Complex(..)) + { + return Self::compile_complex_binary_op(op, lhs, rhs); + } + + let is_assignment = false; + build_binary_expr(is_assignment, op, lhs, rhs, binary.span()) + } + + fn compile_angle_binary_op( + &mut self, + op: qsast::BinOp, + lhs: qsast::Expr, + rhs: qsast::Expr, + lhs_ty: &crate::semantic::types::Type, + rhs_ty: &crate::semantic::types::Type, + ) -> qsast::Expr { + let span = Span { + lo: lhs.span.lo, + hi: rhs.span.hi, + }; + + let mut operands = vec![lhs, rhs]; + + let fn_name: &str = match op { + // Bit shift + qsast::BinOp::Shl => "__AngleShl__", + qsast::BinOp::Shr => "__AngleShr__", + + // Bitwise + qsast::BinOp::AndB => "__AngleAndB__", + qsast::BinOp::OrB => "__AngleOrB__", + qsast::BinOp::XorB => "__AngleXorB__", + + // Comparison + qsast::BinOp::Eq => "__AngleEq__", + qsast::BinOp::Neq => "__AngleNeq__", + qsast::BinOp::Gt => "__AngleGt__", + qsast::BinOp::Gte => "__AngleGte__", + qsast::BinOp::Lt => "__AngleLt__", + qsast::BinOp::Lte => "__AngleLte__", + + // Arithmetic + qsast::BinOp::Add => "__AddAngles__", + qsast::BinOp::Sub => "__SubtractAngles__", + qsast::BinOp::Mul => { + // if we are doing `int * angle` we need to + // reverse the order of the args to __MultiplyAngleByInt__ + if matches!(lhs_ty, Type::Int(..) | Type::UInt(..)) { + operands.reverse(); + } + "__MultiplyAngleByInt__" + } + qsast::BinOp::Div => { + if matches!(lhs_ty, Type::Angle(..)) + && matches!(rhs_ty, Type::Int(..) | Type::UInt(..)) + { + "__DivideAngleByInt__" + } else { + "__DivideAngleByAngle__" + } + } + + _ => { + self.push_unsupported_error_message("angle binary operation", span); + return err_expr(span); + } + }; + + build_call_with_params(fn_name, &[], operands, span, span) + } + + fn compile_complex_binary_op( + op: qsast::BinOp, + lhs: qsast::Expr, + rhs: qsast::Expr, + ) -> qsast::Expr { + let span = Span { + lo: lhs.span.lo, + hi: rhs.span.hi, + }; + + let fn_name: &str = match op { + // Arithmetic + qsast::BinOp::Add => "PlusC", + qsast::BinOp::Sub => "MinusC", + qsast::BinOp::Mul => "TimesC", + qsast::BinOp::Div => "DividedByC", + qsast::BinOp::Exp => "PowC", + _ => { + // we are already pushing a semantic error in the lowerer + // if the operation is not supported. So, we just return + // an Expr::Err here. + return err_expr(span); + } + }; + + build_math_call_from_exprs(fn_name, vec![lhs, rhs], span) + } + + fn compile_literal_expr(&mut self, lit: &LiteralKind, span: Span) -> qsast::Expr { + match lit { + LiteralKind::Angle(value) => build_lit_angle_expr(*value, span), + LiteralKind::Array(value) => self.compile_array_literal(value, span), + LiteralKind::Bitstring(big_int, width) => { + Self::compile_bitstring_literal(big_int, *width, span) + } + LiteralKind::Bit(value) => Self::compile_bit_literal(*value, span), + LiteralKind::Bool(value) => Self::compile_bool_literal(*value, span), + LiteralKind::Duration(value, time_unit) => { + self.compile_duration_literal(*value, *time_unit, span) + } + LiteralKind::Float(value) => Self::compile_float_literal(*value, span), + LiteralKind::Complex(real, imag) => Self::compile_complex_literal(*real, *imag, span), + LiteralKind::Int(value) => Self::compile_int_literal(*value, span), + LiteralKind::BigInt(value) => Self::compile_bigint_literal(value, span), + LiteralKind::String(value) => self.compile_string_literal(value, span), + } + } + + fn compile_cast_expr(&mut self, cast: &Cast) -> qsast::Expr { + let expr = self.compile_expr(&cast.expr); + let cast_expr = match cast.expr.ty { + crate::semantic::types::Type::Bit(_) => { + Self::cast_bit_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) + } + crate::semantic::types::Type::Bool(_) => { + Self::cast_bool_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) + } + crate::semantic::types::Type::Duration(_) => { + self.cast_duration_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) + } + crate::semantic::types::Type::Angle(_, _) => { + Self::cast_angle_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) + } + crate::semantic::types::Type::Complex(_, _) => { + self.cast_complex_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) + } + crate::semantic::types::Type::Float(_, _) => { + Self::cast_float_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) + } + crate::semantic::types::Type::Int(_, _) | crate::semantic::types::Type::UInt(_, _) => { + Self::cast_int_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) + } + crate::semantic::types::Type::BitArray(ArrayDimensions::One(size), _) => { + Self::cast_bit_array_expr_to_ty(expr, &cast.expr.ty, &cast.ty, size, cast.span) + } + _ => err_expr(cast.span), + }; + if matches!(*cast_expr.kind, qsast::ExprKind::Err) { + self.push_unsupported_error_message( + format!("casting {} to {} type", cast.expr.ty, cast.ty), + cast.span, + ); + } + cast_expr + } + + fn compile_index_expr(&mut self, index_expr: &IndexExpr) -> qsast::Expr { + let expr = self.compile_expr(&index_expr.collection); + let index = self.compile_index_element(&index_expr.index); + + qsast::Expr { + id: qsast::NodeId::default(), + span: index_expr.span, + kind: Box::new(qsast::ExprKind::Index(Box::new(expr), Box::new(index))), + } + } + + fn compile_paren_expr(&mut self, paren: &Expr, span: Span) -> qsast::Expr { + let expr = self.compile_expr(paren); + wrap_expr_in_parens(expr, span) + } + + fn compile_measure_expr(&mut self, expr: &MeasureExpr) -> qsast::Expr { + let call_span = expr.span; + let name_span = expr.measure_token_span; + let arg = self.compile_gate_operand(&expr.operand); + let operand_span = expr.operand.span; + build_measure_call(arg, name_span, operand_span, call_span) + } + + fn compile_gate_operand(&mut self, op: &GateOperand) -> qsast::Expr { + match &op.kind { + GateOperandKind::HardwareQubit(hw) => { + // We don't support hardware qubits, so we need to push an error + // but we can still create an identifier for the hardware qubit + // and let the rest of the containing expression compile to + // catch any other errors + let message = "hardware qubit operands"; + self.push_unsupported_error_message(message, op.span); + build_path_ident_expr(hw.name.clone(), hw.span, op.span) + } + GateOperandKind::Expr(expr) => self.compile_expr(expr), + GateOperandKind::Err => err_expr(op.span), + } + } + + fn compile_index_element(&mut self, elem: &IndexElement) -> qsast::Expr { + match elem { + IndexElement::DiscreteSet(discrete_set) => self.compile_discrete_set(discrete_set), + IndexElement::IndexSet(index_set) => self.compile_index_set(index_set), + } + } + + fn compile_discrete_set(&mut self, set: &DiscreteSet) -> qsast::Expr { + let expr_list: Vec<_> = set + .values + .iter() + .map(|expr| self.compile_expr(expr)) + .collect(); + + build_expr_array_expr(expr_list, set.span) + } + + fn compile_index_set(&mut self, set: &IndexSet) -> qsast::Expr { + // This is a temporary limitation. We can only handle + // single index expressions for now. + if set.values.len() == 1 { + if let semast::IndexSetItem::Expr(expr) = &*set.values[0] { + return self.compile_expr(expr); + } + } + + self.push_unsupported_error_message("index set expressions with multiple values", set.span); + err_expr(set.span) + } + + fn compile_enumerable_set(&mut self, set: &semast::EnumerableSet) -> qsast::Expr { + match set { + semast::EnumerableSet::DiscreteSet(set) => self.compile_discrete_set(set), + semast::EnumerableSet::Expr(expr) => self.compile_expr(expr), + semast::EnumerableSet::RangeDefinition(range) => self.compile_range_expr(range), + } + } + + fn compile_range_expr(&mut self, range: &semast::RangeDefinition) -> qsast::Expr { + let Some(start) = &range.start else { + self.push_unimplemented_error_message("omitted range start", range.span); + return err_expr(range.span); + }; + let Some(end) = &range.end else { + self.push_unimplemented_error_message("omitted range end", range.span); + return err_expr(range.span); + }; + + let start = self.compile_expr(start); + let end = self.compile_expr(end); + let step = range.step.as_ref().map(|expr| self.compile_expr(expr)); + build_range_expr(start, end, step, range.span) + } + + fn compile_array_literal(&mut self, _value: &List, span: Span) -> qsast::Expr { + self.push_unimplemented_error_message("array literals", span); + err_expr(span) + } + + fn compile_bit_literal(value: bool, span: Span) -> qsast::Expr { + build_lit_result_expr(value.into(), span) + } + + fn compile_bool_literal(value: bool, span: Span) -> qsast::Expr { + build_lit_bool_expr(value, span) + } + + fn compile_duration_literal( + &mut self, + _value: f64, + _unit: TimeUnit, + span: Span, + ) -> qsast::Expr { + self.push_unsupported_error_message("timing literals", span); + err_expr(span) + } + + fn compile_bitstring_literal(value: &BigInt, width: u32, span: Span) -> qsast::Expr { + let width = width as usize; + let bitstring = if value == &BigInt::ZERO && width == 0 { + "Bitstring(\"\")".to_string() + } else { + format!("Bitstring(\"{:0>width$}\")", value.to_str_radix(2)) + }; + build_lit_result_array_expr_from_bitstring(bitstring, span) + } + + fn compile_complex_literal(real: f64, imag: f64, span: Span) -> qsast::Expr { + build_lit_complex_expr(crate::types::Complex::new(real, imag), span) + } + + fn compile_float_literal(value: f64, span: Span) -> qsast::Expr { + build_lit_double_expr(value, span) + } + + fn compile_int_literal(value: i64, span: Span) -> qsast::Expr { + build_lit_int_expr(value, span) + } + + fn compile_bigint_literal(value: &BigInt, span: Span) -> qsast::Expr { + build_lit_bigint_expr(value.clone(), span) + } + + fn compile_string_literal(&mut self, _value: &Rc, span: Span) -> qsast::Expr { + self.push_unimplemented_error_message("string literal expressions", span); + err_expr(span) + } + + /// Pushes an unsupported error with the supplied message. + pub fn push_unsupported_error_message>(&mut self, message: S, span: Span) { + let kind = CompilerErrorKind::NotSupported(message.as_ref().to_string(), span); + self.push_compiler_error(kind); + } + + /// Pushes an unimplemented error with the supplied message. + pub fn push_unimplemented_error_message>(&mut self, message: S, span: Span) { + let kind = CompilerErrorKind::Unimplemented(message.as_ref().to_string(), span); + self.push_compiler_error(kind); + } + + /// Pushes a semantic error with the given kind. + pub fn push_compiler_error(&mut self, kind: CompilerErrorKind) { + let kind = crate::ErrorKind::Compiler(error::Error(kind)); + let error = crate::Error(kind); + let error = WithSource::from_map(&self.source_map, error); + self.errors.push(error); + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | angle | Yes | No | No | No | - | Yes | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + fn cast_angle_expr_to_ty( + expr: qsast::Expr, + expr_ty: &crate::semantic::types::Type, + ty: &crate::semantic::types::Type, + span: Span, + ) -> qsast::Expr { + assert!(matches!(expr_ty, Type::Angle(..))); + // https://openqasm.com/language/types.html#casting-from-angle + match ty { + Type::Angle(..) => { + // we know they are both angles, here we promote the width. + let promoted_ty = promote_types(expr_ty, ty); + if promoted_ty.width().is_some() && promoted_ty.width() != expr_ty.width() { + // we need to convert the angle to a different width + let width = promoted_ty.width().expect("width should be set"); + build_global_call_with_two_params( + "__ConvertAngleToWidthNoTrunc__", + expr, + build_lit_int_expr(width.into(), span), + span, + span, + ) + } else { + expr + } + } + Type::Bit(..) => { + build_call_with_param("__AngleAsResult__", &[], expr, span, span, span) + } + Type::BitArray(..) => { + build_call_with_param("__AngleAsResultArray__", &[], expr, span, span, span) + } + Type::Bool(..) => build_call_with_param("__AngleAsBool__", &[], expr, span, span, span), + _ => err_expr(span), + } + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | bit | Yes | Yes | Yes | No | Yes | - | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + fn cast_bit_expr_to_ty( + expr: qsast::Expr, + expr_ty: &crate::semantic::types::Type, + ty: &crate::semantic::types::Type, + span: Span, + ) -> qsast::Expr { + assert!(matches!(expr_ty, Type::Bit(..))); + // There is no operand, choosing the span of the node + // but we could use the expr span as well. + let operand_span = expr.span; + let name_span = span; + match ty { + &Type::Angle(..) => { + build_cast_call_by_name("__ResultAsAngle__", expr, name_span, operand_span) + } + &Type::Bool(..) => { + build_cast_call_by_name("__ResultAsBool__", expr, name_span, operand_span) + } + &Type::Float(..) => { + // The spec says that this cast isn't supported, but it + // casts to other types that case to float. For now, we'll + // say it is invalid like the spec. + err_expr(span) + } + &Type::Int(w, _) | &Type::UInt(w, _) => { + let function = if let Some(width) = w { + if width > 64 { + "__ResultAsBigInt__" + } else { + "__ResultAsInt__" + } + } else { + "__ResultAsInt__" + }; + + build_cast_call_by_name(function, expr, name_span, operand_span) + } + _ => err_expr(span), + } + } + + fn cast_bit_array_expr_to_ty( + expr: qsast::Expr, + expr_ty: &crate::semantic::types::Type, + ty: &crate::semantic::types::Type, + size: u32, + span: Span, + ) -> qsast::Expr { + assert!(matches!( + expr_ty, + Type::BitArray(ArrayDimensions::One(_), _) + )); + + let name_span = expr.span; + let operand_span = span; + + if !matches!(ty, Type::Int(..) | Type::UInt(..)) { + return err_expr(span); + } + // we know we have a bit array being cast to an int/uint + // verfiy widths + let int_width = ty.width(); + + if int_width.is_none() || (int_width == Some(size)) { + build_cast_call_by_name("__ResultArrayAsIntBE__", expr, name_span, operand_span) + } else { + err_expr(span) + } + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | bool | - | Yes | Yes | Yes | No | Yes | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + fn cast_bool_expr_to_ty( + expr: qsast::Expr, + expr_ty: &crate::semantic::types::Type, + ty: &crate::semantic::types::Type, + span: Span, + ) -> qsast::Expr { + assert!(matches!(expr_ty, Type::Bool(..))); + let name_span = expr.span; + let operand_span = span; + match ty { + Type::Bit(..) => { + build_cast_call_by_name("__BoolAsResult__", expr, name_span, operand_span) + } + Type::Float(..) => { + build_cast_call_by_name("__BoolAsDouble__", expr, name_span, operand_span) + } + Type::Int(w, _) | Type::UInt(w, _) => { + let function = if let Some(width) = w { + if *width > 64 { + "__BoolAsBigInt__" + } else { + "__BoolAsInt__" + } + } else { + "__BoolAsInt__" + }; + build_cast_call_by_name(function, expr, name_span, operand_span) + } + _ => err_expr(span), + } + } + + fn cast_complex_expr_to_ty( + &mut self, + _expr: qsast::Expr, + _expr_ty: &crate::semantic::types::Type, + _ty: &crate::semantic::types::Type, + span: Span, + ) -> qsast::Expr { + self.push_unimplemented_error_message("cast complex expressions", span); + err_expr(span) + } + + fn cast_duration_expr_to_ty( + &mut self, + _expr: qsast::Expr, + _expr_ty: &crate::semantic::types::Type, + _ty: &crate::semantic::types::Type, + span: Span, + ) -> qsast::Expr { + self.push_unimplemented_error_message("cast duration expressions", span); + err_expr(span) + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | float | Yes | Yes | Yes | - | Yes | No | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// + /// Additional cast to complex + fn cast_float_expr_to_ty( + expr: qsast::Expr, + expr_ty: &crate::semantic::types::Type, + ty: &crate::semantic::types::Type, + span: Span, + ) -> qsast::Expr { + assert!(matches!(expr_ty, Type::Float(..))); + + match ty { + &Type::Complex(..) => build_complex_from_expr(expr), + &Type::Angle(width, _) => { + let expr_span = expr.span; + let width = + build_lit_int_expr(width.unwrap_or(f64::MANTISSA_DIGITS).into(), expr_span); + build_call_with_params( + "__DoubleAsAngle__", + &[], + vec![expr, width], + expr_span, + expr_span, + ) + } + &Type::Int(w, _) | &Type::UInt(w, _) => { + let expr = build_math_call_from_exprs("Truncate", vec![expr], span); + if let Some(w) = w { + if w > 64 { + build_convert_call_expr(expr, "IntAsBigInt") + } else { + expr + } + } else { + expr + } + } + // This is a width promotion, but it is a no-op in Q#. + &Type::Float(..) => expr, + &Type::Bool(..) => { + let span = expr.span; + let expr = build_math_call_from_exprs("Truncate", vec![expr], span); + let const_int_zero_expr = build_lit_int_expr(0, span); + let qsop = qsast::BinOp::Eq; + let cond = build_binary_expr(false, qsop, expr, const_int_zero_expr, span); + build_if_expr_then_expr_else_expr( + cond, + build_lit_bool_expr(false, span), + build_lit_bool_expr(true, span), + span, + ) + } + _ => err_expr(span), + } + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | int | Yes | - | Yes | Yes | No | Yes | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | uint | Yes | Yes | - | Yes | No | Yes | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// + /// Additional cast to ``BigInt`` + /// With the exception of casting to ``BigInt``, there is no checking for overflow, + /// widths, truncation, etc. Qiskit doesn't do these kinds of casts. For general + /// `OpenQASM` support this will need to be fleshed out. + #[allow(clippy::too_many_lines)] + fn cast_int_expr_to_ty( + expr: qsast::Expr, + expr_ty: &crate::semantic::types::Type, + ty: &crate::semantic::types::Type, + span: Span, + ) -> qsast::Expr { + assert!(matches!(expr_ty, Type::Int(..) | Type::UInt(..))); + let name_span = expr.span; + let operand_span = span; + match ty { + Type::BitArray(dims, _) => { + let ArrayDimensions::One(size) = dims else { + return err_expr(span); + }; + let size = i64::from(*size); + + let size_expr = build_lit_int_expr(size, Span::default()); + build_global_call_with_two_params( + "__IntAsResultArrayBE__", + expr, + size_expr, + name_span, + operand_span, + ) + } + Type::Float(..) => build_convert_call_expr(expr, "IntAsDouble"), + Type::Int(tw, _) | Type::UInt(tw, _) => { + // uint to int, or int/uint to BigInt + if let Some(tw) = tw { + if *tw > 64 { + build_convert_call_expr(expr, "IntAsBigInt") + } else { + expr + } + } else { + expr + } + } + Type::Bool(..) => { + let expr_span = expr.span; + let const_int_zero_expr = build_lit_int_expr(0, expr.span); + let qsop = qsast::BinOp::Eq; + let cond = build_binary_expr(false, qsop, expr, const_int_zero_expr, expr_span); + build_if_expr_then_expr_else_expr( + cond, + build_lit_bool_expr(false, expr_span), + build_lit_bool_expr(true, expr_span), + expr_span, + ) + } + Type::Bit(..) => { + let expr_span = expr.span; + let const_int_zero_expr = build_lit_int_expr(0, expr.span); + let qsop = qsast::BinOp::Eq; + let cond = build_binary_expr(false, qsop, expr, const_int_zero_expr, expr_span); + build_if_expr_then_expr_else_expr( + cond, + build_lit_result_expr(qsast::Result::One, expr_span), + build_lit_result_expr(qsast::Result::Zero, expr_span), + expr_span, + ) + } + Type::Complex(..) => { + let expr = build_convert_call_expr(expr, "IntAsDouble"); + build_complex_from_expr(expr) + } + _ => err_expr(span), + } + } + + fn map_bin_op(op: semast::BinOp) -> qsast::BinOp { + match op { + semast::BinOp::Add => qsast::BinOp::Add, + semast::BinOp::AndB => qsast::BinOp::AndB, + semast::BinOp::AndL => qsast::BinOp::AndL, + semast::BinOp::Div => qsast::BinOp::Div, + semast::BinOp::Eq => qsast::BinOp::Eq, + semast::BinOp::Exp => qsast::BinOp::Exp, + semast::BinOp::Gt => qsast::BinOp::Gt, + semast::BinOp::Gte => qsast::BinOp::Gte, + semast::BinOp::Lt => qsast::BinOp::Lt, + semast::BinOp::Lte => qsast::BinOp::Lte, + semast::BinOp::Mod => qsast::BinOp::Mod, + semast::BinOp::Mul => qsast::BinOp::Mul, + semast::BinOp::Neq => qsast::BinOp::Neq, + semast::BinOp::OrB => qsast::BinOp::OrB, + semast::BinOp::OrL => qsast::BinOp::OrL, + semast::BinOp::Shl => qsast::BinOp::Shl, + semast::BinOp::Shr => qsast::BinOp::Shr, + semast::BinOp::Sub => qsast::BinOp::Sub, + semast::BinOp::XorB => qsast::BinOp::XorB, + } + } +} diff --git a/compiler/qsc_qasm/src/compiler/error.rs b/compiler/qsc_qasm/src/compiler/error.rs new file mode 100644 index 0000000000..b0a70b160a --- /dev/null +++ b/compiler/qsc_qasm/src/compiler/error.rs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +use miette::Diagnostic; +use qsc_data_structures::span::Span; +use thiserror::Error; + +#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] +#[error(transparent)] +#[diagnostic(transparent)] +pub struct Error(pub CompilerErrorKind); + +/// Represents the kind of semantic error that occurred during compilation of a QASM file(s). +/// For the most part, these errors are fatal and prevent compilation and are +/// safety checks to ensure that the QASM code is valid. +#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] +pub enum CompilerErrorKind { + #[error("annotations only valid on def and gate statements")] + #[diagnostic(code("Qasm.Compiler.InvalidAnnotationTarget"))] + InvalidAnnotationTarget(#[label] Span), + #[error("gate expects {0} qubit arguments, but {1} were provided")] + #[diagnostic(code("Qasm.Compiler.InvalidNumberOfQubitArgs"))] + InvalidNumberOfQubitArgs(usize, usize, #[label] Span), + #[error("{0} are not supported")] + #[diagnostic(code("Qasm.Compiler.NotSupported"))] + NotSupported(String, #[label] Span), + #[error("Qiskit circuits must have output registers")] + #[diagnostic(code("Qasm.Compiler.QiskitEntryPointMissingOutput"))] + QiskitEntryPointMissingOutput(#[label] Span), + #[error("unexpected annotation: {0}")] + #[diagnostic(code("Qasm.Compiler.UnknownAnnotation"))] + UnknownAnnotation(String, #[label] Span), + #[error("this statement is not yet handled during OpenQASM 3 import: {0}")] + #[diagnostic(code("Qasm.Compiler.Unimplemented"))] + Unimplemented(String, #[label] Span), +} + +impl From for crate::Error { + fn from(val: Error) -> Self { + crate::Error(crate::ErrorKind::Compiler(val)) + } +} diff --git a/compiler/qsc_qasm/src/convert.rs b/compiler/qsc_qasm/src/convert.rs new file mode 100644 index 0000000000..b7ed2466d8 --- /dev/null +++ b/compiler/qsc_qasm/src/convert.rs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/// `i64` is 64 bits wide, but `f64`'s mantissa is only 53 bits wide +pub(crate) fn safe_i64_to_f64(value: i64) -> Option { + const MAX_EXACT_INT: i64 = 2i64.pow(f64::MANTISSA_DIGITS); + const MAX_EXACT_NEG_INT: i64 = -(2i64.pow(f64::MANTISSA_DIGITS)); + if (MAX_EXACT_NEG_INT..=MAX_EXACT_INT).contains(&value) { + #[allow(clippy::cast_precision_loss)] + Some(value as f64) + } else { + None + } +} + +pub(crate) fn safe_u64_to_f64(value: u64) -> Option { + const MAX_EXACT_UINT: u64 = 2u64.pow(f64::MANTISSA_DIGITS); + if value <= MAX_EXACT_UINT { + #[allow(clippy::cast_precision_loss)] + Some(value as f64) + } else { + None + } +} diff --git a/compiler/qsc_qasm/src/display_utils.rs b/compiler/qsc_qasm/src/display_utils.rs new file mode 100644 index 0000000000..0e3177cbb8 --- /dev/null +++ b/compiler/qsc_qasm/src/display_utils.rs @@ -0,0 +1,198 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::fmt::{self, Display, Write}; + +/// Takes a unicode buffer or stream and wraps it with +/// `indenter::Idented`. Which applies an indentation of 1 +/// each time you insert a new line. +fn with_indentation(f: &mut T) -> indenter::Indented<'_, T> +where + T: fmt::Write, +{ + let indent = indenter::indented(f); + set_indentation(indent, 1) +} + +/// Takes an `indenter::Idented` and changes its indentation level. +fn set_indentation(indent: indenter::Indented<'_, T>, level: usize) -> indenter::Indented<'_, T> +where + T: fmt::Write, +{ + match level { + 0 => indent.with_str(""), + 1 => indent.with_str(" "), + 2 => indent.with_str(" "), + 3 => indent.with_str(" "), + _ => unimplemented!("indentation level not supported"), + } +} + +/// Writes a list of elements to the given buffer or stream. +fn write_list<'write, 'itemref, 'item, T, I>(f: &'write mut impl Write, vals: I) -> fmt::Result +where + 'item: 'itemref, + T: Display + 'item, + I: IntoIterator, +{ + let mut iter = vals.into_iter().peekable(); + if iter.peek().is_none() { + write!(f, " ") + } else { + for elt in iter { + write!(f, "\n{elt}")?; + } + Ok(()) + } +} + +/// Writes a list of elements to the given buffer or stream +/// with an additional indentation level. +pub fn write_indented_list<'write, 'itemref, 'item, T, I>( + f: &'write mut impl Write, + vals: I, +) -> fmt::Result +where + 'item: 'itemref, + T: Display + 'item, + I: IntoIterator, +{ + let mut iter = vals.into_iter().peekable(); + if iter.peek().is_none() { + write!(f, " ") + } else { + let mut indent = with_indentation(f); + for elt in iter { + write!(indent, "\n{elt}")?; + } + Ok(()) + } +} + +/// Writes the name and span of a structure to the given buffer or stream. +pub fn write_header(f: &mut impl Write, name: &str, span: super::Span) -> fmt::Result { + write!(f, "{name} {span}:") +} + +/// Writes the name and span of a structure to the given buffer or stream. +/// Inserts a newline afterwards. +pub fn writeln_header(f: &mut impl Write, name: &str, span: super::Span) -> fmt::Result { + writeln!(f, "{name} {span}:") +} + +/// Writes a field of a structure to the given buffer +/// or stream with an additional indententation level. +pub fn write_field(f: &mut impl Write, field_name: &str, val: &T) -> fmt::Result { + let mut indent = with_indentation(f); + write!(indent, "{field_name}: {val}") +} + +/// Writes a field of a structure to the given buffer +/// or stream with an additional indententation level. +/// Inserts a newline afterwards. +pub fn writeln_field(f: &mut impl Write, field_name: &str, val: &T) -> fmt::Result { + write_field(f, field_name, val)?; + writeln!(f) +} + +/// Writes an optional field of a structure to the given buffer +/// or stream with an additional indententation level. +pub fn write_opt_field( + f: &mut impl Write, + field_name: &str, + opt_val: Option<&T>, +) -> fmt::Result { + if let Some(val) = opt_val { + write_field(f, field_name, val) + } else { + write_field(f, field_name, &"") + } +} + +/// Writes an optional field of a structure to the given buffer +/// or stream with an additional indententation level. +/// Inserts a newline afterwards. +pub fn writeln_opt_field( + f: &mut impl Write, + field_name: &str, + opt_val: Option<&T>, +) -> fmt::Result { + write_opt_field(f, field_name, opt_val)?; + writeln!(f) +} + +/// Writes a field of a structure to the given buffer +/// or stream with an additional indententation level. +/// The field must be an iterable. +pub fn write_list_field<'write, 'itemref, 'item, T, I>( + f: &mut impl Write, + field_name: &str, + vals: I, +) -> fmt::Result +where + 'item: 'itemref, + T: Display + 'item, + I: IntoIterator, +{ + let mut indent = with_indentation(f); + write!(indent, "{field_name}:")?; + let mut indent = set_indentation(indent, 2); + write_list(&mut indent, vals) +} + +/// Writes a field of a structure to the given buffer +/// or stream with an additional indententation level. +/// The field must be an iterable. +/// Inserts a newline afterwards. +pub fn writeln_list_field<'write, 'itemref, 'item, T, I>( + f: &mut impl Write, + field_name: &str, + vals: I, +) -> fmt::Result +where + 'item: 'itemref, + T: Display + 'item, + I: IntoIterator, +{ + write_list_field(f, field_name, vals)?; + writeln!(f) +} + +/// Writes an optional field of a structure to the given buffer +/// or stream with an additional indententation level. +/// The field must be an iterable. +pub fn write_opt_list_field<'write, 'itemref, 'item, T, I>( + f: &mut impl Write, + field_name: &str, + opt_vals: Option, +) -> fmt::Result +where + 'item: 'itemref, + T: Display + 'item, + I: IntoIterator, +{ + if let Some(vals) = opt_vals { + write_list_field(f, field_name, vals) + } else { + let mut indent = with_indentation(f); + write!(indent, "{field_name}: ") + } +} + +/// Writes an optional field of a structure to the given buffer +/// or stream with an additional indententation level. +/// The field must be an iterable. +/// Inserts a newline afterwards. +pub fn writeln_opt_list_field<'write, 'itemref, 'item, T, I>( + f: &mut impl Write, + field_name: &str, + opt_vals: Option, +) -> fmt::Result +where + 'item: 'itemref, + T: Display + 'item, + I: IntoIterator, +{ + write_opt_list_field(f, field_name, opt_vals)?; + writeln!(f) +} diff --git a/compiler/qsc_qasm/src/io.rs b/compiler/qsc_qasm/src/io.rs new file mode 100644 index 0000000000..18b49fc3e9 --- /dev/null +++ b/compiler/qsc_qasm/src/io.rs @@ -0,0 +1,202 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +mod error; +pub use error::Error; +pub use error::ErrorKind; + +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; + +use rustc_hash::FxHashMap; + +/// A trait for resolving include file paths to their contents. +/// This is used by the parser to resolve `include` directives. +/// Implementations of this trait can be provided to the parser +/// to customize how include files are resolved. +pub trait SourceResolver { + fn ctx(&mut self) -> &mut SourceResolverContext; + + fn resolve

(&mut self, path: P) -> miette::Result<(PathBuf, String), Error> + where + P: AsRef; +} + +pub struct IncludeGraphNode { + parent: Option, + children: Vec, +} + +#[derive(Default)] +pub struct SourceResolverContext { + /// A graph representation of the include chain. + include_graph: FxHashMap, + /// Path being resolved. + current_file: Option, +} + +impl SourceResolverContext { + pub fn check_include_errors(&mut self, path: &PathBuf) -> miette::Result<(), Error> { + // If the new path makes a cycle in the include graph, we return + // an error showing the cycle to the user. + if let Some(cycle) = self.cycle_made_by_including_path(path) { + return Err(Error(ErrorKind::CyclicInclude(cycle))); + } + + // If the new path doesn't make a cycle but it was already + // included before, we return a `MultipleInclude` + // error saying " was already included in ". + if let Some(parent_file) = self.path_was_already_included(path) { + return Err(Error(ErrorKind::MultipleInclude( + path.display().to_string(), + parent_file.display().to_string(), + ))); + } + + self.add_path_to_include_graph(path.clone()); + + Ok(()) + } + + /// Changes `current_path` to its parent in the `include_graph`. + pub fn pop_current_file(&mut self) { + let parent = self + .current_file + .as_ref() + .and_then(|file| self.include_graph.get(file).map(|node| node.parent.clone())) + .flatten(); + self.current_file = parent; + } + + /// If including the path makes a cycle, returns a vector of the paths + /// that make the cycle. Else, returns None. + /// + /// To check if adding `path` to the include graph creates a cycle we just + /// need to verify if path is an ancestor of the current file. + fn cycle_made_by_including_path(&self, path: &PathBuf) -> Option { + let mut current_file = self.current_file.as_ref(); + let mut paths = Vec::new(); + + while let Some(file) = current_file { + paths.push(file.clone()); + current_file = self.get_parent(file); + if file == path { + paths.reverse(); + paths.push(path.clone()); + return Some(Cycle { paths }); + } + } + + None + } + + /// Returns the file that included `path`. + /// Returns `None` if `path` is the "main" file. + fn get_parent(&self, path: &PathBuf) -> Option<&PathBuf> { + self.include_graph + .get(path) + .and_then(|node| node.parent.as_ref()) + } + + /// If the path was already included, returns the path of the file that + /// included it. Else, returns None. + fn path_was_already_included(&self, path: &PathBuf) -> Option { + // SAFETY: The call to expect should be unreachable, since the parent + // will only be None for the "main" file. But including the + // main file will trigger a cyclic include error before this + // function is called. + self.include_graph + .get(path) + .map(|node| node.parent.clone().expect("unreachable")) + } + + /// Adds `path` as a child of `current_path`, and then changes + /// the `current_path` to `path`. + fn add_path_to_include_graph(&mut self, path: PathBuf) { + // 1. Add path to the current file children. + self.current_file.as_ref().and_then(|file| { + self.include_graph + .get_mut(file) + .map(|node| node.children.push(path.clone())) + }); + + // 2. Add path to the include graph. + self.include_graph.insert( + path.clone(), + IncludeGraphNode { + parent: self.current_file.clone(), + children: Vec::new(), + }, + ); + + // 3. Update the current file. + self.current_file = Some(path); + } +} + +/// We use this struct to print a nice error message when we find a cycle. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Cycle { + paths: Vec, +} + +impl std::fmt::Display for Cycle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let parents = self.paths[0..(self.paths.len() - 1)].iter(); + let children = self.paths[1..].iter(); + + for (parent, child) in parents.zip(children) { + write!(f, "\n {} includes {}", parent.display(), child.display())?; + } + + Ok(()) + } +} + +/// A source resolver that resolves include files from an in-memory map. +/// This is useful for testing or environments in which file system access +/// is not available. +/// +/// This requires users to build up a map of include file paths to their +/// contents prior to parsing. +pub struct InMemorySourceResolver { + sources: FxHashMap, + ctx: SourceResolverContext, +} + +impl FromIterator<(Arc, Arc)> for InMemorySourceResolver { + fn from_iter, Arc)>>(iter: T) -> Self { + let mut map = FxHashMap::default(); + for (path, source) in iter { + map.insert(PathBuf::from(path.to_string()), source.to_string()); + } + + InMemorySourceResolver { + sources: map, + ctx: Default::default(), + } + } +} + +impl SourceResolver for InMemorySourceResolver { + fn ctx(&mut self) -> &mut SourceResolverContext { + &mut self.ctx + } + + fn resolve

(&mut self, path: P) -> miette::Result<(PathBuf, String), Error> + where + P: AsRef, + { + let path = path.as_ref(); + self.ctx().check_include_errors(&path.to_path_buf())?; + match self.sources.get(path) { + Some(source) => Ok((path.to_owned(), source.clone())), + None => Err(Error(ErrorKind::NotFound(format!( + "Could not resolve include file: {}", + path.display() + )))), + } + } +} diff --git a/compiler/qsc_qasm/src/io/error.rs b/compiler/qsc_qasm/src/io/error.rs new file mode 100644 index 0000000000..64f58f036c --- /dev/null +++ b/compiler/qsc_qasm/src/io/error.rs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use miette::Diagnostic; +use thiserror::Error; + +use super::Cycle; + +#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] +#[error(transparent)] +#[diagnostic(transparent)] +pub struct Error(pub ErrorKind); + +#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] +pub enum ErrorKind { + #[error("Not Found {0}")] + NotFound(String), + #[error("IO Error: {0}")] + IO(String), + #[error("{0} was already included in: {1}")] + MultipleInclude(String, String), + #[error("Cyclic include:{0}")] + CyclicInclude(Cycle), +} + +impl From for crate::Error { + fn from(val: Error) -> Self { + crate::Error(crate::ErrorKind::IO(val)) + } +} diff --git a/compiler/qsc_qasm/src/keyword.rs b/compiler/qsc_qasm/src/keyword.rs new file mode 100644 index 0000000000..fac326a415 --- /dev/null +++ b/compiler/qsc_qasm/src/keyword.rs @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use enum_iterator::Sequence; +use std::{ + fmt::{self, Display, Formatter}, + str::FromStr, +}; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Sequence)] +pub enum Keyword { + Barrier, + Box, + Break, + Cal, + Case, + Const, + Continue, + CReg, + Ctrl, + Def, + DefCal, + DefCalGrammar, + Default, + Delay, + Dim, + Else, + End, + Extern, + False, + For, + Gate, + If, + In, + Include, + Input, + Inv, + Let, + Measure, + Mutable, + NegCtrl, + OpenQASM, + Output, + Pow, + Pragma, + QReg, + Qubit, + Reset, + True, + ReadOnly, + Return, + Switch, + Void, + While, +} + +impl Keyword { + pub(super) fn as_str(self) -> &'static str { + match self { + Keyword::Barrier => "barrier", + Keyword::Box => "box", + Keyword::Break => "break", + Keyword::Cal => "cal", + Keyword::Case => "case", + Keyword::Const => "const", + Keyword::Continue => "continue", + Keyword::CReg => "creg", + Keyword::Ctrl => "ctrl", + Keyword::Def => "def", + Keyword::DefCal => "defcal", + Keyword::DefCalGrammar => "defcalgrammar", + Keyword::Default => "default", + Keyword::Delay => "delay", + Keyword::Dim => "#dim", + Keyword::Else => "else", + Keyword::End => "end", + Keyword::Extern => "extern", + Keyword::False => "false", + Keyword::For => "for", + Keyword::Gate => "gate", + Keyword::If => "if", + Keyword::In => "in", + Keyword::Include => "include", + Keyword::Input => "input", + Keyword::Inv => "inv", + Keyword::Let => "let", + Keyword::Measure => "measure", + Keyword::Mutable => "mutable", + Keyword::NegCtrl => "negctrl", + Keyword::OpenQASM => "OPENQASM", + Keyword::Output => "output", + Keyword::Pow => "pow", + Keyword::Pragma => "pragma", + Keyword::QReg => "qreg", + Keyword::Qubit => "qubit", + Keyword::Reset => "reset", + Keyword::True => "true", + Keyword::ReadOnly => "readonly", + Keyword::Return => "return", + Keyword::Switch => "switch", + Keyword::Void => "void", + Keyword::While => "while", + } + } +} + +impl Display for Keyword { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl FromStr for Keyword { + type Err = (); + + // This is a hot function. Use a match expression so that the Rust compiler + // can optimize the string comparisons better, and order the cases by + // frequency in Q# so that fewer comparisons are needed on average. + fn from_str(s: &str) -> Result { + match s { + "barrier" => Ok(Self::Barrier), + "box" => Ok(Self::Box), + "break" => Ok(Self::Break), + "cal" => Ok(Self::Cal), + "case" => Ok(Self::Case), + "const" => Ok(Self::Const), + "continue" => Ok(Self::Continue), + "creg" => Ok(Self::CReg), + "ctrl" => Ok(Self::Ctrl), + "def" => Ok(Self::Def), + "defcal" => Ok(Self::DefCal), + "defcalgrammar" => Ok(Self::DefCalGrammar), + "default" => Ok(Self::Default), + "delay" => Ok(Self::Delay), + "dim" => Ok(Self::Dim), + "else" => Ok(Self::Else), + "end" => Ok(Self::End), + "extern" => Ok(Self::Extern), + "false" => Ok(Self::False), + "for" => Ok(Self::For), + "gate" => Ok(Self::Gate), + "if" => Ok(Self::If), + "in" => Ok(Self::In), + "include" => Ok(Self::Include), + "input" => Ok(Self::Input), + "inv" => Ok(Self::Inv), + "let" => Ok(Self::Let), + "measure" => Ok(Self::Measure), + "mutable" => Ok(Self::Mutable), + "negctrl" => Ok(Self::NegCtrl), + "OPENQASM" => Ok(Self::OpenQASM), + "output" => Ok(Self::Output), + "pow" => Ok(Self::Pow), + "pragma" => Ok(Self::Pragma), + "qreg" => Ok(Self::QReg), + "qubit" => Ok(Self::Qubit), + "reset" => Ok(Self::Reset), + "true" => Ok(Self::True), + "readonly" => Ok(Self::ReadOnly), + "return" => Ok(Self::Return), + "switch" => Ok(Self::Switch), + "void" => Ok(Self::Void), + "while" => Ok(Self::While), + _ => Err(()), + } + } +} diff --git a/compiler/qsc_qasm/src/lex.rs b/compiler/qsc_qasm/src/lex.rs new file mode 100644 index 0000000000..16257a1ea0 --- /dev/null +++ b/compiler/qsc_qasm/src/lex.rs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#![allow(unused)] + +pub mod cooked; +pub mod raw; +use enum_iterator::Sequence; + +pub(super) use cooked::{ClosedBinOp, Error, Lexer, Token, TokenKind}; + +/// A delimiter token. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum Delim { + /// `{` or `}` + Brace, + /// `[` or `]` + Bracket, + /// `(` or `)` + Paren, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum Radix { + Binary, + Octal, + Decimal, + Hexadecimal, +} + +impl From for u32 { + fn from(value: Radix) -> Self { + match value { + Radix::Binary => 2, + Radix::Octal => 8, + Radix::Decimal => 10, + Radix::Hexadecimal => 16, + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum InterpolatedStart { + DollarQuote, + RBrace, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum InterpolatedEnding { + Quote, + LBrace, +} diff --git a/compiler/qsc_qasm/src/lex/cooked.rs b/compiler/qsc_qasm/src/lex/cooked.rs new file mode 100644 index 0000000000..5f0abbea92 --- /dev/null +++ b/compiler/qsc_qasm/src/lex/cooked.rs @@ -0,0 +1,760 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! The second lexing phase "cooks" a raw token stream, transforming them into tokens that directly +//! correspond to components in the `OpenQASM` grammar. Keywords are treated as identifiers, except `and` +//! and `or`, which are cooked into [`BinaryOperator`] so that `and=` and `or=` are lexed correctly. +//! +//! Whitespace and comment tokens are discarded; this means that cooked tokens are not necessarily +//! contiguous, so they include both a starting and ending byte offset. +//! +//! Tokens never contain substrings from the original input, but are simply labels that refer back +//! to regions in the input. Lexing never fails, but may produce error tokens. + +#[cfg(test)] +mod tests; + +use super::{ + raw::{self, Number, Single}, + Delim, Radix, +}; +use crate::keyword::Keyword; +use enum_iterator::Sequence; +use miette::Diagnostic; +use qsc_data_structures::span::Span; +use std::{ + fmt::{self, Display, Formatter}, + iter::Peekable, + str::FromStr, +}; +use thiserror::Error; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) struct Token { + pub(crate) kind: TokenKind, + pub(crate) span: Span, +} + +#[derive(Clone, Copy, Debug, Diagnostic, Eq, Error, PartialEq)] +pub enum Error { + #[error("expected {0} to complete {1}, found {2}")] + #[diagnostic(code("Qasm.Lex.Incomplete"))] + Incomplete(raw::TokenKind, TokenKind, raw::TokenKind, #[label] Span), + + #[error("expected {0} to complete {1}, found EOF")] + #[diagnostic(code("Qasm.Lex.IncompleteEof"))] + IncompleteEof(raw::TokenKind, TokenKind, #[label] Span), + + #[error("unterminated string literal")] + #[diagnostic(code("Qasm.Lex.UnterminatedString"))] + UnterminatedString(#[label] Span), + + #[error("string literal with an invalid escape sequence")] + #[diagnostic(code("Qasm.Lex.InvalidEscapeSequence"))] + InvalidEscapeSequence(#[label] Span), + + #[error("unrecognized character `{0}`")] + #[diagnostic(code("Qasm.Lex.UnknownChar"))] + Unknown(char, #[label] Span), +} + +impl Error { + pub(crate) fn with_offset(self, offset: u32) -> Self { + match self { + Self::Incomplete(expected, token, actual, span) => { + Self::Incomplete(expected, token, actual, span + offset) + } + Self::IncompleteEof(expected, token, span) => { + Self::IncompleteEof(expected, token, span + offset) + } + Self::UnterminatedString(span) => Self::UnterminatedString(span + offset), + Self::InvalidEscapeSequence(span) => Self::InvalidEscapeSequence(span + offset), + Self::Unknown(c, span) => Self::Unknown(c, span + offset), + } + } + + pub(crate) fn span(self) -> Span { + match self { + Error::Incomplete(_, _, _, s) + | Error::IncompleteEof(_, _, s) + | Error::UnterminatedString(s) + | Error::InvalidEscapeSequence(s) + | Error::Unknown(_, s) => s, + } + } +} + +/// A token kind. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum TokenKind { + /// `@DOT_SEPARETED_IDENTIFIER REST_OF_LINE` + /// Examples: + /// @qsharp.SimulatableIntrinsic + /// @qsharp.Config Base + Annotation, + /// `pragma REST_OF_LINE` + /// or + /// `#pragma REST_OF_LINE` + Pragma, + Keyword(Keyword), + Type(Type), + + // Builtin identifiers and operations + GPhase, + DurationOf, + + Literal(Literal), + + // Symbols + /// `{[(` + Open(Delim), + /// `}])` + Close(Delim), + + // Punctuation + /// `:` + Colon, + /// `;` + Semicolon, + /// `.` + Dot, + /// `,` + Comma, + /// `++` + PlusPlus, + /// `->` + Arrow, + /// `@` + At, + + // Operators, + ClosedBinOp(ClosedBinOp), + BinOpEq(ClosedBinOp), + ComparisonOp(ComparisonOp), + /// `=` + Eq, + /// `!` + Bang, + /// `~` + Tilde, + + Identifier, + HardwareQubit, + /// End of file. + Eof, +} + +impl Display for TokenKind { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + TokenKind::Annotation => write!(f, "annotation"), + TokenKind::Pragma => write!(f, "pragma"), + TokenKind::Keyword(keyword) => write!(f, "keyword `{keyword}`"), + TokenKind::Type(type_) => write!(f, "keyword `{type_}`"), + TokenKind::GPhase => write!(f, "gphase"), + TokenKind::DurationOf => write!(f, "durationof"), + TokenKind::Literal(literal) => write!(f, "literal `{literal}`"), + TokenKind::Open(Delim::Brace) => write!(f, "`{{`"), + TokenKind::Open(Delim::Bracket) => write!(f, "`[`"), + TokenKind::Open(Delim::Paren) => write!(f, "`(`"), + TokenKind::Close(Delim::Brace) => write!(f, "`}}`"), + TokenKind::Close(Delim::Bracket) => write!(f, "`]`"), + TokenKind::Close(Delim::Paren) => write!(f, "`)`"), + TokenKind::Colon => write!(f, "`:`"), + TokenKind::Semicolon => write!(f, "`;`"), + TokenKind::Dot => write!(f, "`.`"), + TokenKind::Comma => write!(f, "`,`"), + TokenKind::PlusPlus => write!(f, "`++`"), + TokenKind::Arrow => write!(f, "`->`"), + TokenKind::At => write!(f, "`@`"), + TokenKind::ClosedBinOp(op) => write!(f, "`{op}`"), + TokenKind::BinOpEq(op) => write!(f, "`{op}=`"), + TokenKind::ComparisonOp(op) => write!(f, "`{op}`"), + TokenKind::Eq => write!(f, "`=`"), + TokenKind::Bang => write!(f, "`!`"), + TokenKind::Tilde => write!(f, "`~`"), + TokenKind::Identifier => write!(f, "identifier"), + TokenKind::HardwareQubit => write!(f, "hardware bit"), + TokenKind::Eof => f.write_str("EOF"), + } + } +} + +impl From for TokenKind { + fn from(value: Number) -> Self { + match value { + Number::Float => Self::Literal(Literal::Float), + Number::Int(radix) => Self::Literal(Literal::Integer(radix)), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum Type { + Input, + Output, + Const, + Readonly, + Mutable, + + QReg, + Qubit, + + CReg, + Bool, + Bit, + Int, + UInt, + Float, + Angle, + Complex, + Array, + Void, + + Duration, + Stretch, +} + +impl Display for Type { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Type::Input => "input", + Type::Output => "output", + Type::Const => "const", + Type::Readonly => "readonly", + Type::Mutable => "mutable", + Type::QReg => "qreg", + Type::Qubit => "qubit", + Type::CReg => "creg", + Type::Bool => "bool", + Type::Bit => "bit", + Type::Int => "int", + Type::UInt => "uint", + Type::Float => "float", + Type::Angle => "angle", + Type::Complex => "complex", + Type::Array => "array", + Type::Void => "void", + Type::Duration => "duration", + Type::Stretch => "stretch", + }) + } +} + +impl FromStr for Type { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "input" => Ok(Type::Input), + "output" => Ok(Type::Output), + "const" => Ok(Type::Const), + "readonly" => Ok(Type::Readonly), + "mutable" => Ok(Type::Mutable), + "qreg" => Ok(Type::QReg), + "qubit" => Ok(Type::Qubit), + "creg" => Ok(Type::CReg), + "bool" => Ok(Type::Bool), + "bit" => Ok(Type::Bit), + "int" => Ok(Type::Int), + "uint" => Ok(Type::UInt), + "float" => Ok(Type::Float), + "angle" => Ok(Type::Angle), + "complex" => Ok(Type::Complex), + "array" => Ok(Type::Array), + "void" => Ok(Type::Void), + "duration" => Ok(Type::Duration), + "stretch" => Ok(Type::Stretch), + _ => Err(()), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum Literal { + Bitstring, + Float, + Imaginary, + Integer(Radix), + String, + Timing(TimingLiteralKind), +} + +impl Display for Literal { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Literal::Bitstring => "bitstring", + Literal::Float => "float", + Literal::Imaginary => "imaginary", + Literal::Integer(_) => "integer", + Literal::String => "string", + Literal::Timing(_) => "timing", + }) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum TimingLiteralKind { + /// Timing literal: Backend-dependent unit. + /// Equivalent to the duration of one waveform sample on the backend. + Dt, + /// Timing literal: Nanoseconds. + Ns, + /// Timing literal: Microseconds. + Us, + /// Timing literal: Milliseconds. + Ms, + /// Timing literal: Seconds. + S, +} + +/// A binary operator that returns the same type as the type of its first operand; in other words, +/// the domain of the first operand is closed under this operation. These are candidates for +/// compound assignment operators, like `+=`. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum ClosedBinOp { + /// `&` + Amp, + /// `&&` + AmpAmp, + /// `|` + Bar, + /// `||` + BarBar, + /// `^` + Caret, + /// `>>` + GtGt, + /// `<<` + LtLt, + /// `-` + Minus, + /// `%` + Percent, + /// `+` + Plus, + /// `/` + Slash, + /// `*` + Star, + /// `**` + StarStar, + // Note: Missing Tilde according to qasm3Lexer.g4 to be able to express ~= + // But this is this a bug in the official qasm lexer? +} + +impl Display for ClosedBinOp { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str(match self { + ClosedBinOp::Amp => "&", + ClosedBinOp::AmpAmp => "&&", + ClosedBinOp::Bar => "|", + ClosedBinOp::BarBar => "||", + ClosedBinOp::Caret => "^", + ClosedBinOp::GtGt => ">>", + ClosedBinOp::LtLt => "<<", + ClosedBinOp::Minus => "-", + ClosedBinOp::Percent => "%", + ClosedBinOp::Plus => "+", + ClosedBinOp::Slash => "/", + ClosedBinOp::Star => "*", + ClosedBinOp::StarStar => "**", + }) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum ComparisonOp { + /// `!=` + BangEq, + /// `==` + EqEq, + /// `>` + Gt, + /// `>=` + GtEq, + /// `<` + Lt, + /// `<=` + LtEq, +} + +impl Display for ComparisonOp { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(match self { + ComparisonOp::BangEq => "!=", + ComparisonOp::EqEq => "==", + ComparisonOp::Gt => ">", + ComparisonOp::GtEq => ">=", + ComparisonOp::Lt => "<", + ComparisonOp::LtEq => "<=", + }) + } +} + +pub(crate) struct Lexer<'a> { + input: &'a str, + len: u32, + + // This uses a `Peekable` iterator over the raw lexer, which allows for one token lookahead. + tokens: Peekable>, + + /// This flag is used to detect annotations at the + /// beginning of a file. Normally annotations are + /// detected because there is a Newline followed by an `@`, + /// but there is no newline at the beginning of a file. + beginning_of_file: bool, +} + +impl<'a> Lexer<'a> { + pub(crate) fn new(input: &'a str) -> Self { + Self { + input, + len: input + .len() + .try_into() + .expect("input length should fit into u32"), + tokens: raw::Lexer::new(input).peekable(), + beginning_of_file: true, + } + } + + fn offset(&mut self) -> u32 { + self.tokens.peek().map_or_else(|| self.len, |t| t.offset) + } + + fn next_if_eq_single(&mut self, single: Single) -> bool { + self.next_if_eq(raw::TokenKind::Single(single)) + } + + fn next_if_eq(&mut self, tok: raw::TokenKind) -> bool { + self.tokens.next_if(|t| t.kind == tok).is_some() + } + + fn expect_single(&mut self, single: Single, complete: TokenKind) -> Result<(), Error> { + self.expect(raw::TokenKind::Single(single), complete) + } + + fn expect(&mut self, tok: raw::TokenKind, complete: TokenKind) -> Result<(), Error> { + if self.next_if_eq(tok) { + Ok(()) + } else if let Some(&raw::Token { kind, offset }) = self.tokens.peek() { + let mut tokens = self.tokens.clone(); + let hi = tokens.nth(1).map_or_else(|| self.len, |t| t.offset); + let span = Span { lo: offset, hi }; + Err(Error::Incomplete(tok, complete, kind, span)) + } else { + let lo = self.len; + let span = Span { lo, hi: lo }; + Err(Error::IncompleteEof(tok, complete, span)) + } + } + + /// Returns the first token ahead of the cursor without consuming it. This operation is fast, + /// but if you know you want to consume the token if it matches, use [`next_if_eq`] instead. + fn first(&mut self) -> Option { + self.tokens.peek().map(|i| i.kind) + } + + /// Consumes the characters while they satisfy `f`. Returns the last character eaten, if any. + fn eat_while(&mut self, mut f: impl FnMut(raw::TokenKind) -> bool) -> Option { + let mut last_eaten: Option = None; + loop { + let t = self.tokens.next_if(|t| f(t.kind)); + if t.is_none() { + return last_eaten.map(|t| t.kind); + } + last_eaten = t; + } + } + + fn eat_to_end_of_line(&mut self) { + self.eat_while(|t| t != raw::TokenKind::Newline); + } + + /// Consumes a list of tokens zero or more times. + fn kleen_star(&mut self, tokens: &[raw::TokenKind], complete: TokenKind) -> Result<(), Error> { + let mut iter = tokens.iter(); + while self.next_if_eq(*(iter.next().expect("tokens should have at least one token"))) { + for token in iter { + self.expect(*token, complete)?; + } + iter = tokens.iter(); + } + Ok(()) + } + + #[allow(clippy::too_many_lines)] + fn cook(&mut self, token: &raw::Token) -> Result, Error> { + let kind = match token.kind { + raw::TokenKind::Bitstring { terminated: true } => { + Ok(Some(TokenKind::Literal(Literal::Bitstring))) + } + raw::TokenKind::Bitstring { terminated: false } => { + Err(Error::UnterminatedString(Span { + lo: token.offset, + hi: token.offset, + })) + } + raw::TokenKind::Comment(_) | raw::TokenKind::Whitespace => Ok(None), + raw::TokenKind::Newline => { + // AnnotationKeyword: '@' Identifier ('.' Identifier)* -> pushMode(EAT_TO_LINE_END); + self.next_if_eq(raw::TokenKind::Whitespace); + match self.tokens.peek() { + Some(token) if token.kind == raw::TokenKind::Single(Single::At) => { + let token = self.tokens.next().expect("self.tokens.peek() was Some(_)"); + let complete = TokenKind::Annotation; + self.expect(raw::TokenKind::Ident, complete); + self.eat_to_end_of_line(); + let kind = Some(complete); + return Ok(kind.map(|kind| { + let span = Span { + lo: token.offset, + hi: self.offset(), + }; + Token { kind, span } + })); + } + _ => Ok(None), + } + } + raw::TokenKind::Ident => { + let ident = &self.input[(token.offset as usize)..(self.offset() as usize)]; + let cooked_ident = Self::ident(ident); + match cooked_ident { + TokenKind::Keyword(Keyword::Pragma) => { + self.eat_to_end_of_line(); + Ok(Some(TokenKind::Pragma)) + } + _ => Ok(Some(cooked_ident)), + } + } + raw::TokenKind::HardwareQubit => Ok(Some(TokenKind::HardwareQubit)), + raw::TokenKind::LiteralFragment(_) => { + // if a literal fragment does not appear after a decimal + // or a float, treat it as an identifier. + Ok(Some(TokenKind::Identifier)) + } + raw::TokenKind::Number(number) => { + // after reading a decimal number or a float there could be a whitespace + // followed by a fragment, which will change the type of the literal. + let numeric_part_hi = self.offset(); + self.next_if_eq(raw::TokenKind::Whitespace); + + if let Some(raw::TokenKind::LiteralFragment(fragment)) = self.first() { + use self::Literal::{Imaginary, Timing}; + use TokenKind::Literal; + + // Consume the fragment. + self.next(); + + Ok(Some(match fragment { + raw::LiteralFragmentKind::Imag => Literal(Imaginary), + raw::LiteralFragmentKind::Dt => Literal(Timing(TimingLiteralKind::Dt)), + raw::LiteralFragmentKind::Ns => Literal(Timing(TimingLiteralKind::Ns)), + raw::LiteralFragmentKind::Us => Literal(Timing(TimingLiteralKind::Us)), + raw::LiteralFragmentKind::Ms => Literal(Timing(TimingLiteralKind::Ms)), + raw::LiteralFragmentKind::S => Literal(Timing(TimingLiteralKind::S)), + })) + } else { + let kind: TokenKind = number.into(); + let span = Span { + lo: token.offset, + hi: numeric_part_hi, + }; + return Ok(Some(Token { kind, span })); + } + } + raw::TokenKind::Single(Single::Sharp) => { + self.expect(raw::TokenKind::Ident, TokenKind::Identifier)?; + let ident = &self.input[(token.offset as usize + 1)..(self.offset() as usize)]; + match Self::ident(ident) { + TokenKind::Keyword(Keyword::Dim) => Ok(Some(TokenKind::Keyword(Keyword::Dim))), + TokenKind::Keyword(Keyword::Pragma) => { + self.eat_to_end_of_line(); + Ok(Some(TokenKind::Pragma)) + } + _ => { + let span = Span { + lo: token.offset, + hi: self.offset(), + }; + Err(Error::Incomplete( + raw::TokenKind::Ident, + TokenKind::Pragma, + raw::TokenKind::Ident, + span, + )) + } + } + } + raw::TokenKind::Single(single) => self.single(single).map(Some), + raw::TokenKind::String { terminated: true } => { + Ok(Some(TokenKind::Literal(Literal::String))) + } + raw::TokenKind::String { terminated: false } => Err(Error::UnterminatedString(Span { + lo: token.offset, + hi: token.offset, + })), + raw::TokenKind::Unknown => { + let c = self.input[(token.offset as usize)..] + .chars() + .next() + .expect("token offset should be the start of a character"); + let span = Span { + lo: token.offset, + hi: self.offset(), + }; + Err(Error::Unknown(c, span)) + } + }?; + + Ok(kind.map(|kind| { + let span = Span { + lo: token.offset, + hi: self.offset(), + }; + Token { kind, span } + })) + } + + #[allow(clippy::too_many_lines)] + fn single(&mut self, single: Single) -> Result { + match single { + Single::Amp => { + if self.next_if_eq_single(Single::Amp) { + Ok(TokenKind::ClosedBinOp(ClosedBinOp::AmpAmp)) + } else { + Ok(self.closed_bin_op(ClosedBinOp::Amp)) + } + } + Single::At => { + if self.beginning_of_file { + let complete = TokenKind::Annotation; + self.expect(raw::TokenKind::Ident, complete); + self.eat_to_end_of_line(); + Ok(complete) + } else { + Ok(TokenKind::At) + } + } + Single::Bang => { + if self.next_if_eq_single(Single::Eq) { + Ok(TokenKind::ComparisonOp(ComparisonOp::BangEq)) + } else { + Ok(TokenKind::Bang) + } + } + Single::Bar => { + if self.next_if_eq_single(Single::Bar) { + Ok(TokenKind::ClosedBinOp(ClosedBinOp::BarBar)) + } else { + Ok(self.closed_bin_op(ClosedBinOp::Bar)) + } + } + Single::Caret => Ok(self.closed_bin_op(ClosedBinOp::Caret)), + Single::Close(delim) => Ok(TokenKind::Close(delim)), + Single::Colon => Ok(TokenKind::Colon), + Single::Comma => Ok(TokenKind::Comma), + Single::Dot => Ok(TokenKind::Dot), + Single::Eq => { + if self.next_if_eq_single(Single::Eq) { + Ok(TokenKind::ComparisonOp(ComparisonOp::EqEq)) + } else { + Ok(TokenKind::Eq) + } + } + Single::Gt => { + if self.next_if_eq_single(Single::Eq) { + Ok(TokenKind::ComparisonOp(ComparisonOp::GtEq)) + } else if self.next_if_eq_single(Single::Gt) { + Ok(self.closed_bin_op(ClosedBinOp::GtGt)) + } else { + Ok(TokenKind::ComparisonOp(ComparisonOp::Gt)) + } + } + Single::Lt => { + if self.next_if_eq_single(Single::Eq) { + Ok(TokenKind::ComparisonOp(ComparisonOp::LtEq)) + } else if self.next_if_eq_single(Single::Lt) { + Ok(self.closed_bin_op(ClosedBinOp::LtLt)) + } else { + Ok(TokenKind::ComparisonOp(ComparisonOp::Lt)) + } + } + Single::Minus => { + if self.next_if_eq_single(Single::Gt) { + Ok(TokenKind::Arrow) + } else { + Ok(self.closed_bin_op(ClosedBinOp::Minus)) + } + } + Single::Open(delim) => Ok(TokenKind::Open(delim)), + Single::Percent => Ok(self.closed_bin_op(ClosedBinOp::Percent)), + Single::Plus => { + if self.next_if_eq_single(Single::Plus) { + Ok(TokenKind::PlusPlus) + } else { + Ok(self.closed_bin_op(ClosedBinOp::Plus)) + } + } + Single::Semi => Ok(TokenKind::Semicolon), + Single::Sharp => unreachable!(), + Single::Slash => Ok(self.closed_bin_op(ClosedBinOp::Slash)), + Single::Star => { + if self.next_if_eq_single(Single::Star) { + Ok(self.closed_bin_op(ClosedBinOp::StarStar)) + } else { + Ok(self.closed_bin_op(ClosedBinOp::Star)) + } + } + Single::Tilde => Ok(TokenKind::Tilde), + } + } + + fn closed_bin_op(&mut self, op: ClosedBinOp) -> TokenKind { + if self.next_if_eq_single(Single::Eq) { + TokenKind::BinOpEq(op) + } else { + TokenKind::ClosedBinOp(op) + } + } + + fn ident(ident: &str) -> TokenKind { + match ident { + "gphase" => TokenKind::GPhase, + "durationof" => TokenKind::DurationOf, + ident => { + if let Ok(keyword) = ident.parse::() { + TokenKind::Keyword(keyword) + } else if let Ok(type_) = ident.parse::() { + TokenKind::Type(type_) + } else { + TokenKind::Identifier + } + } + } + } +} + +impl Iterator for Lexer<'_> { + type Item = Result; + + fn next(&mut self) -> Option { + while let Some(token) = self.tokens.next() { + match self.cook(&token) { + Ok(None) => self.beginning_of_file = false, + Ok(Some(token)) => { + self.beginning_of_file = false; + return Some(Ok(token)); + } + Err(err) => { + self.beginning_of_file = false; + return Some(Err(err)); + } + } + } + + None + } +} diff --git a/compiler/qsc_qasm/src/lex/cooked/tests.rs b/compiler/qsc_qasm/src/lex/cooked/tests.rs new file mode 100644 index 0000000000..2716c3640e --- /dev/null +++ b/compiler/qsc_qasm/src/lex/cooked/tests.rs @@ -0,0 +1,1257 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use super::{Lexer, Token, TokenKind}; +use crate::lex::Delim; +use expect_test::{expect, Expect}; +use qsc_data_structures::span::Span; + +fn check(input: &str, expect: &Expect) { + let actual: Vec<_> = Lexer::new(input).collect(); + expect.assert_debug_eq(&actual); +} + +fn op_string(kind: TokenKind) -> Option { + match kind { + TokenKind::Close(Delim::Brace) => Some("}".to_string()), + TokenKind::Close(Delim::Bracket) => Some("]".to_string()), + TokenKind::Close(Delim::Paren) => Some(")".to_string()), + TokenKind::Colon => Some(":".to_string()), + TokenKind::Comma => Some(",".to_string()), + TokenKind::Dot => Some(".".to_string()), + TokenKind::Eq => Some("=".to_string()), + TokenKind::Bang => Some("!".to_string()), + TokenKind::Tilde => Some("~".to_string()), + TokenKind::Open(Delim::Brace) => Some("{".to_string()), + TokenKind::Open(Delim::Bracket) => Some("[".to_string()), + TokenKind::Open(Delim::Paren) => Some("(".to_string()), + TokenKind::PlusPlus => Some("++".to_string()), + TokenKind::Keyword(keyword) => Some(keyword.to_string()), + TokenKind::Type(type_) => Some(type_.to_string()), + TokenKind::GPhase => Some("gphase".to_string()), + TokenKind::DurationOf => Some("durationof".to_string()), + TokenKind::Semicolon => Some(";".to_string()), + TokenKind::Arrow => Some("->".to_string()), + TokenKind::At => Some("@".to_string()), + TokenKind::ClosedBinOp(op) => Some(op.to_string()), + TokenKind::BinOpEq(super::ClosedBinOp::AmpAmp | super::ClosedBinOp::BarBar) + | TokenKind::Literal(_) + | TokenKind::Annotation + | TokenKind::Pragma => None, + TokenKind::BinOpEq(op) => Some(format!("{op}=")), + TokenKind::ComparisonOp(op) => Some(op.to_string()), + TokenKind::Identifier => Some("foo".to_string()), + TokenKind::HardwareQubit => Some("$1".to_string()), + TokenKind::Eof => Some("EOF".to_string()), + } +} + +#[test] +#[ignore = "Need to talk through how to handle this"] +fn basic_ops() { + for kind in enum_iterator::all() { + let Some(input) = op_string(kind) else { + continue; + }; + let actual: Vec<_> = Lexer::new(&input).collect(); + let len = input + .len() + .try_into() + .expect("input length should fit into u32"); + assert_eq!( + actual, + vec![Ok(Token { + kind, + span: Span { lo: 0, hi: len } + }),] + ); + } +} + +#[test] +fn empty() { + check( + "", + &expect![[r#" + [] + "#]], + ); +} + +#[test] +fn amp() { + check( + "&", + &expect![[r#" + [ + Ok( + Token { + kind: ClosedBinOp( + Amp, + ), + span: Span { + lo: 0, + hi: 1, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn amp_amp() { + check( + "&&", + &expect![[r#" + [ + Ok( + Token { + kind: ClosedBinOp( + AmpAmp, + ), + span: Span { + lo: 0, + hi: 2, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn amp_plus() { + check( + "&+", + &expect![[r#" + [ + Ok( + Token { + kind: ClosedBinOp( + Amp, + ), + span: Span { + lo: 0, + hi: 1, + }, + }, + ), + Ok( + Token { + kind: ClosedBinOp( + Plus, + ), + span: Span { + lo: 1, + hi: 2, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn amp_multibyte() { + check( + "&🦀", + &expect![[r#" + [ + Ok( + Token { + kind: ClosedBinOp( + Amp, + ), + span: Span { + lo: 0, + hi: 1, + }, + }, + ), + Err( + Unknown( + '🦀', + Span { + lo: 1, + hi: 5, + }, + ), + ), + ] + "#]], + ); +} + +#[test] +fn amp_amp_amp_amp() { + check( + "&&&&", + &expect![[r#" + [ + Ok( + Token { + kind: ClosedBinOp( + AmpAmp, + ), + span: Span { + lo: 0, + hi: 2, + }, + }, + ), + Ok( + Token { + kind: ClosedBinOp( + AmpAmp, + ), + span: Span { + lo: 2, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn int() { + check( + "123", + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + Integer( + Decimal, + ), + ), + span: Span { + lo: 0, + hi: 3, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn negative_int() { + check( + "-123", + &expect![[r#" + [ + Ok( + Token { + kind: ClosedBinOp( + Minus, + ), + span: Span { + lo: 0, + hi: 1, + }, + }, + ), + Ok( + Token { + kind: Literal( + Integer( + Decimal, + ), + ), + span: Span { + lo: 1, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn positive_int() { + check( + "+123", + &expect![[r#" + [ + Ok( + Token { + kind: ClosedBinOp( + Plus, + ), + span: Span { + lo: 0, + hi: 1, + }, + }, + ), + Ok( + Token { + kind: Literal( + Integer( + Decimal, + ), + ), + span: Span { + lo: 1, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn imag() { + check( + "123im", + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + Imaginary, + ), + span: Span { + lo: 0, + hi: 5, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn imag_with_whitespace() { + check( + "123 im", + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + Imaginary, + ), + span: Span { + lo: 0, + hi: 6, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn imag_with_whitespace_semicolon() { + check( + "123 im;", + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + Imaginary, + ), + span: Span { + lo: 0, + hi: 6, + }, + }, + ), + Ok( + Token { + kind: Semicolon, + span: Span { + lo: 6, + hi: 7, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn negative_imag() { + check( + "-123im", + &expect![[r#" + [ + Ok( + Token { + kind: ClosedBinOp( + Minus, + ), + span: Span { + lo: 0, + hi: 1, + }, + }, + ), + Ok( + Token { + kind: Literal( + Imaginary, + ), + span: Span { + lo: 1, + hi: 6, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn positive_imag() { + check( + "+123im", + &expect![[r#" + [ + Ok( + Token { + kind: ClosedBinOp( + Plus, + ), + span: Span { + lo: 0, + hi: 1, + }, + }, + ), + Ok( + Token { + kind: Literal( + Imaginary, + ), + span: Span { + lo: 1, + hi: 6, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn float() { + check( + "1.23", + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + Float, + ), + span: Span { + lo: 0, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn negative_float() { + check( + "-1.23", + &expect![[r#" + [ + Ok( + Token { + kind: ClosedBinOp( + Minus, + ), + span: Span { + lo: 0, + hi: 1, + }, + }, + ), + Ok( + Token { + kind: Literal( + Float, + ), + span: Span { + lo: 1, + hi: 5, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn positive_float() { + check( + "+1.23", + &expect![[r#" + [ + Ok( + Token { + kind: ClosedBinOp( + Plus, + ), + span: Span { + lo: 0, + hi: 1, + }, + }, + ), + Ok( + Token { + kind: Literal( + Float, + ), + span: Span { + lo: 1, + hi: 5, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn leading_point() { + check( + ".1", + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + Float, + ), + span: Span { + lo: 0, + hi: 2, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn trailing_point() { + check( + "1.", + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + Float, + ), + span: Span { + lo: 0, + hi: 2, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn leading_zero_float() { + check( + "0.42", + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + Float, + ), + span: Span { + lo: 0, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn dot_float() { + check( + "..1", + &expect![[r#" + [ + Ok( + Token { + kind: Dot, + span: Span { + lo: 0, + hi: 1, + }, + }, + ), + Ok( + Token { + kind: Literal( + Float, + ), + span: Span { + lo: 1, + hi: 3, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn float_dot() { + check( + "1..", + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + Float, + ), + span: Span { + lo: 0, + hi: 2, + }, + }, + ), + Ok( + Token { + kind: Dot, + span: Span { + lo: 2, + hi: 3, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn dot_dot_int_dot_dot() { + check( + "..1..", + &expect![[r#" + [ + Ok( + Token { + kind: Dot, + span: Span { + lo: 0, + hi: 1, + }, + }, + ), + Ok( + Token { + kind: Literal( + Float, + ), + span: Span { + lo: 1, + hi: 3, + }, + }, + ), + Ok( + Token { + kind: Dot, + span: Span { + lo: 3, + hi: 4, + }, + }, + ), + Ok( + Token { + kind: Dot, + span: Span { + lo: 4, + hi: 5, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn two_points_with_leading() { + check( + ".1.2", + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + Float, + ), + span: Span { + lo: 0, + hi: 2, + }, + }, + ), + Ok( + Token { + kind: Literal( + Float, + ), + span: Span { + lo: 2, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn leading_point_exp() { + check( + ".1e2", + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + Float, + ), + span: Span { + lo: 0, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn ident() { + check( + "foo", + &expect![[r#" + [ + Ok( + Token { + kind: Identifier, + span: Span { + lo: 0, + hi: 3, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn string() { + check( + r#""string""#, + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + String, + ), + span: Span { + lo: 0, + hi: 8, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn string_empty() { + check( + r#""""#, + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + String, + ), + span: Span { + lo: 0, + hi: 2, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn string_missing_ending() { + check( + r#""Uh oh..."#, + &expect![[r#" + [ + Err( + UnterminatedString( + Span { + lo: 0, + hi: 0, + }, + ), + ), + ] + "#]], + ); +} + +#[test] +fn string_escape_quote() { + check( + r#""\"""#, + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + String, + ), + span: Span { + lo: 0, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn string_escape_single_quote() { + check( + r#""\'""#, + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + String, + ), + span: Span { + lo: 0, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn string_escape_newline() { + check( + r#""\n""#, + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + String, + ), + span: Span { + lo: 0, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn string_escape_return() { + check( + r#""\"""#, + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + String, + ), + span: Span { + lo: 0, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn string_escape_tab() { + check( + r#""\t""#, + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + String, + ), + span: Span { + lo: 0, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn string_invalid_escape() { + check( + r#""foo\abar" a"#, + &expect![[r#" + [ + Ok( + Token { + kind: Literal( + String, + ), + span: Span { + lo: 0, + hi: 10, + }, + }, + ), + Ok( + Token { + kind: Identifier, + span: Span { + lo: 11, + hi: 12, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn hardware_qubit() { + check( + r"$12", + &expect![[r#" + [ + Ok( + Token { + kind: HardwareQubit, + span: Span { + lo: 0, + hi: 3, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn unknown() { + check( + "##", + &expect![[r#" + [ + Err( + Incomplete( + Ident, + Identifier, + Single( + Sharp, + ), + Span { + lo: 1, + hi: 2, + }, + ), + ), + Err( + IncompleteEof( + Ident, + Identifier, + Span { + lo: 2, + hi: 2, + }, + ), + ), + ] + "#]], + ); +} + +#[test] +fn comment() { + check( + "//comment\nx", + &expect![[r#" + [ + Ok( + Token { + kind: Identifier, + span: Span { + lo: 10, + hi: 11, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn block_comment() { + check( + "/*comment*/x", + &expect![[r#" + [ + Ok( + Token { + kind: Identifier, + span: Span { + lo: 11, + hi: 12, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn comment_four_slashes() { + check( + "////comment\nx", + &expect![[r#" + [ + Ok( + Token { + kind: Identifier, + span: Span { + lo: 12, + hi: 13, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn annotation() { + check( + "@foo.bar 1 2 3;", + &expect![[r#" + [ + Ok( + Token { + kind: Annotation, + span: Span { + lo: 0, + hi: 15, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn pragma() { + check( + "pragma", + &expect![[r#" + [ + Ok( + Token { + kind: Pragma, + span: Span { + lo: 0, + hi: 6, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn pragma_ident() { + check( + "pragma foo", + &expect![[r#" + [ + Ok( + Token { + kind: Pragma, + span: Span { + lo: 0, + hi: 10, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn sharp_pragma() { + check( + "#pragma", + &expect![[r#" + [ + Ok( + Token { + kind: Pragma, + span: Span { + lo: 0, + hi: 7, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn sharp_pragma_ident() { + check( + "#pragma foo", + &expect![[r#" + [ + Ok( + Token { + kind: Pragma, + span: Span { + lo: 0, + hi: 11, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn dim() { + check( + "dim", + &expect![[r#" + [ + Ok( + Token { + kind: Keyword( + Dim, + ), + span: Span { + lo: 0, + hi: 3, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn sharp_dim() { + check( + "#dim", + &expect![[r#" + [ + Ok( + Token { + kind: Keyword( + Dim, + ), + span: Span { + lo: 0, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/lex/raw.rs b/compiler/qsc_qasm/src/lex/raw.rs new file mode 100644 index 0000000000..4e0be3285d --- /dev/null +++ b/compiler/qsc_qasm/src/lex/raw.rs @@ -0,0 +1,641 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! The first lexing phase transforms an input string into literals, single-character operators, +//! whitespace, and comments. Keywords are treated as identifiers. The raw token stream is +//! contiguous: there are no gaps between tokens. +//! +//! These are "raw" tokens because single-character operators don't always correspond to `OpenQASM` +//! operators, and whitespace and comments will later be discarded. Raw tokens are the ingredients +//! that are "cooked" into compound tokens before they can be consumed by the parser. +//! +//! Tokens never contain substrings from the original input, but are simply labels that refer back +//! to offsets in the input. Lexing never fails, but may produce unknown tokens. + +#[cfg(test)] +mod tests; + +use super::{Delim, Radix}; +use enum_iterator::Sequence; +use std::{ + fmt::{self, Display, Formatter, Write}, + iter::Peekable, + str::CharIndices, +}; + +/// An enum used internally by the raw lexer to signal whether +/// a token was partially parsed or if it wasn't parsed at all. +enum NumberLexError { + /// A number ending in an underscore. + EndsInUnderscore, + /// An incomplete binary, octal, or hex numer. + Incomplete, + /// The token wasn't parsed and no characters were consumed + /// when trying to parse the token. + None, +} + +/// A raw token. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Token { + /// The token kind. + pub kind: TokenKind, + /// The byte offset of the token starting character. + pub offset: u32, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum TokenKind { + Bitstring { terminated: bool }, + Comment(CommentKind), + HardwareQubit, + Ident, + LiteralFragment(LiteralFragmentKind), + Newline, + Number(Number), + Single(Single), + String { terminated: bool }, + Unknown, + Whitespace, +} + +impl Display for TokenKind { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + TokenKind::Bitstring { .. } => f.write_str("bitstring"), + TokenKind::Comment(CommentKind::Block) => f.write_str("block comment"), + TokenKind::Comment(CommentKind::Normal) => f.write_str("comment"), + TokenKind::HardwareQubit => f.write_str("hardware qubit"), + TokenKind::Ident => f.write_str("identifier"), + TokenKind::LiteralFragment(_) => f.write_str("literal fragment"), + TokenKind::Newline => f.write_str("newline"), + TokenKind::Number(Number::Float) => f.write_str("float"), + TokenKind::Number(Number::Int(_)) => f.write_str("integer"), + TokenKind::Single(single) => write!(f, "`{single}`"), + TokenKind::String { .. } => f.write_str("string"), + TokenKind::Unknown => f.write_str("unknown"), + TokenKind::Whitespace => f.write_str("whitespace"), + } + } +} + +/// A single-character operator token. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum Single { + /// `&` + Amp, + /// `@` + At, + /// `!` + Bang, + /// `|` + Bar, + /// `^` + Caret, + /// A closing delimiter. + Close(Delim), + /// `:` + Colon, + /// `,` + Comma, + /// `.` + Dot, + /// `=` + Eq, + /// `>` + Gt, + /// `<` + Lt, + /// `-` + Minus, + /// An opening delimiter. + Open(Delim), + /// `%` + Percent, + /// `+` + Plus, + /// `;` + Semi, + /// `#` Used for pragmas. + Sharp, + /// `/` + Slash, + /// `*` + Star, + /// `~` + Tilde, +} + +impl Display for Single { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_char(match self { + Single::Amp => '&', + Single::At => '@', + Single::Bang => '!', + Single::Bar => '|', + Single::Caret => '^', + Single::Close(Delim::Brace) => '}', + Single::Close(Delim::Bracket) => ']', + Single::Close(Delim::Paren) => ')', + Single::Colon => ':', + Single::Comma => ',', + Single::Dot => '.', + Single::Eq => '=', + Single::Gt => '>', + Single::Lt => '<', + Single::Minus => '-', + Single::Open(Delim::Brace) => '{', + Single::Open(Delim::Bracket) => '[', + Single::Open(Delim::Paren) => '(', + Single::Percent => '%', + Single::Plus => '+', + Single::Semi => ';', + Single::Sharp => '#', + Single::Slash => '/', + Single::Star => '*', + Single::Tilde => '~', + }) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum Number { + Float, + Int(Radix), +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub struct StringToken { + pub terminated: bool, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum CommentKind { + Block, + Normal, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum LiteralFragmentKind { + /// Imaginary literal fragment. + Imag, + /// Timing literal: Backend-dependent unit. + /// Equivalent to the duration of one waveform sample on the backend. + Dt, + /// Timing literal: Nanoseconds. + Ns, + /// Timing literal: Microseconds. + Us, + /// Timing literal: Milliseconds. + Ms, + /// Timing literal: Seconds. + S, +} + +#[derive(Clone)] +pub struct Lexer<'a> { + chars: Peekable>, + starting_offset: u32, +} + +impl<'a> Lexer<'a> { + #[must_use] + pub fn new(input: &'a str) -> Self { + Self { + chars: input.char_indices().peekable(), + starting_offset: 0, + } + } + + #[must_use] + pub fn new_with_starting_offset(input: &'a str, starting_offset: u32) -> Self { + Self { + chars: input.char_indices().peekable(), + starting_offset, + } + } + + fn next_if(&mut self, f: impl FnOnce(char) -> bool) -> bool { + self.chars.next_if(|i| f(i.1)).is_some() + } + + fn next_if_eq(&mut self, c: char) -> bool { + self.chars.next_if(|i| i.1 == c).is_some() + } + + /// Consumes the characters while they satisfy `f`. Returns the last character eaten, if any. + fn eat_while(&mut self, mut f: impl FnMut(char) -> bool) -> Option { + let mut last_eaten = None; + loop { + let c = self.chars.next_if(|i| f(i.1)); + if c.is_none() { + return last_eaten.map(|(_, c)| c); + } + last_eaten = c; + } + } + + /// Returns the first character ahead of the cursor without consuming it. This operation is fast, + /// but if you know you want to consume the character if it matches, use [`next_if_eq`] instead. + fn first(&mut self) -> Option { + self.chars.peek().map(|i| i.1) + } + + /// Returns the second character ahead of the cursor without consuming it. This is slower + /// than [`first`] and should be avoided when possible. + fn second(&self) -> Option { + let mut chars = self.chars.clone(); + chars.next(); + chars.next().map(|i| i.1) + } + + fn newline(&mut self, c: char) -> bool { + if is_newline(c) { + self.eat_while(is_newline); + true + } else { + false + } + } + + fn whitespace(&mut self, c: char) -> bool { + if is_whitespace(c) { + self.eat_while(is_whitespace); + true + } else { + false + } + } + + fn comment(&mut self, c: char) -> Option { + if c == '/' && self.next_if_eq('/') { + self.eat_while(|c| !is_newline(c)); + Some(CommentKind::Normal) + } else if c == '/' && self.next_if_eq('*') { + loop { + let (_, c) = self.chars.next()?; + if c == '*' && self.next_if_eq('/') { + return Some(CommentKind::Block); + } + } + } else { + None + } + } + + fn ident(&mut self, c: char) -> Option { + // Check for some special literal fragments. + // We need to check that the character following the fragment isn't an + // underscore or an alphanumeric character, else it is an identifier. + let first = self.first(); + if c == 's' + && (first.is_none() || first.is_some_and(|c1| c1 != '_' && !c1.is_alphanumeric())) + { + return Some(TokenKind::LiteralFragment(LiteralFragmentKind::S)); + } + + let second = self.second(); + if let Some(c1) = first { + if second.is_none() || second.is_some_and(|c1| c1 != '_' && !c1.is_alphanumeric()) { + let fragment = match (c, c1) { + ('i', 'm') => Some(TokenKind::LiteralFragment(LiteralFragmentKind::Imag)), + ('d', 't') => Some(TokenKind::LiteralFragment(LiteralFragmentKind::Dt)), + ('n', 's') => Some(TokenKind::LiteralFragment(LiteralFragmentKind::Ns)), + ('u' | 'µ', 's') => Some(TokenKind::LiteralFragment(LiteralFragmentKind::Us)), + ('m', 's') => Some(TokenKind::LiteralFragment(LiteralFragmentKind::Ms)), + _ => None, + }; + + if fragment.is_some() { + // Consume `first` before returning. + self.chars.next(); + return fragment; + } + } + } + + if c == '_' || c.is_alphabetic() { + self.eat_while(|c| c == '_' || c.is_alphanumeric()); + Some(TokenKind::Ident) + } else { + None + } + } + + fn number(&mut self, c: char) -> Result { + match self.leading_zero(c) { + Ok(number) => return Ok(number), + Err(NumberLexError::None) => (), + Err(err) => return Err(err), + } + + match self.leading_dot(c) { + Ok(number) => return Ok(number), + Err(NumberLexError::None) => (), + Err(err) => return Err(err), + } + + self.decimal_or_float(c) + } + + /// This rule allows us to differentiate a leading dot from a mid dot. + /// A float starting with a leading dot must contain at least one digit + /// after the dot. + fn leading_dot(&mut self, c: char) -> Result { + if c == '.' && self.first().is_some_and(|c| c.is_ascii_digit()) { + let (_, c1) = self.chars.next().expect("first.is_some_and() succeeded"); + self.decimal(c1)?; + match self.exp() { + Ok(()) | Err(NumberLexError::None) => Ok(Number::Float), + Err(err) => Err(err), + } + } else { + Err(NumberLexError::None) + } + } + + /// A float with a middle dot could optionally contain numbers after the dot. + /// This rule is necessary to differentiate from the floats with a leading dot, + /// which must have digits after the dot. + fn mid_dot(&mut self, c: char) -> Result { + if c == '.' { + match self.first() { + Some(c1) if c1.is_ascii_digit() => { + self.chars.next(); + match self.decimal(c1) { + Err(NumberLexError::EndsInUnderscore) => { + Err(NumberLexError::EndsInUnderscore) + } + Ok(_) | Err(NumberLexError::None) => match self.exp() { + Ok(()) | Err(NumberLexError::None) => Ok(Number::Float), + Err(_) => Err(NumberLexError::EndsInUnderscore), + }, + Err(NumberLexError::Incomplete) => unreachable!(), + } + } + Some('e' | 'E') => match self.exp() { + Ok(()) => Ok(Number::Float), + Err(NumberLexError::None) => unreachable!("we know there is an `e`"), + Err(NumberLexError::Incomplete) => { + unreachable!("this only applies when lexing binary, octal, or hex") + } + Err(err) => Err(err), + }, + None | Some(_) => Ok(Number::Float), + } + } else { + Err(NumberLexError::None) + } + } + + /// This rule parses binary, octal, hexadecimal numbers, or decimal/floats + /// if the next character isn't a radix specifier. + /// Numbers in Qasm aren't allowed to end in an underscore. + fn leading_zero(&mut self, c: char) -> Result { + if c != '0' { + return Err(NumberLexError::None); + } + + let radix = if self.next_if_eq('b') || self.next_if_eq('B') { + Radix::Binary + } else if self.next_if_eq('o') || self.next_if_eq('O') { + Radix::Octal + } else if self.next_if_eq('x') || self.next_if_eq('X') { + Radix::Hexadecimal + } else { + Radix::Decimal + }; + + let last_eaten = self.eat_while(|c| c == '_' || c.is_digit(radix.into())); + + match radix { + Radix::Binary | Radix::Octal | Radix::Hexadecimal => match last_eaten { + None => Err(NumberLexError::Incomplete), + Some('_') => Err(NumberLexError::EndsInUnderscore), + _ => Ok(Number::Int(radix)), + }, + Radix::Decimal => match self.first() { + Some(c1 @ '.') => { + self.chars.next(); + self.mid_dot(c1) + } + Some('e' | 'E') => match self.exp() { + Ok(()) => Ok(Number::Float), + Err(NumberLexError::None) => unreachable!(), + Err(_) => Err(NumberLexError::EndsInUnderscore), + }, + None | Some(_) => Ok(Number::Int(Radix::Decimal)), + }, + } + } + + /// This rule parses a decimal integer. + /// Numbers in QASM aren't allowed to end in an underscore. + /// The rule in the .g4 file is + /// `DecimalIntegerLiteral: ([0-9] '_'?)* [0-9];` + fn decimal(&mut self, c: char) -> Result { + if !c.is_ascii_digit() { + return Err(NumberLexError::None); + } + + let last_eaten = self.eat_while(|c| c == '_' || c.is_ascii_digit()); + + match last_eaten { + None if c == '_' => Err(NumberLexError::None), + Some('_') => Err(NumberLexError::EndsInUnderscore), + _ => Ok(Number::Int(Radix::Decimal)), + } + } + + /// This rule disambiguates between a decimal integer and a float with a + /// mid dot, like `12.3`. + fn decimal_or_float(&mut self, c: char) -> Result { + self.decimal(c)?; + match self.first() { + None => Ok(Number::Int(Radix::Decimal)), + Some(first @ '.') => { + self.chars.next(); + self.mid_dot(first) + } + _ => match self.exp() { + Ok(()) => Ok(Number::Float), + Err(NumberLexError::None) => Ok(Number::Int(Radix::Decimal)), + Err(NumberLexError::EndsInUnderscore) => Err(NumberLexError::EndsInUnderscore), + Err(NumberLexError::Incomplete) => unreachable!(), + }, + } + } + + /// Parses an exponent. Errors if the exponent is an invalid decimal. + /// The rule `decimal_or_float` uses the `LexError::None` variant of the error + /// to classify the token as an integer. + /// The `leading_dot` and `mid_dot` rules use the `LexError::None` variant to + /// classify the token as a float. + fn exp(&mut self) -> Result<(), NumberLexError> { + if self.next_if(|c| c == 'e' || c == 'E') { + // Optionally there could be a + or - sign. + self.chars.next_if(|i| i.1 == '+' || i.1 == '-'); + + // If we reached the end of file, we return a valid float. + let Some(first) = self.first() else { + return Ok(()); + }; + + // If the next character isn't a digit + // we issue an error without consuming it. + if first.is_ascii_digit() { + self.chars.next(); + match self.decimal(first) { + Ok(_) => Ok(()), + Err(NumberLexError::EndsInUnderscore) => Err(NumberLexError::EndsInUnderscore), + Err(NumberLexError::None | NumberLexError::Incomplete) => unreachable!(), + } + } else { + Ok(()) + } + } else { + Err(NumberLexError::None) + } + } + + /// Tries to parse a string or a bitstring. QASM strings can be enclosed + /// by double quotes or single quotes. Bitstrings can only be enclosed by + /// double quotes and contain 0s and 1s. + fn string(&mut self, string_start: char) -> Option { + if string_start != '"' && string_start != '\'' { + return None; + } + + if let Some(bitstring) = self.bitstring() { + // Try consuming the closing '"'. + self.chars.next(); + return Some(bitstring); + } + + let mut invalid_escape = false; + + while self.first().is_some_and(|c| c != string_start) { + self.eat_while(|c| c != '\\' && c != string_start); + if self.next_if_eq('\\') { + self.chars.next(); + } + } + + Some(TokenKind::String { + terminated: self.next_if_eq(string_start), + }) + } + + /// Parses the body of a bitstring. Bitstrings can only contain 0s and 1s. + /// Returns `None` if it finds an invalid character. + fn bitstring(&mut self) -> Option { + const STRING_START: char = '"'; + + // A bitstring must have at least one character. + if matches!(self.first(), None | Some(STRING_START)) { + return None; + } + + // A bitstring must end in a 0 or a 1. + if let Some('_') = self.eat_while(is_bitstring_char) { + return None; + } + + // Check the next character to determine if the bitstring is valid and closed, + // valid and open because we reached the EOF, or invalid, in which case we + // will treat it as a regular string. + match self.first() { + Some(STRING_START) => Some(TokenKind::Bitstring { terminated: true }), + None => Some(TokenKind::Bitstring { terminated: false }), + _ => None, + } + } + + /// Tries parsing a hardware qubit literal, consisting of a `$` sign followed by + /// ASCII digits. + fn hardware_qubit(&mut self, c: char) -> bool { + if c == '$' { + self.eat_while(|c| c.is_ascii_digit()).is_some() + } else { + false + } + } +} + +impl Iterator for Lexer<'_> { + type Item = Token; + + fn next(&mut self) -> Option { + let (offset, c) = self.chars.next()?; + let kind = if let Some(kind) = self.comment(c) { + TokenKind::Comment(kind) + } else if self.whitespace(c) { + TokenKind::Whitespace + } else if self.newline(c) { + TokenKind::Newline + } else if let Some(ident) = self.ident(c) { + ident + } else if self.hardware_qubit(c) { + TokenKind::HardwareQubit + } else { + match self.number(c) { + Ok(number) => TokenKind::Number(number), + Err(NumberLexError::EndsInUnderscore | NumberLexError::Incomplete) => { + TokenKind::Unknown + } + Err(NumberLexError::None) => self + .string(c) + .or_else(|| single(c).map(TokenKind::Single)) + .unwrap_or(TokenKind::Unknown), + } + }; + let offset: u32 = offset.try_into().expect("offset should fit into u32"); + Some(Token { + kind, + offset: offset + self.starting_offset, + }) + } +} + +fn single(c: char) -> Option { + match c { + '-' => Some(Single::Minus), + ',' => Some(Single::Comma), + ';' => Some(Single::Semi), + ':' => Some(Single::Colon), + '!' => Some(Single::Bang), + '.' => Some(Single::Dot), + '(' => Some(Single::Open(Delim::Paren)), + ')' => Some(Single::Close(Delim::Paren)), + '[' => Some(Single::Open(Delim::Bracket)), + ']' => Some(Single::Close(Delim::Bracket)), + '{' => Some(Single::Open(Delim::Brace)), + '}' => Some(Single::Close(Delim::Brace)), + '@' => Some(Single::At), + '*' => Some(Single::Star), + '/' => Some(Single::Slash), + '&' => Some(Single::Amp), + '%' => Some(Single::Percent), + '^' => Some(Single::Caret), + '+' => Some(Single::Plus), + '<' => Some(Single::Lt), + '=' => Some(Single::Eq), + '>' => Some(Single::Gt), + '|' => Some(Single::Bar), + '~' => Some(Single::Tilde), + '#' => Some(Single::Sharp), + _ => None, + } +} + +fn is_bitstring_char(c: char) -> bool { + c == '0' || c == '1' || c == '_' +} + +fn is_newline(c: char) -> bool { + c == '\n' || c == '\r' +} + +fn is_whitespace(c: char) -> bool { + !is_newline(c) && c.is_whitespace() +} diff --git a/compiler/qsc_qasm/src/lex/raw/tests.rs b/compiler/qsc_qasm/src/lex/raw/tests.rs new file mode 100644 index 0000000000..aee13c0ab1 --- /dev/null +++ b/compiler/qsc_qasm/src/lex/raw/tests.rs @@ -0,0 +1,1311 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use super::Lexer; +use crate::lex::raw::{Single, Token, TokenKind}; +use expect_test::{expect, Expect}; + +fn check(input: &str, expect: &Expect) { + let actual: Vec<_> = Lexer::new(input).collect(); + expect.assert_debug_eq(&actual); +} + +#[test] +fn singles() { + for single in enum_iterator::all::() { + let actual: Vec<_> = Lexer::new(&single.to_string()).collect(); + let kind = TokenKind::Single(single); + assert_eq!(actual, vec![Token { kind, offset: 0 }]); + } +} + +#[test] +fn braces() { + check( + "{}", + &expect![[r#" + [ + Token { + kind: Single( + Open( + Brace, + ), + ), + offset: 0, + }, + Token { + kind: Single( + Close( + Brace, + ), + ), + offset: 1, + }, + ] + "#]], + ); +} + +#[test] +fn negate() { + check( + "-x", + &expect![[r#" + [ + Token { + kind: Single( + Minus, + ), + offset: 0, + }, + Token { + kind: Ident, + offset: 1, + }, + ] + "#]], + ); +} + +#[test] +fn whitespace() { + check( + "- x", + &expect![[r#" + [ + Token { + kind: Single( + Minus, + ), + offset: 0, + }, + Token { + kind: Whitespace, + offset: 1, + }, + Token { + kind: Ident, + offset: 4, + }, + ] + "#]], + ); +} + +#[test] +fn comment() { + check( + "//comment\nx", + &expect![[r#" + [ + Token { + kind: Comment( + Normal, + ), + offset: 0, + }, + Token { + kind: Newline, + offset: 9, + }, + Token { + kind: Ident, + offset: 10, + }, + ] + "#]], + ); +} + +#[test] +fn block_comment() { + check( + "/* comment\n x */", + &expect![[r#" + [ + Token { + kind: Comment( + Block, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn comment_four_slashes() { + check( + "////comment\nx", + &expect![[r#" + [ + Token { + kind: Comment( + Normal, + ), + offset: 0, + }, + Token { + kind: Newline, + offset: 11, + }, + Token { + kind: Ident, + offset: 12, + }, + ] + "#]], + ); +} + +#[test] +fn string() { + check( + r#""string""#, + &expect![[r#" + [ + Token { + kind: String { + terminated: true, + }, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn string_missing_ending() { + check( + r#""string"#, + &expect![[r#" + [ + Token { + kind: String { + terminated: false, + }, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn string_escape_quote() { + check( + r#""\"""#, + &expect![[r#" + [ + Token { + kind: String { + terminated: true, + }, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn string_escape_single_quote() { + check( + r#""\'""#, + &expect![[r#" + [ + Token { + kind: String { + terminated: true, + }, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn string_escape_newline() { + check( + r#""\n""#, + &expect![[r#" + [ + Token { + kind: String { + terminated: true, + }, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn string_escape_return() { + check( + r#""\r""#, + &expect![[r#" + [ + Token { + kind: String { + terminated: true, + }, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn string_escape_tab() { + check( + r#""\t""#, + &expect![[r#" + [ + Token { + kind: String { + terminated: true, + }, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn string_invalid_escape() { + check( + r#""\s""#, + &expect![[r#" + [ + Token { + kind: String { + terminated: true, + }, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn binary() { + check( + "0b10110", + &expect![[r#" + [ + Token { + kind: Number( + Int( + Binary, + ), + ), + offset: 0, + }, + ] + "#]], + ); + check( + "0B10110", + &expect![[r#" + [ + Token { + kind: Number( + Int( + Binary, + ), + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn octal() { + check( + "0o70351", + &expect![[r#" + [ + Token { + kind: Number( + Int( + Octal, + ), + ), + offset: 0, + }, + ] + "#]], + ); + check( + "0O70351", + &expect![[r#" + [ + Token { + kind: Number( + Int( + Octal, + ), + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn decimal() { + check( + "123", + &expect![[r#" + [ + Token { + kind: Number( + Int( + Decimal, + ), + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn number_seps() { + check( + "123_456", + &expect![[r#" + [ + Token { + kind: Number( + Int( + Decimal, + ), + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn float_dot() { + check( + "0..", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + Token { + kind: Single( + Dot, + ), + offset: 2, + }, + ] + "#]], + ); +} + +#[test] +fn float_dot2() { + check( + ".0.", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + Token { + kind: Single( + Dot, + ), + offset: 2, + }, + ] + "#]], + ); +} + +#[test] +fn leading_dot_float() { + check( + ".0", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn dot_float() { + check( + "..0", + &expect![[r#" + [ + Token { + kind: Single( + Dot, + ), + offset: 0, + }, + Token { + kind: Number( + Float, + ), + offset: 1, + }, + ] + "#]], + ); +} + +#[test] +fn dot_dot_float() { + check( + "...0", + &expect![[r#" + [ + Token { + kind: Single( + Dot, + ), + offset: 0, + }, + Token { + kind: Single( + Dot, + ), + offset: 1, + }, + Token { + kind: Number( + Float, + ), + offset: 2, + }, + ] + "#]], + ); +} + +#[test] +fn hexadecimal() { + check( + "0x123abc", + &expect![[r#" + [ + Token { + kind: Number( + Int( + Hexadecimal, + ), + ), + offset: 0, + }, + ] + "#]], + ); + check( + "0X123abc", + &expect![[r#" + [ + Token { + kind: Number( + Int( + Hexadecimal, + ), + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn negative() { + check( + "-4", + &expect![[r#" + [ + Token { + kind: Single( + Minus, + ), + offset: 0, + }, + Token { + kind: Number( + Int( + Decimal, + ), + ), + offset: 1, + }, + ] + "#]], + ); +} + +#[test] +fn positive() { + check( + "+4", + &expect![[r#" + [ + Token { + kind: Single( + Plus, + ), + offset: 0, + }, + Token { + kind: Number( + Int( + Decimal, + ), + ), + offset: 1, + }, + ] + "#]], + ); +} + +#[test] +fn float() { + check( + "1.23", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn incomplete_exponent_lexed_as_float() { + check( + "1.e", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn leading_zero() { + check( + "0123", + &expect![[r#" + [ + Token { + kind: Number( + Int( + Decimal, + ), + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn leading_point() { + check( + ".123", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn trailing_point() { + check( + "123.", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn exp() { + check( + "1e23", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); + check( + "1E23", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn exp_plus() { + check( + "1e+23", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn exp_minus() { + check( + "1e-23", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn leading_point_exp() { + check( + ".25e2", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn incomplete_exp() { + check( + "0e", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); + check( + "1e", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn incomplete_exp2() { + check( + "0.e3_", + &expect![[r#" + [ + Token { + kind: Unknown, + offset: 0, + }, + ] + "#]], + ); + check( + "1e", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn leading_zero_point() { + check( + "0.25", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn leading_zero_zero_point() { + check( + "00.25", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn leading_zero_exp() { + check( + "0.25e2", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn unknown() { + check( + "##", + &expect![[r#" + [ + Token { + kind: Single( + Sharp, + ), + offset: 0, + }, + Token { + kind: Single( + Sharp, + ), + offset: 1, + }, + ] + "#]], + ); +} + +#[test] +fn float_hexadecimal() { + check( + "0x123.45", + &expect![[r#" + [ + Token { + kind: Number( + Int( + Hexadecimal, + ), + ), + offset: 0, + }, + Token { + kind: Number( + Float, + ), + offset: 5, + }, + ] + "#]], + ); +} + +#[test] +fn fragments() { + check( + "im dt ns us µs ms s", + &expect![[r#" + [ + Token { + kind: LiteralFragment( + Imag, + ), + offset: 0, + }, + Token { + kind: Whitespace, + offset: 2, + }, + Token { + kind: LiteralFragment( + Dt, + ), + offset: 3, + }, + Token { + kind: Whitespace, + offset: 5, + }, + Token { + kind: LiteralFragment( + Ns, + ), + offset: 6, + }, + Token { + kind: Whitespace, + offset: 8, + }, + Token { + kind: LiteralFragment( + Us, + ), + offset: 9, + }, + Token { + kind: Whitespace, + offset: 11, + }, + Token { + kind: LiteralFragment( + Us, + ), + offset: 12, + }, + Token { + kind: Whitespace, + offset: 15, + }, + Token { + kind: LiteralFragment( + Ms, + ), + offset: 16, + }, + Token { + kind: Whitespace, + offset: 18, + }, + Token { + kind: LiteralFragment( + S, + ), + offset: 19, + }, + ] + "#]], + ); +} + +#[test] +fn identifiers_with_fragment_prefixes() { + check( + "imx dtx nsx usx µsx msx sx", + &expect![[r#" + [ + Token { + kind: Ident, + offset: 0, + }, + Token { + kind: Whitespace, + offset: 3, + }, + Token { + kind: Ident, + offset: 4, + }, + Token { + kind: Whitespace, + offset: 7, + }, + Token { + kind: Ident, + offset: 8, + }, + Token { + kind: Whitespace, + offset: 11, + }, + Token { + kind: Ident, + offset: 12, + }, + Token { + kind: Whitespace, + offset: 15, + }, + Token { + kind: Ident, + offset: 16, + }, + Token { + kind: Whitespace, + offset: 20, + }, + Token { + kind: Ident, + offset: 21, + }, + Token { + kind: Whitespace, + offset: 24, + }, + Token { + kind: Ident, + offset: 25, + }, + ] + "#]], + ); +} + +#[test] +fn leading_underscores_digit() { + check( + "___3", + &expect![[r#" + [ + Token { + kind: Ident, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn leading_underscores_ident_dot() { + check( + "___3.", + &expect![[r#" + [ + Token { + kind: Ident, + offset: 0, + }, + Token { + kind: Single( + Dot, + ), + offset: 4, + }, + ] + "#]], + ); +} + +#[test] +fn leading_underscores_binary() { + check( + "___0b11", + &expect![[r#" + [ + Token { + kind: Ident, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn leading_underscores_binary_extended() { + check( + "___0b11abc", + &expect![[r#" + [ + Token { + kind: Ident, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn leading_underscores_identifier() { + check( + "___abc", + &expect![[r#" + [ + Token { + kind: Ident, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn hardware_qubit() { + check( + "$12", + &expect![[r#" + [ + Token { + kind: HardwareQubit, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn hardware_qubit_dot() { + check( + "$2.", + &expect![[r#" + [ + Token { + kind: HardwareQubit, + offset: 0, + }, + Token { + kind: Single( + Dot, + ), + offset: 2, + }, + ] + "#]], + ); +} + +#[test] +fn incomplete_hardware_qubit() { + check( + "$", + &expect![[r#" + [ + Token { + kind: Unknown, + offset: 0, + }, + ] + "#]], + ); +} + +#[test] +fn incomplete_hardware_qubit_identifier() { + check( + "$a", + &expect![[r#" + [ + Token { + kind: Unknown, + offset: 0, + }, + Token { + kind: Ident, + offset: 1, + }, + ] + "#]], + ); +} + +#[test] +fn incomplete_hardware_qubit_float() { + check( + "$.2", + &expect![[r#" + [ + Token { + kind: Unknown, + offset: 0, + }, + Token { + kind: Number( + Float, + ), + offset: 1, + }, + ] + "#]], + ); +} + +#[test] +fn hardware_qubit_with_underscore_at_end() { + check( + "$12_", + &expect![[r#" + [ + Token { + kind: HardwareQubit, + offset: 0, + }, + Token { + kind: Ident, + offset: 3, + }, + ] + "#]], + ); +} + +#[test] +fn hardware_qubit_with_underscore_in_the_middle() { + check( + "$12_3", + &expect![[r#" + [ + Token { + kind: HardwareQubit, + offset: 0, + }, + Token { + kind: Ident, + offset: 3, + }, + ] + "#]], + ); +} + +#[test] +fn decimal_space_imag_semicolon() { + check( + "10 im;", + &expect![[r#" + [ + Token { + kind: Number( + Int( + Decimal, + ), + ), + offset: 0, + }, + Token { + kind: Whitespace, + offset: 2, + }, + Token { + kind: LiteralFragment( + Imag, + ), + offset: 4, + }, + Token { + kind: Single( + Semi, + ), + offset: 6, + }, + ] + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/lib.rs b/compiler/qsc_qasm/src/lib.rs new file mode 100644 index 0000000000..65ecefce82 --- /dev/null +++ b/compiler/qsc_qasm/src/lib.rs @@ -0,0 +1,307 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +mod ast_builder; +mod compiler; +mod stdlib; +pub use compiler::compile_to_qsharp_ast_with_config; +pub use stdlib::package_store_with_qasm; +mod convert; +pub mod display_utils; +pub mod io; +mod keyword; +mod lex; +pub mod parser; +pub mod semantic; +mod types; + +#[cfg(test)] +pub(crate) mod tests; + +use std::{fmt::Write, sync::Arc}; + +use miette::Diagnostic; +use qsc_ast::ast::Package; +use qsc_data_structures::span::Span; +use qsc_frontend::{compile::SourceMap, error::WithSource}; +use thiserror::Error; + +#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] +#[diagnostic(transparent)] +#[error(transparent)] +pub struct Error(pub ErrorKind); + +impl Error { + #[must_use] + pub fn is_syntax_error(&self) -> bool { + matches!(self.0, ErrorKind::Parser(..)) + } + + #[must_use] + pub fn is_semantic_error(&self) -> bool { + matches!(self.0, ErrorKind::Semantic(..)) + } +} + +/// Represents the kind of error that occurred during compilation of a QASM file(s). +/// The errors fall into a few categories: +/// - Unimplemented features +/// - Not supported features +/// - Parsing errors (converted from the parser) +/// - Semantic errors +/// - IO errors +#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] +#[error(transparent)] +pub enum ErrorKind { + #[error(transparent)] + #[diagnostic(transparent)] + Compiler(#[from] crate::compiler::error::Error), + #[error(transparent)] + #[diagnostic(transparent)] + IO(#[from] crate::io::Error), + #[error(transparent)] + #[diagnostic(transparent)] + Parser(#[from] crate::parser::Error), + #[error(transparent)] + #[diagnostic(transparent)] + Semantic(#[from] crate::semantic::Error), + #[error(transparent)] + #[diagnostic(transparent)] + ConstEval(#[from] crate::semantic::const_eval::ConstEvalError), +} + +/// Qubit semantics differ between Q# and Qiskit. This enum is used to +/// specify which semantics to use when compiling QASM to Q#. +/// +/// Q# requires qubits to be in the 0 state before and after use. +/// Qiskit makes no assumptions about the state of qubits before or after use. +/// +/// During compliation, if Qiskit semantics are used, the compiler will insert +/// calls to create qubits instead of `use` bindings. This means that later +/// compiler passes won't generate the Q# code that would check the qubits. +/// +/// If Q# semantics are used, the compiler will insert `use` bindings. +/// +/// The Qiskit semantics can also be useful if we ever want to do state +/// vector simulation as it will allow us to get the simulator state at +/// the end of the program. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum QubitSemantics { + QSharp, + Qiskit, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CompilerConfig { + pub qubit_semantics: QubitSemantics, + pub output_semantics: OutputSemantics, + pub program_ty: ProgramType, + operation_name: Option>, + namespace: Option>, +} + +impl CompilerConfig { + #[must_use] + pub fn new( + qubit_semantics: QubitSemantics, + output_semantics: OutputSemantics, + program_ty: ProgramType, + operation_name: Option>, + namespace: Option>, + ) -> Self { + Self { + qubit_semantics, + output_semantics, + program_ty, + operation_name, + namespace, + } + } + + fn operation_name(&self) -> Arc { + self.operation_name + .clone() + .unwrap_or_else(|| Arc::from("program")) + } + + fn namespace(&self) -> Arc { + self.namespace + .clone() + .unwrap_or_else(|| Arc::from("qasm_import")) + } +} + +impl Default for CompilerConfig { + fn default() -> Self { + Self { + qubit_semantics: QubitSemantics::Qiskit, + output_semantics: OutputSemantics::Qiskit, + program_ty: ProgramType::Fragments, + operation_name: None, + namespace: None, + } + } +} + +/// Represents the type of compilation output to create +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ProgramType { + /// Creates an operation in a namespace as if the program is a standalone + /// file. Inputs are lifted to the operation params. Output are lifted to + /// the operation return type. The operation is marked as `@EntryPoint` + /// as long as there are no input parameters. + File, + /// Programs are compiled to a standalone function. Inputs are lifted to + /// the operation params. Output are lifted to the operation return type. + Operation, + /// Creates a list of statements from the program. This is useful for + /// interactive environments where the program is a list of statements + /// imported into the current scope. + /// This is also useful for testing individual statements compilation. + Fragments, +} + +/// Represents the signature of an operation. +/// This is used to create a function signature for the +/// operation that is created from the QASM source code. +/// This is the human readable form of the operation. +pub struct OperationSignature { + pub name: String, + pub ns: Option, + pub input: Vec<(String, String)>, + pub output: String, +} + +impl OperationSignature { + /// Creates a human readable operation signature of the form: + /// `ns.name(input)` + /// which is required to call the operation from other code. + #[must_use] + pub fn create_entry_expr_from_params>(&self, params: S) -> String { + let mut expr = String::new(); + if let Some(ns) = &self.ns { + write!(expr, "{ns}.").unwrap(); + } + write!(expr, "{}(", self.name).unwrap(); + write!(expr, "{}", params.as_ref()).unwrap(); + write!(expr, ")").unwrap(); + + expr + } + + /// Renders the input parameters as a string of comma separated + /// pairs. + #[must_use] + pub fn input_params(&self) -> String { + let mut expr = String::new(); + for (i, (name, ty)) in self.input.iter().enumerate() { + if i > 0 { + write!(expr, ", ").unwrap(); + } + write!(expr, "{name}: {ty}").unwrap(); + } + expr + } +} + +impl std::fmt::Display for OperationSignature { + /// Renders the operation signature as a human readable string. + /// The signature is of the form: + /// `ns.name(input) -> output` + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(ns) = &self.ns { + write!(f, "{ns}.")?; + } + write!(f, "{}(", self.name)?; + for (i, (name, ty)) in self.input.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{name}: {ty}")?; + } + write!(f, ") -> {}", self.output) + } +} + +/// A unit of compilation for QASM source code. +/// This is the result of parsing and compiling a QASM source file. +pub struct QasmCompileUnit { + /// Source map created from the accumulated source files, + source_map: SourceMap, + /// Semantic errors encountered during compilation. + /// These are always fatal errors that prevent compilation. + errors: Vec>, + /// The compiled AST package, if compilation was successful. + /// There is no guarantee that this package is valid unless + /// there are no errors. + package: Option, + /// The signature of the operation created from the QASM source code. + /// None if the program type is `ProgramType::Fragments`. + signature: Option, +} + +/// Represents a QASM compilation unit. +/// This is the result of parsing and compiling a QASM source file. +/// The result contains the source map, errors, and the compiled package. +/// The package is only valid if there are no errors. +impl QasmCompileUnit { + #[must_use] + pub fn new( + source_map: SourceMap, + errors: Vec>, + package: Option, + signature: Option, + ) -> Self { + Self { + source_map, + errors, + package, + signature, + } + } + + /// Returns true if there are errors in the compilation unit. + #[must_use] + pub fn has_errors(&self) -> bool { + !self.errors.is_empty() + } + + /// Returns a list of errors in the compilation unit. + #[must_use] + pub fn errors(&self) -> Vec> { + self.errors.clone() + } + + /// Deconstructs the compilation unit into its owned parts. + #[must_use] + pub fn into_tuple( + self, + ) -> ( + SourceMap, + Vec>, + Option, + Option, + ) { + (self.source_map, self.errors, self.package, self.signature) + } +} + +/// Represents the output semantics of the compilation. +/// Each has implications on the output of the compilation +/// and the semantic checks that are performed. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OutputSemantics { + /// The output is in Qiskit format meaning that the output + /// is all of the classical registers, in reverse order + /// in which they were added to the circuit with each + /// bit within each register in reverse order. + Qiskit, + /// [OpenQASM 3 has two output modes](https://openqasm.com/language/directives.html#input-output) + /// - If the programmer provides one or more `output` declarations, then + /// variables described as outputs will be returned as output. + /// The spec make no mention of endianness or order of the output. + /// - Otherwise, assume all of the declared variables are returned as output. + OpenQasm, + /// No output semantics are applied. The entry point returns `Unit`. + ResourceEstimation, +} diff --git a/compiler/qsc_qasm/src/parser.rs b/compiler/qsc_qasm/src/parser.rs new file mode 100644 index 0000000000..fb1225d34a --- /dev/null +++ b/compiler/qsc_qasm/src/parser.rs @@ -0,0 +1,321 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +pub mod ast; +use crate::io::SourceResolver; +use ast::{Program, StmtKind}; +use mut_visit::MutVisitor; +use qsc_data_structures::span::Span; +use qsc_frontend::compile::SourceMap; +use qsc_frontend::error::WithSource; +use scan::ParserContext; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +#[cfg(test)] +pub(crate) mod tests; + +pub mod completion; +mod error; +pub use error::Error; +mod expr; +mod mut_visit; +mod prgm; +mod prim; +mod scan; +mod stmt; + +struct Offsetter(pub(super) u32); + +impl MutVisitor for Offsetter { + fn visit_span(&mut self, span: &mut Span) { + span.lo += self.0; + span.hi += self.0; + } +} + +pub struct QasmParseResult { + pub source: QasmSource, + pub source_map: SourceMap, +} + +impl QasmParseResult { + #[must_use] + pub fn new(source: QasmSource) -> QasmParseResult { + let source_map = create_source_map(&source); + let mut source = source; + update_offsets(&source_map, &mut source); + QasmParseResult { source, source_map } + } + + #[must_use] + pub fn has_errors(&self) -> bool { + self.source.has_errors() + } + + pub fn all_errors(&self) -> Vec> { + let mut self_errors = self.errors(); + let include_errors = self + .source + .includes() + .iter() + .flat_map(QasmSource::all_errors) + .map(|e| self.map_error(e)) + .collect::>(); + + self_errors.extend(include_errors); + self_errors + } + + #[must_use] + pub fn errors(&self) -> Vec> { + self.source + .errors() + .iter() + .map(|e| self.map_error(e.clone())) + .collect::>() + } + + fn map_error(&self, error: Error) -> WithSource { + WithSource::from_map( + &self.source_map, + crate::Error(crate::ErrorKind::Parser(error)), + ) + } +} + +/// all spans and errors spans are relative to the start of the file +/// We need to update the spans based on the offset of the file in the source map. +/// We have to do this after a full parse as we don't know what files will be loaded +/// until we have parsed all the includes. +fn update_offsets(source_map: &SourceMap, source: &mut QasmSource) { + let source_file = source_map.find_by_name(&source.path().display().to_string()); + let offset = source_file.map_or(0, |source| source.offset); + // Update the errors' offset + source + .errors + .iter_mut() + .for_each(|e| *e = e.clone().with_offset(offset)); + // Update the program's spans with the offset + let mut offsetter = Offsetter(offset); + offsetter.visit_program(&mut source.program); + + // Recursively update the includes, their programs, and errors + for include in source.includes_mut() { + update_offsets(source_map, include); + } +} + +/// Parse a QASM file and return the parse result. +/// This function will resolve includes using the provided resolver. +/// If an include file cannot be resolved, an error will be returned. +/// If a file is included recursively, a stack overflow occurs. +pub fn parse_source(source: S, path: P, resolver: &mut R) -> QasmParseResult +where + S: AsRef, + P: AsRef, + R: SourceResolver, +{ + let res = parse_qasm_source(source, path, resolver); + QasmParseResult::new(res) +} + +/// Creates a Q# source map from a QASM parse output. The `QasmSource` +/// has all of the recursive includes resolved with their own source +/// and parse results. +fn create_source_map(source: &QasmSource) -> SourceMap { + let mut files: Vec<(Arc, Arc)> = Vec::new(); + collect_source_files(source, &mut files); + SourceMap::new(files, None) +} + +/// Recursively collect all source files from the includes +fn collect_source_files(source: &QasmSource, files: &mut Vec<(Arc, Arc)>) { + files.push(( + Arc::from(source.path().to_string_lossy().to_string()), + Arc::from(source.source()), + )); + // Collect all source files from the includes, this + // begins the recursive process of collecting all source files. + for include in source.includes() { + collect_source_files(include, files); + } +} + +/// Represents a QASM source file that has been parsed. +#[derive(Clone, Debug)] +pub struct QasmSource { + /// The path to the source file. This is used for error reporting. + /// This path is just a name, it does not have to exist on disk. + path: PathBuf, + /// The source code of the file. + source: Arc, + /// The parsed AST of the source file or any parse errors. + program: Program, + /// Any parse errors that occurred. + errors: Vec, + /// Any included files that were resolved. + /// Note that this is a recursive structure. + included: Vec, +} + +impl QasmSource { + pub fn new, P: AsRef>( + source: T, + file_path: P, + program: Program, + errors: Vec, + included: Vec, + ) -> QasmSource { + QasmSource { + path: file_path.as_ref().to_owned(), + source: source.as_ref().into(), + program, + errors, + included, + } + } + + #[must_use] + pub fn has_errors(&self) -> bool { + if !self.errors().is_empty() { + return true; + } + self.includes().iter().any(QasmSource::has_errors) + } + + #[must_use] + pub fn all_errors(&self) -> Vec { + let mut self_errors = self.errors(); + let include_errors = self.includes().iter().flat_map(QasmSource::all_errors); + self_errors.extend(include_errors); + self_errors + } + + #[must_use] + pub fn includes(&self) -> &Vec { + self.included.as_ref() + } + + #[must_use] + pub fn includes_mut(&mut self) -> &mut Vec { + self.included.as_mut() + } + + #[must_use] + pub fn program(&self) -> &Program { + &self.program + } + + #[must_use] + pub fn path(&self) -> PathBuf { + self.path.clone() + } + + #[must_use] + pub fn errors(&self) -> Vec { + self.errors.clone() + } + + #[must_use] + pub fn source(&self) -> &str { + self.source.as_ref() + } +} + +/// Parse a QASM file and return the parse result using the provided resolver. +/// Returns `Err` if the resolver cannot resolve the file. +/// Returns `Ok` otherwise. Any parse errors will be included in the result. +/// +/// This function is the start of a recursive process that will resolve all +/// includes in the QASM file. Any includes are parsed as if their contents +/// were defined where the include statement is. +fn parse_qasm_file(path: P, resolver: &mut R) -> QasmSource +where + P: AsRef, + R: SourceResolver, +{ + match resolver.resolve(&path) { + Ok((path, source)) => { + let parse_result = parse_qasm_source(source, path, resolver); + + // Once we finish parsing the source, we pop the file from the + // resolver. This is needed to keep track of multiple includes + // and cyclic includes. + resolver.ctx().pop_current_file(); + + parse_result + } + Err(e) => { + let error = crate::parser::error::ErrorKind::IO(e); + let error = crate::parser::Error(error, None); + QasmSource { + path: path.as_ref().to_owned(), + source: Default::default(), + program: Program { + span: Span::default(), + statements: vec![].into_boxed_slice(), + version: None, + }, + errors: vec![error], + included: vec![], + } + } + } +} + +fn parse_qasm_source(source: S, path: P, resolver: &mut R) -> QasmSource +where + S: AsRef, + P: AsRef, + R: SourceResolver, +{ + let (program, errors, includes) = parse_source_and_includes(source.as_ref(), resolver); + QasmSource::new(source, path, program, errors, includes) +} + +fn parse_source_and_includes, R>( + source: P, + resolver: &mut R, +) -> (Program, Vec, Vec) +where + R: SourceResolver, +{ + let (program, errors) = parse(source.as_ref()); + let included = parse_includes(&program, resolver); + (program, errors, included) +} + +fn parse_includes(program: &Program, resolver: &mut R) -> Vec +where + R: SourceResolver, +{ + let mut includes = vec![]; + for stmt in &program.statements { + if let StmtKind::Include(include) = stmt.kind.as_ref() { + let file_path = &include.filename; + // Skip the standard gates include file. + // Handling of this file is done by the compiler. + if file_path.to_lowercase() == "stdgates.inc" { + continue; + } + let source = parse_qasm_file(file_path, resolver); + includes.push(source); + } + } + + includes +} + +pub(crate) type Result = std::result::Result; + +pub(crate) trait Parser: FnMut(&mut ParserContext) -> Result {} + +impl Result> Parser for F {} + +#[must_use] +pub fn parse(input: &str) -> (Program, Vec) { + let mut scanner = ParserContext::new(input); + let program = prgm::parse(&mut scanner); + (program, scanner.into_errors()) +} diff --git a/compiler/qsc_qasm/src/parser/ast.rs b/compiler/qsc_qasm/src/parser/ast.rs new file mode 100644 index 0000000000..2cbf6847e8 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/ast.rs @@ -0,0 +1,1805 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::display_utils::{ + write_field, write_header, write_indented_list, write_list_field, write_opt_field, + write_opt_list_field, writeln_field, writeln_header, writeln_list_field, writeln_opt_field, +}; + +use num_bigint::BigInt; +use qsc_data_structures::span::{Span, WithSpan}; +use std::{ + fmt::{self, Display, Formatter}, + hash::Hash, + rc::Rc, +}; + +use super::prim::SeqItem; + +/// An alternative to `Vec` that uses less stack space. +pub(crate) type List = Box<[Box]>; + +pub(crate) fn list_from_iter(vals: impl IntoIterator) -> List { + vals.into_iter().map(Box::new).collect() +} + +#[derive(Clone, Debug)] +pub struct Program { + pub span: Span, + pub statements: List, + pub version: Option, +} + +impl Display for Program { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "Program", self.span)?; + writeln_opt_field(f, "version", self.version.as_ref())?; + write_list_field(f, "statements", &self.statements) + } +} + +#[derive(Clone, Debug)] +pub struct Stmt { + pub span: Span, + pub annotations: List, + pub kind: Box, +} + +impl Display for Stmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "Stmt", self.span)?; + writeln_list_field(f, "annotations", &self.annotations)?; + write_field(f, "kind", &self.kind) + } +} + +#[derive(Clone, Debug)] +pub struct Annotation { + pub span: Span, + pub identifier: Rc, + pub value: Option>, +} + +impl Display for Annotation { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let identifier = format!("\"{}\"", self.identifier); + let value = self.value.as_ref().map(|val| format!("\"{val}\"")); + writeln_header(f, "Annotation", self.span)?; + writeln_field(f, "identifier", &identifier)?; + write_opt_field(f, "value", value.as_ref()) + } +} + +/// A path that may or may not have been successfully parsed. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub enum PathKind { + /// A successfully parsed path. + Ok(Box), + /// An invalid path. + Err(Option>), +} + +impl Default for PathKind { + fn default() -> Self { + PathKind::Err(None) + } +} + +impl Display for PathKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + PathKind::Ok(path) => write!(f, "{path}"), + PathKind::Err(Some(incomplete_path)) => { + write!(f, "Err IncompletePath {}:", incomplete_path.span)?; + write_list_field(f, "segments", &incomplete_path.segments) + } + PathKind::Err(None) => write!(f, "Err",), + } + } +} + +/// A path that was successfully parsed up to a certain `.`, +/// but is missing its final identifier. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct IncompletePath { + /// The whole span of the incomplete path, + /// including the final `.` and any whitespace or keyword + /// that follows it. + pub span: Span, + /// Any segments that were successfully parsed before the final `.`. + pub segments: Box<[Ident]>, + /// Whether a keyword exists after the final `.`. + /// This keyword can be presumed to be a partially typed identifier. + pub keyword: bool, +} + +/// A path to a declaration or a field access expression, +/// to be disambiguated during name resolution. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct Path { + /// The span. + pub span: Span, + /// The segments that make up the front of the path before the final `.`. + pub segments: Option>, + /// The declaration or field name. + pub name: Box, +} + +impl Display for Path { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln_header(f, "Path", self.span)?; + writeln_field(f, "name", &self.name)?; + write_opt_list_field(f, "segments", self.segments.as_ref()) + } +} + +impl WithSpan for Path { + fn with_span(self, span: Span) -> Self { + Self { span, ..self } + } +} + +#[derive(Clone, Debug)] +pub struct MeasureExpr { + pub span: Span, + pub measure_token_span: Span, + pub operand: GateOperand, +} + +impl Display for MeasureExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "MeasureExpr", self.span)?; + write_field(f, "operand", &self.operand) + } +} + +/// A binary operator. +#[derive(Clone, Copy, Debug)] +pub enum BinOp { + /// Addition: `+`. + Add, + /// Bitwise AND: `&`. + AndB, + /// Logical AND: `&&`. + AndL, + /// Division: `/`. + Div, + /// Equality: `==`. + Eq, + /// Exponentiation: `**`. + Exp, + /// Greater than: `>`. + Gt, + /// Greater than or equal: `>=`. + Gte, + /// Less than: `<`. + Lt, + /// Less than or equal: `<=`. + Lte, + /// Modulus: `%`. + Mod, + /// Multiplication: `*`. + Mul, + /// Inequality: `!=`. + Neq, + /// Bitwise OR: `|`. + OrB, + /// Logical OR: `||`. + OrL, + /// Shift left: `<<`. + Shl, + /// Shift right: `>>`. + Shr, + /// Subtraction: `-`. + Sub, + /// Bitwise XOR: `^`. + XorB, +} + +impl Display for BinOp { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + BinOp::Add => write!(f, "Add"), + BinOp::AndB => write!(f, "AndB"), + BinOp::AndL => write!(f, "AndL"), + BinOp::Div => write!(f, "Div"), + BinOp::Eq => write!(f, "Eq"), + BinOp::Exp => write!(f, "Exp"), + BinOp::Gt => write!(f, "Gt"), + BinOp::Gte => write!(f, "Gte"), + BinOp::Lt => write!(f, "Lt"), + BinOp::Lte => write!(f, "Lte"), + BinOp::Mod => write!(f, "Mod"), + BinOp::Mul => write!(f, "Mul"), + BinOp::Neq => write!(f, "Neq"), + BinOp::OrB => write!(f, "OrB"), + BinOp::OrL => write!(f, "OrL"), + BinOp::Shl => write!(f, "Shl"), + BinOp::Shr => write!(f, "Shr"), + BinOp::Sub => write!(f, "Sub"), + BinOp::XorB => write!(f, "XorB"), + } + } +} + +/// A unary operator. +#[derive(Clone, Copy, Debug)] +pub enum UnaryOp { + /// Negation: `-`. + Neg, + /// Bitwise NOT: `~`. + NotB, + /// Logical NOT: `!`. + NotL, +} + +impl Display for UnaryOp { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + UnaryOp::Neg => write!(f, "Neg"), + UnaryOp::NotB => write!(f, "NotB"), + UnaryOp::NotL => write!(f, "NotL"), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct GateOperand { + pub span: Span, + pub kind: GateOperandKind, +} + +impl WithSpan for GateOperand { + fn with_span(self, span: Span) -> Self { + Self { + span, + kind: self.kind.with_span(span), + } + } +} + +impl Display for GateOperand { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "GateOperand", self.span)?; + write_field(f, "kind", &self.kind) + } +} + +#[derive(Clone, Debug, Default)] +pub enum GateOperandKind { + IndexedIdent(Box), + HardwareQubit(Box), + #[default] + Err, +} + +impl Display for GateOperandKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::IndexedIdent(ident) => write!(f, "{ident}"), + Self::HardwareQubit(qubit) => write!(f, "{qubit}"), + Self::Err => write!(f, "Error"), + } + } +} + +impl WithSpan for GateOperandKind { + fn with_span(self, span: Span) -> Self { + match self { + Self::IndexedIdent(ident) => Self::IndexedIdent(ident.with_span(span)), + Self::HardwareQubit(qubit) => Self::HardwareQubit(qubit.with_span(span)), + Self::Err => Self::Err, + } + } +} + +#[derive(Clone, Debug)] +pub struct HardwareQubit { + pub span: Span, + pub name: Rc, +} + +impl Display for HardwareQubit { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "HardwareQubit {}: {}", self.span, self.name) + } +} + +impl WithSpan for HardwareQubit { + fn with_span(self, span: Span) -> Self { + Self { span, ..self } + } +} + +#[derive(Clone, Debug)] +pub struct AliasDeclStmt { + pub span: Span, + pub ident: Identifier, + pub exprs: List, +} + +impl Display for AliasDeclStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "AliasDeclStmt", self.span)?; + writeln_field(f, "ident", &self.ident)?; + write_list_field(f, "exprs", &self.exprs) + } +} + +/// A statement kind. +#[derive(Clone, Debug, Default)] +pub enum StmtKind { + Alias(AliasDeclStmt), + Assign(AssignStmt), + AssignOp(AssignOpStmt), + Barrier(BarrierStmt), + Box(BoxStmt), + Break(BreakStmt), + Block(Block), + Cal(CalibrationStmt), + CalibrationGrammar(CalibrationGrammarStmt), + ClassicalDecl(ClassicalDeclarationStmt), + ConstDecl(ConstantDeclStmt), + Continue(ContinueStmt), + Def(DefStmt), + DefCal(DefCalStmt), + Delay(DelayStmt), + End(EndStmt), + ExprStmt(ExprStmt), + ExternDecl(ExternDecl), + For(ForStmt), + If(IfStmt), + GateCall(GateCall), + GPhase(GPhase), + Include(IncludeStmt), + IODeclaration(IODeclaration), + Measure(MeasureArrowStmt), + Pragma(Pragma), + QuantumGateDefinition(QuantumGateDefinition), + QuantumDecl(QubitDeclaration), + Reset(ResetStmt), + Return(ReturnStmt), + Switch(SwitchStmt), + WhileLoop(WhileLoop), + /// An invalid statement. + #[default] + Err, +} + +impl Display for StmtKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + StmtKind::Assign(stmt) => write!(f, "{stmt}"), + StmtKind::AssignOp(stmt) => write!(f, "{stmt}"), + StmtKind::Alias(alias) => write!(f, "{alias}"), + StmtKind::Barrier(barrier) => write!(f, "{barrier}"), + StmtKind::Box(box_stmt) => write!(f, "{box_stmt}"), + StmtKind::Break(break_stmt) => write!(f, "{break_stmt}"), + StmtKind::Block(block) => write!(f, "{block}"), + StmtKind::Cal(cal) => write!(f, "{cal}"), + StmtKind::CalibrationGrammar(grammar) => write!(f, "{grammar}"), + StmtKind::ClassicalDecl(decl) => write!(f, "{decl}"), + StmtKind::ConstDecl(decl) => write!(f, "{decl}"), + StmtKind::Continue(continue_stmt) => write!(f, "{continue_stmt}"), + StmtKind::Def(def) => write!(f, "{def}"), + StmtKind::DefCal(defcal) => write!(f, "{defcal}"), + StmtKind::Delay(delay) => write!(f, "{delay}"), + StmtKind::End(end_stmt) => write!(f, "{end_stmt}"), + StmtKind::ExprStmt(expr) => write!(f, "{expr}"), + StmtKind::ExternDecl(decl) => write!(f, "{decl}"), + StmtKind::For(for_stmt) => write!(f, "{for_stmt}"), + StmtKind::GateCall(gate_call) => write!(f, "{gate_call}"), + StmtKind::GPhase(gphase) => write!(f, "{gphase}"), + StmtKind::If(if_stmt) => write!(f, "{if_stmt}"), + StmtKind::Include(include) => write!(f, "{include}"), + StmtKind::IODeclaration(io) => write!(f, "{io}"), + StmtKind::Measure(measure) => write!(f, "{measure}"), + StmtKind::Pragma(pragma) => write!(f, "{pragma}"), + StmtKind::QuantumGateDefinition(gate) => write!(f, "{gate}"), + StmtKind::QuantumDecl(decl) => write!(f, "{decl}"), + StmtKind::Reset(reset_stmt) => write!(f, "{reset_stmt}"), + StmtKind::Return(return_stmt) => write!(f, "{return_stmt}"), + StmtKind::Switch(switch_stmt) => write!(f, "{switch_stmt}"), + StmtKind::WhileLoop(while_loop) => write!(f, "{while_loop}"), + StmtKind::Err => write!(f, "Err"), + } + } +} + +#[derive(Clone, Debug)] +pub struct CalibrationGrammarStmt { + pub span: Span, + pub name: String, +} + +impl Display for CalibrationGrammarStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "CalibrationGrammarStmt", self.span)?; + write_field(f, "name", &self.name) + } +} + +#[derive(Clone, Debug)] +pub struct DefCalStmt { + pub span: Span, +} + +impl Display for DefCalStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "DefCalStmt {}", self.span) + } +} + +#[derive(Clone, Debug)] +pub struct IfStmt { + pub span: Span, + pub condition: Expr, + pub if_body: Stmt, + pub else_body: Option, +} + +impl Display for IfStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "IfStmt", self.span)?; + writeln_field(f, "condition", &self.condition)?; + writeln_field(f, "if_body", &self.if_body)?; + write_opt_field(f, "else_body", self.else_body.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct BarrierStmt { + pub span: Span, + pub qubits: List, +} + +impl Display for BarrierStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "BarrierStmt", self.span)?; + write_list_field(f, "operands", &self.qubits) + } +} + +#[derive(Clone, Debug)] +pub struct ResetStmt { + pub span: Span, + pub reset_token_span: Span, + pub operand: Box, +} + +impl Display for ResetStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ResetStmt", self.span)?; + writeln_field(f, "reset_token_span", &self.reset_token_span)?; + write_field(f, "operand", &self.operand) + } +} + +/// A sequenced block of statements. +#[derive(Clone, Debug, Default)] +pub struct Block { + /// The span. + pub span: Span, + /// The statements in the block. + pub stmts: List, +} + +impl Display for Block { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write_header(f, "Block", self.span)?; + write_indented_list(f, &self.stmts) + } +} + +#[derive(Clone, Debug)] +pub enum Identifier { + Ident(Box), + IndexedIdent(Box), +} + +impl Identifier { + #[must_use] + pub fn span(&self) -> Span { + match self { + Identifier::Ident(ident) => ident.span, + Identifier::IndexedIdent(ident) => ident.span, + } + } +} + +impl Display for Identifier { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Identifier::Ident(ident) => write!(f, "{ident}"), + Identifier::IndexedIdent(ident) => write!(f, "{ident}"), + } + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct Ident { + pub span: Span, + pub name: Rc, +} + +impl Default for Ident { + fn default() -> Self { + Ident { + span: Span::default(), + name: "".into(), + } + } +} + +impl Display for Ident { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Ident {} \"{}\"", self.span, self.name) + } +} + +impl WithSpan for Ident { + fn with_span(self, span: Span) -> Self { + Self { span, ..self } + } +} + +#[derive(Clone, Debug)] +pub struct IndexedIdent { + pub span: Span, + pub index_span: Span, + pub name: Ident, + pub indices: List, +} + +impl Display for IndexedIdent { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "IndexedIdent", self.span)?; + writeln_field(f, "name", &self.name)?; + writeln_field(f, "index_span", &self.index_span)?; + write_list_field(f, "indices", &self.indices) + } +} + +impl WithSpan for IndexedIdent { + fn with_span(self, span: Span) -> Self { + Self { span, ..self } + } +} + +#[derive(Clone, Debug)] +pub struct ExprStmt { + pub span: Span, + pub expr: Expr, +} + +impl Display for ExprStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ExprStmt", self.span)?; + write_field(f, "expr", &self.expr) + } +} + +#[derive(Clone, Debug, Default)] +pub struct Expr { + pub span: Span, + pub kind: Box, +} + +impl WithSpan for Expr { + fn with_span(self, span: Span) -> Self { + Self { span, ..self } + } +} + +impl Display for Expr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Expr {}: {}", self.span, self.kind) + } +} + +#[derive(Clone, Debug)] +pub struct DiscreteSet { + pub span: Span, + pub values: List, +} + +impl Display for DiscreteSet { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "DiscreteSet", self.span)?; + write_list_field(f, "values", &self.values) + } +} + +#[derive(Clone, Debug)] +pub struct IndexSet { + pub span: Span, + pub values: List, +} + +impl Display for IndexSet { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "IndexSet", self.span)?; + write_list_field(f, "values", &self.values) + } +} + +#[derive(Clone, Debug)] +pub struct RangeDefinition { + pub span: Span, + pub start: Option, + pub end: Option, + pub step: Option, +} + +impl WithSpan for RangeDefinition { + fn with_span(self, span: Span) -> Self { + Self { span, ..self } + } +} + +impl Display for RangeDefinition { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "RangeDefinition", self.span)?; + writeln_opt_field(f, "start", self.start.as_ref())?; + writeln_opt_field(f, "step", self.step.as_ref())?; + write_opt_field(f, "end", self.end.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct QuantumGateModifier { + pub span: Span, + pub modifier_keyword_span: Span, + pub kind: GateModifierKind, +} + +impl Display for QuantumGateModifier { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "QuantumGateModifier", self.span)?; + writeln_field(f, "modifier_keyword_span", &self.modifier_keyword_span)?; + write_field(f, "kind", &self.kind) + } +} + +#[derive(Clone, Debug)] +pub enum GateModifierKind { + Inv, + Pow(Expr), + Ctrl(Option), + NegCtrl(Option), +} + +impl Display for GateModifierKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + GateModifierKind::Inv => write!(f, "Inv"), + GateModifierKind::Pow(expr) => write!(f, "Pow {expr}"), + GateModifierKind::Ctrl(expr) => write!(f, "Ctrl {expr:?}"), + GateModifierKind::NegCtrl(expr) => write!(f, "NegCtrl {expr:?}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct ClassicalArgument { + pub span: Span, + pub ty: ScalarType, + pub name: Identifier, + pub access: Option, +} + +impl Display for ClassicalArgument { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(access) = &self.access { + write!( + f, + "ClassicalArgument {}: {}, {}, {}", + self.span, self.ty, self.name, access + ) + } else { + write!( + f, + "ClassicalArgument {}: {}, {}", + self.span, self.ty, self.name + ) + } + } +} + +#[derive(Clone, Debug)] +pub enum ExternParameter { + ArrayReference(ArrayReferenceType, Span), + Scalar(ScalarType, Span), +} + +impl ExternParameter { + #[must_use] + pub fn span(&self) -> Span { + match self { + ExternParameter::ArrayReference(_, span) | ExternParameter::Scalar(_, span) => *span, + } + } +} + +impl Display for ExternParameter { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + ExternParameter::Scalar(ty, span) => { + write!(f, "{span}: {ty}") + } + ExternParameter::ArrayReference(ty, span) => { + write!(f, "{span}: {ty}") + } + } + } +} + +impl Default for ExternParameter { + fn default() -> Self { + ExternParameter::Scalar(ScalarType::default(), Span::default()) + } +} + +impl WithSpan for ExternParameter { + fn with_span(self, span: Span) -> Self { + match self { + ExternParameter::Scalar(ty, _) => ExternParameter::Scalar(ty, span), + ExternParameter::ArrayReference(ty, _) => ExternParameter::ArrayReference(ty, span), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct ScalarType { + pub span: Span, + pub kind: ScalarTypeKind, +} + +impl Display for ScalarType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "ScalarType {}: {}", self.span, self.kind) + } +} + +#[derive(Clone, Debug, Default)] +pub enum ScalarTypeKind { + Bit(BitType), + Int(IntType), + UInt(UIntType), + Float(FloatType), + Complex(ComplexType), + Angle(AngleType), + BoolType, + Duration, + Stretch, + // Any usage of Err should have pushed a parse error + #[default] + Err, +} + +impl Display for ScalarTypeKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + ScalarTypeKind::Int(int) => write!(f, "{int}"), + ScalarTypeKind::UInt(uint) => write!(f, "{uint}"), + ScalarTypeKind::Float(float) => write!(f, "{float}"), + ScalarTypeKind::Complex(complex) => write!(f, "{complex}"), + ScalarTypeKind::Angle(angle) => write!(f, "{angle}"), + ScalarTypeKind::Bit(bit) => write!(f, "{bit}"), + ScalarTypeKind::BoolType => write!(f, "BoolType"), + ScalarTypeKind::Duration => write!(f, "Duration"), + ScalarTypeKind::Stretch => write!(f, "Stretch"), + ScalarTypeKind::Err => write!(f, "Err"), + } + } +} + +#[derive(Clone, Debug)] +pub enum ArrayBaseTypeKind { + Int(IntType), + UInt(UIntType), + Float(FloatType), + Complex(ComplexType), + Angle(AngleType), + BoolType, + Duration, +} + +impl Display for ArrayBaseTypeKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + ArrayBaseTypeKind::Int(int) => write!(f, "ArrayBaseTypeKind {int}"), + ArrayBaseTypeKind::UInt(uint) => write!(f, "ArrayBaseTypeKind {uint}"), + ArrayBaseTypeKind::Float(float) => write!(f, "ArrayBaseTypeKind {float}"), + ArrayBaseTypeKind::Complex(complex) => write!(f, "ArrayBaseTypeKind {complex}"), + ArrayBaseTypeKind::Angle(angle) => write!(f, "ArrayBaseTypeKind {angle}"), + ArrayBaseTypeKind::Duration => write!(f, "ArrayBaseTypeKind DurationType"), + ArrayBaseTypeKind::BoolType => write!(f, "ArrayBaseTypeKind BoolType"), + } + } +} + +#[derive(Clone, Debug)] +pub struct IntType { + pub span: Span, + pub size: Option, +} + +impl Display for IntType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "IntType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct UIntType { + pub span: Span, + pub size: Option, +} + +impl Display for UIntType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "UIntType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct FloatType { + pub span: Span, + pub size: Option, +} + +impl Display for FloatType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "FloatType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct ComplexType { + pub span: Span, + pub base_size: Option, +} + +impl Display for ComplexType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ComplexType", self.span)?; + write_opt_field(f, "base_size", self.base_size.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct AngleType { + pub span: Span, + pub size: Option, +} + +impl Display for AngleType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "AngleType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct BitType { + pub span: Span, + pub size: Option, +} + +impl Display for BitType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "BitType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub enum TypeDef { + Scalar(ScalarType), + Array(ArrayType), + ArrayReference(ArrayReferenceType), +} + +impl TypeDef { + #[must_use] + pub fn span(&self) -> Span { + match self { + TypeDef::Scalar(ident) => ident.span, + TypeDef::Array(array) => array.span, + TypeDef::ArrayReference(array) => array.span, + } + } +} + +impl Display for TypeDef { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + TypeDef::Scalar(scalar) => write!(f, "{scalar}"), + TypeDef::Array(array) => write!(f, "{array}"), + TypeDef::ArrayReference(array) => write!(f, "{array}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct ArrayType { + pub span: Span, + pub base_type: ArrayBaseTypeKind, + pub dimensions: List, +} + +impl Display for ArrayType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ArrayType", self.span)?; + writeln_field(f, "base_type", &self.base_type)?; + write_list_field(f, "dimensions", &self.dimensions) + } +} + +#[derive(Clone, Debug)] +pub struct ArrayReferenceType { + pub span: Span, + pub mutability: AccessControl, + pub base_type: ArrayBaseTypeKind, + pub dimensions: List, +} + +impl Display for ArrayReferenceType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ArrayReferenceType", self.span)?; + writeln_field(f, "mutability", &self.mutability)?; + writeln_field(f, "base_type", &self.base_type)?; + writeln_list_field(f, "dimensions", &self.dimensions) + } +} + +#[derive(Clone, Debug)] +pub enum AccessControl { + ReadOnly, + Mutable, +} + +impl Display for AccessControl { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + AccessControl::ReadOnly => write!(f, "ReadOnly"), + AccessControl::Mutable => write!(f, "Mutable"), + } + } +} + +#[derive(Clone, Debug)] +pub struct QuantumArgument { + pub span: Span, + pub expr: Option, +} + +impl Display for QuantumArgument { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "QuantumArgument", self.span)?; + write_opt_field(f, "expr", self.expr.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct Pragma { + pub span: Span, + pub identifier: Rc, + pub value: Option>, +} + +impl Display for Pragma { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let identifier = format!("\"{}\"", self.identifier); + let value = self.value.as_ref().map(|val| format!("\"{val}\"")); + writeln_header(f, "Pragma", self.span)?; + writeln_field(f, "identifier", &identifier)?; + write_opt_field(f, "value", value.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct IncludeStmt { + pub span: Span, + pub filename: String, +} + +impl Display for IncludeStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "IncludeStmt", self.span)?; + write_field(f, "filename", &self.filename) + } +} + +#[derive(Clone, Debug)] +pub struct QubitDeclaration { + pub span: Span, + pub ty_span: Span, + pub qubit: Ident, + pub size: Option, +} + +impl Display for QubitDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "QubitDeclaration", self.span)?; + writeln_field(f, "ty_span", &self.ty_span)?; + writeln_field(f, "ident", &self.qubit)?; + write_opt_field(f, "size", self.size.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct QuantumGateDefinition { + pub span: Span, + pub ident: Box, + pub params: List>, + pub qubits: List>, + pub body: Box, +} + +impl Display for QuantumGateDefinition { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "Gate", self.span)?; + writeln_field(f, "ident", &self.ident)?; + writeln_list_field(f, "parameters", &self.params)?; + writeln_list_field(f, "qubits", &self.qubits)?; + write_field(f, "body", &self.body) + } +} + +#[derive(Clone, Debug)] +pub struct ExternDecl { + pub span: Span, + pub ident: Box, + pub params: List, + pub return_type: Option, +} + +impl Display for ExternDecl { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ExternDecl", self.span)?; + writeln_field(f, "ident", &self.ident)?; + writeln_list_field(f, "parameters", &self.params)?; + write_opt_field(f, "return_type", self.return_type.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct GateCall { + pub span: Span, + pub modifiers: List, + pub name: Ident, + pub args: List, + pub qubits: List, + pub duration: Option, +} + +impl Display for GateCall { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "GateCall", self.span)?; + writeln_list_field(f, "modifiers", &self.modifiers)?; + writeln_field(f, "name", &self.name)?; + writeln_list_field(f, "args", &self.args)?; + writeln_opt_field(f, "duration", self.duration.as_ref())?; + write_list_field(f, "qubits", &self.qubits) + } +} + +#[derive(Clone, Debug)] +pub struct GPhase { + pub span: Span, + pub gphase_token_span: Span, + pub modifiers: List, + pub args: List, + pub qubits: List, + pub duration: Option, +} + +impl Display for GPhase { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "GPhase", self.span)?; + writeln_field(f, "gphase_token_span", &self.gphase_token_span)?; + writeln_list_field(f, "modifiers", &self.modifiers)?; + writeln_list_field(f, "args", &self.args)?; + writeln_opt_field(f, "duration", self.duration.as_ref())?; + write_list_field(f, "qubits", &self.qubits) + } +} + +#[derive(Clone, Debug)] +pub struct DelayStmt { + pub span: Span, + pub duration: Expr, + pub qubits: List, +} + +impl Display for DelayStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "DelayStmt", self.span)?; + writeln_field(f, "duration", &self.duration)?; + write_list_field(f, "qubits", &self.qubits) + } +} + +#[derive(Clone, Debug)] +pub struct BoxStmt { + pub span: Span, + pub duration: Option, + pub body: List, +} + +impl Display for BoxStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "BoxStmt", self.span)?; + writeln_opt_field(f, "duration", self.duration.as_ref())?; + write_list_field(f, "body", &self.body) + } +} + +#[derive(Clone, Debug)] +pub struct MeasureArrowStmt { + pub span: Span, + pub measurement: MeasureExpr, + pub target: Option>, +} + +impl Display for MeasureArrowStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "MeasureArrowStmt", self.span)?; + writeln_field(f, "measurement", &self.measurement)?; + write_opt_field(f, "target", self.target.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct ClassicalDeclarationStmt { + pub span: Span, + pub ty: Box, + pub identifier: Ident, + pub init_expr: Option>, +} + +impl Display for ClassicalDeclarationStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ClassicalDeclarationStmt", self.span)?; + writeln_field(f, "type", &self.ty)?; + writeln_field(f, "ident", &self.identifier)?; + write_opt_field(f, "init_expr", self.init_expr.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub enum ValueExpr { + Expr(Expr), + Measurement(MeasureExpr), +} + +impl Display for ValueExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Expr(expr) => write!(f, "{expr}"), + Self::Measurement(measure) => write!(f, "{measure}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct IODeclaration { + pub span: Span, + pub io_identifier: IOKeyword, + pub ty: TypeDef, + pub ident: Box, +} + +impl Display for IODeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "IODeclaration", self.span)?; + writeln_field(f, "io_keyword", &self.io_identifier)?; + writeln_field(f, "type", &self.ty)?; + write_field(f, "ident", &self.ident) + } +} + +#[derive(Clone, Debug)] +pub struct ConstantDeclStmt { + pub span: Span, + pub ty: TypeDef, + pub identifier: Box, + pub init_expr: ValueExpr, +} + +impl Display for ConstantDeclStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ConstantDeclStmt", self.span)?; + writeln_field(f, "type", &self.ty)?; + writeln_field(f, "ident", &self.identifier)?; + write_field(f, "init_expr", &self.init_expr) + } +} + +#[derive(Clone, Debug)] +pub struct CalibrationGrammarDeclaration { + span: Span, + name: String, +} + +impl Display for CalibrationGrammarDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "CalibrationGrammarDeclaration", self.span)?; + write_field(f, "name", &self.name) + } +} + +#[derive(Clone, Debug)] +pub struct CalibrationStmt { + pub span: Span, +} + +impl Display for CalibrationStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "CalibrationStmt {}", self.span) + } +} + +#[derive(Clone, Debug)] +pub enum TypedParameter { + ArrayReference(ArrayTypedParameter), + Quantum(QuantumTypedParameter), + Scalar(ScalarTypedParameter), +} + +impl WithSpan for TypedParameter { + fn with_span(self, span: Span) -> Self { + match self { + Self::Scalar(param) => Self::Scalar(param.with_span(span)), + Self::Quantum(param) => Self::Quantum(param.with_span(span)), + Self::ArrayReference(param) => Self::ArrayReference(param.with_span(span)), + } + } +} + +impl Display for TypedParameter { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Scalar(param) => write!(f, "{param}"), + Self::Quantum(param) => write!(f, "{param}"), + Self::ArrayReference(param) => write!(f, "{param}"), + } + } +} + +impl Default for TypedParameter { + fn default() -> Self { + Self::Scalar(ScalarTypedParameter { + span: Span::default(), + ident: Ident::default(), + ty: Box::default(), + }) + } +} + +#[derive(Clone, Debug)] +pub struct ScalarTypedParameter { + pub span: Span, + pub ty: Box, + pub ident: Ident, +} + +impl Display for ScalarTypedParameter { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ScalarTypedParameter", self.span)?; + writeln_field(f, "type", &self.ty)?; + write_field(f, "ident", &self.ident) + } +} + +impl WithSpan for ScalarTypedParameter { + fn with_span(self, span: Span) -> Self { + let Self { ty, ident, .. } = self; + Self { span, ty, ident } + } +} + +#[derive(Clone, Debug)] +pub struct QuantumTypedParameter { + pub span: Span, + pub size: Option, + pub ident: Ident, +} + +impl Display for QuantumTypedParameter { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "QuantumTypedParameter", self.span)?; + writeln_opt_field(f, "size", self.size.as_ref())?; + write_field(f, "ident", &self.ident) + } +} + +impl WithSpan for QuantumTypedParameter { + fn with_span(self, span: Span) -> Self { + let Self { size, ident, .. } = self; + Self { span, size, ident } + } +} + +#[derive(Clone, Debug)] +pub struct ArrayTypedParameter { + pub span: Span, + pub ty: Box, + pub ident: Ident, +} + +impl Display for ArrayTypedParameter { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ArrayTypedParameter", self.span)?; + writeln_field(f, "type", &self.ty)?; + write_field(f, "ident", &self.ident) + } +} + +impl WithSpan for ArrayTypedParameter { + fn with_span(self, span: Span) -> Self { + let Self { ty, ident, .. } = self; + Self { span, ty, ident } + } +} + +#[derive(Clone, Debug)] +pub struct DefStmt { + pub span: Span, + pub name: Box, + pub params: List, + pub body: Block, + pub return_type: Option>, +} + +impl Display for DefStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "DefStmt", self.span)?; + writeln_field(f, "ident", &self.name)?; + writeln_list_field(f, "parameters", &self.params)?; + writeln_opt_field(f, "return_type", self.return_type.as_ref())?; + write_field(f, "body", &self.body) + } +} + +#[derive(Clone, Debug)] +pub struct ReturnStmt { + pub span: Span, + pub expr: Option>, +} + +impl Display for ReturnStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ReturnStmt", self.span)?; + write_opt_field(f, "expr", self.expr.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct WhileLoop { + pub span: Span, + pub while_condition: Expr, + pub body: Stmt, +} + +impl Display for WhileLoop { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "WhileLoop", self.span)?; + writeln_field(f, "condition", &self.while_condition)?; + write_field(f, "body", &self.body) + } +} + +#[derive(Clone, Debug)] +pub struct ForStmt { + pub span: Span, + pub ty: ScalarType, + pub ident: Ident, + pub set_declaration: Box, + pub body: Stmt, +} + +impl Display for ForStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ForStmt", self.span)?; + writeln_field(f, "variable_type", &self.ty)?; + writeln_field(f, "variable_name", &self.ident)?; + writeln_field(f, "iterable", &self.set_declaration)?; + write_field(f, "body", &self.body) + } +} + +#[derive(Clone, Debug)] +pub enum EnumerableSet { + DiscreteSet(DiscreteSet), + RangeDefinition(RangeDefinition), + Expr(Expr), +} + +impl Display for EnumerableSet { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + EnumerableSet::DiscreteSet(set) => write!(f, "{set}"), + EnumerableSet::RangeDefinition(range) => write!(f, "{range}"), + EnumerableSet::Expr(expr) => write!(f, "{expr}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct SwitchStmt { + pub span: Span, + pub target: Expr, + pub cases: List, + /// Note that `None` is quite different to `[]` in this case; the latter is + /// an explicitly empty body, whereas the absence of a default might mean + /// that the switch is inexhaustive, and a linter might want to complain. + pub default: Option, +} + +impl Display for SwitchStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "SwitchStmt", self.span)?; + writeln_field(f, "target", &self.target)?; + writeln_list_field(f, "cases", &self.cases)?; + write_opt_field(f, "default_case", self.default.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct SwitchCase { + pub span: Span, + pub labels: List, + pub block: Block, +} + +impl Display for SwitchCase { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "SwitchCase", self.span)?; + writeln_list_field(f, "labels", &self.labels)?; + write_field(f, "block", &self.block) + } +} + +#[derive(Clone, Debug, Default)] +pub enum ExprKind { + /// An expression with invalid syntax that can't be parsed. + #[default] + Err, + Ident(Ident), + UnaryOp(UnaryOpExpr), + BinaryOp(BinaryOpExpr), + Lit(Lit), + FunctionCall(FunctionCall), + Cast(Cast), + IndexExpr(IndexExpr), + Paren(Expr), +} + +impl Display for ExprKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + ExprKind::Err => write!(f, "Err"), + ExprKind::Ident(id) => write!(f, "{id}"), + ExprKind::UnaryOp(expr) => write!(f, "{expr}"), + ExprKind::BinaryOp(expr) => write!(f, "{expr}"), + ExprKind::Lit(lit) => write!(f, "{lit}"), + ExprKind::FunctionCall(call) => write!(f, "{call}"), + ExprKind::Cast(expr) => write!(f, "{expr}"), + ExprKind::IndexExpr(expr) => write!(f, "{expr}"), + ExprKind::Paren(expr) => write!(f, "Paren {expr}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct AssignStmt { + pub span: Span, + pub lhs: IndexedIdent, + pub rhs: ValueExpr, +} + +impl Display for AssignStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "AssignStmt", self.span)?; + writeln_field(f, "lhs", &self.lhs)?; + write_field(f, "rhs", &self.rhs) + } +} + +#[derive(Clone, Debug)] +pub struct AssignOpStmt { + pub span: Span, + pub op: BinOp, + pub lhs: IndexedIdent, + pub rhs: ValueExpr, +} + +impl Display for AssignOpStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "AssignOpStmt", self.span)?; + writeln_field(f, "op", &self.op)?; + writeln_field(f, "lhs", &self.lhs)?; + write_field(f, "rhs", &self.rhs) + } +} + +#[derive(Clone, Debug)] +pub struct UnaryOpExpr { + pub op: UnaryOp, + pub expr: Expr, +} + +impl Display for UnaryOpExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "UnaryOpExpr:")?; + writeln_field(f, "op", &self.op)?; + write_field(f, "expr", &self.expr) + } +} + +#[derive(Clone, Debug)] +pub struct BinaryOpExpr { + pub op: BinOp, + pub lhs: Expr, + pub rhs: Expr, +} + +impl Display for BinaryOpExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "BinaryOpExpr:")?; + writeln_field(f, "op", &self.op)?; + writeln_field(f, "lhs", &self.lhs)?; + write_field(f, "rhs", &self.rhs) + } +} + +#[derive(Clone, Debug)] +pub struct FunctionCall { + pub span: Span, + pub name: Ident, + pub args: List, +} + +impl Display for FunctionCall { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "FunctionCall", self.span)?; + writeln_field(f, "name", &self.name)?; + write_list_field(f, "args", &self.args) + } +} + +#[derive(Clone, Debug)] +pub struct Cast { + pub span: Span, + pub ty: TypeDef, + pub arg: Expr, +} + +impl Display for Cast { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "Cast", self.span)?; + writeln_field(f, "type", &self.ty)?; + write_field(f, "arg", &self.arg) + } +} + +#[derive(Clone, Debug)] +pub struct IndexExpr { + pub span: Span, + pub collection: Expr, + pub index: IndexElement, +} + +impl Display for IndexExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "IndexExpr", self.span)?; + writeln_field(f, "collection", &self.collection)?; + write_field(f, "index", &self.index) + } +} + +#[derive(Clone, Debug)] +pub struct Lit { + pub span: Span, + pub kind: LiteralKind, +} + +impl Display for Lit { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Lit: {}", self.kind) + } +} + +#[derive(Clone, Debug)] +pub enum LiteralKind { + Array(List), + Bitstring(BigInt, u32), + Bool(bool), + Duration(f64, TimeUnit), + Float(f64), + Imaginary(f64), + Int(i64), + BigInt(BigInt), + String(Rc), +} + +impl Display for LiteralKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + LiteralKind::Array(exprs) => write_list_field(f, "Array", exprs), + LiteralKind::Bitstring(value, width) => { + let width = *width as usize; + write!(f, "Bitstring(\"{:0>width$}\")", value.to_str_radix(2)) + } + LiteralKind::Bool(b) => write!(f, "Bool({b:?})"), + LiteralKind::Duration(value, unit) => { + write!(f, "Duration({value:?}, {unit:?})") + } + LiteralKind::Float(value) => write!(f, "Float({value:?})"), + LiteralKind::Imaginary(value) => write!(f, "Imaginary({value:?})"), + LiteralKind::Int(i) => write!(f, "Int({i:?})"), + LiteralKind::BigInt(i) => write!(f, "BigInt({i:?})"), + LiteralKind::String(s) => write!(f, "String({s:?})"), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Version { + pub major: u32, + pub minor: Option, + pub span: Span, +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.minor { + Some(minor) => write!(f, "{}.{}", self.major, minor), + None => write!(f, "{}", self.major), + } + } +} + +#[derive(Clone, Debug)] +pub enum IndexElement { + DiscreteSet(DiscreteSet), + IndexSet(IndexSet), +} + +impl Display for IndexElement { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + IndexElement::DiscreteSet(set) => write!(f, "{set}"), + IndexElement::IndexSet(set) => write!(f, "{set}"), + } + } +} + +impl IndexElement { + #[must_use] + pub fn span(&self) -> Span { + match self { + IndexElement::DiscreteSet(set) => set.span, + IndexElement::IndexSet(set) => set.span, + } + } +} + +#[derive(Clone, Debug, Default)] +pub enum IndexSetItem { + RangeDefinition(RangeDefinition), + Expr(Expr), + #[default] + Err, +} + +/// This is needed to able to use `IndexSetItem` in the `seq` combinator. +impl WithSpan for IndexSetItem { + fn with_span(self, span: Span) -> Self { + match self { + IndexSetItem::RangeDefinition(range) => { + IndexSetItem::RangeDefinition(range.with_span(span)) + } + IndexSetItem::Expr(expr) => IndexSetItem::Expr(expr.with_span(span)), + IndexSetItem::Err => IndexSetItem::Err, + } + } +} + +impl Display for IndexSetItem { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + IndexSetItem::RangeDefinition(range) => write!(f, "{range}"), + IndexSetItem::Expr(expr) => write!(f, "{expr}"), + IndexSetItem::Err => write!(f, "Err"), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum IOKeyword { + Input, + Output, +} + +impl Display for IOKeyword { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + IOKeyword::Input => write!(f, "input"), + IOKeyword::Output => write!(f, "output"), + } + } +} + +impl From for crate::semantic::symbols::IOKind { + fn from(value: IOKeyword) -> Self { + match value { + IOKeyword::Input => crate::semantic::symbols::IOKind::Input, + IOKeyword::Output => crate::semantic::symbols::IOKind::Output, + } + } +} + +#[derive(Clone, Debug, Copy)] +pub enum TimeUnit { + Dt, + /// Nanoseconds. + Ns, + /// Microseconds. + Us, + /// Milliseconds. + Ms, + /// Seconds. + S, +} + +impl Display for TimeUnit { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + TimeUnit::Dt => write!(f, "dt"), + TimeUnit::Ns => write!(f, "ns"), + TimeUnit::Us => write!(f, "us"), + TimeUnit::Ms => write!(f, "ms"), + TimeUnit::S => write!(f, "s"), + } + } +} + +#[derive(Clone, Debug)] +pub struct BreakStmt { + pub span: Span, +} + +impl Display for BreakStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "BreakStmt {}", self.span) + } +} + +#[derive(Clone, Debug)] +pub struct ContinueStmt { + pub span: Span, +} + +impl Display for ContinueStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "ContinueStmt {}", self.span) + } +} + +#[derive(Clone, Debug)] +pub struct EndStmt { + pub span: Span, +} + +impl Display for EndStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "EndStmt {}", self.span) + } +} diff --git a/compiler/qsc_qasm/src/parser/completion.rs b/compiler/qsc_qasm/src/parser/completion.rs new file mode 100644 index 0000000000..2166c0a77d --- /dev/null +++ b/compiler/qsc_qasm/src/parser/completion.rs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#[cfg(test)] +mod tests; + +pub mod collector; +pub mod word_kinds; + +use collector::ValidWordCollector; +use word_kinds::WordKinds; + +use super::{prgm, ParserContext}; + +/// Returns the words that would be valid syntax at a particular offset +/// in the given source file (using the source file parser). +/// +/// This is useful for providing completions in an editor. +#[must_use] +pub fn possible_words_at_offset_in_source(input: &str, at_offset: u32) -> WordKinds { + let mut collector = ValidWordCollector::new(at_offset); + let mut scanner = ParserContext::with_word_collector(input, &mut collector); + let _ = prgm::parse(&mut scanner); + collector.into_words() +} diff --git a/compiler/qsc_qasm/src/parser/completion/collector.rs b/compiler/qsc_qasm/src/parser/completion/collector.rs new file mode 100644 index 0000000000..fd02969ce4 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/completion/collector.rs @@ -0,0 +1,187 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! The [`ValidWordCollector`] provides a mechanism to hook into the parser +//! to collect the possible valid words at a specific cursor location in the +//! code. It's meant to be used by the code completion feature in the +//! language service. +//! +//! Any time the parser is about to try parsing a word token, it records the +//! expected word(s) through a call to [`ValidWordCollector::expect()`]. +//! These are considered to be valid words for that location. +//! +//! If the parser is not at the cursor position yet, this call is ignored. +//! +//! Once the parser has reached the cursor position, the expected word(s) +//! are recorded into a list. +//! +//! At this point, the [`ValidWordCollector`] tricks the parser by +//! intercepting the lexer and returning an EOF token to the parser instead +//! of the real next token from the source. +//! +//! Since EOF will never match a token that the parser is looking for, this +//! causes the parser to keep trying all possible tokens at at this location, +//! recording the expected words in the process. Finally, it gives up. +//! +//! As soon as the parser reports a parse error at the cursor location, +//! the [`ValidWordCollector`] stops recording expected words. This +//! is to prevent the word list from getting polluted with words that are +//! expected after recovery occurs. +//! +//! For example, consider the code sample below, where `|` denotes the +//! cursor location: +//! +//! ```qsharp +//! operation Main() : Unit { let x: | +//! ``` +//! +//! When the parser gets to the cursor location, it looks for the words that are +//! applicable at a type position (paths, type parameters, etc). But it +//! keeps finding the EOF that was inserted by the [`ValidWordCollector`].As the +//! parser goes through each possible word, the word is recorded by the collector. +//! Finally, the parser gives up and reports a parse error. The parser then recovers, +//! and and starts looking for words that can start statements instead (`let`, etc). +//! These words are *not* recorded by the collector since they occur +//! after the parser has already reported an error. +//! +//! Note that returning EOF at the cursor means that the "manipulated" +//! parser will never run further than the cursor location, meaning the two +//! below code inputs are equivalent: +//! +//! ```qsharp +//! operation Foo() : | Unit {} +//! ``` +//! +//! ```qsharp +//! operation Foo() : | +//! ``` + +use super::WordKinds; +use crate::lex::{Token, TokenKind}; +use qsc_data_structures::span::Span; + +pub(crate) struct ValidWordCollector { + cursor_offset: u32, + state: State, + collected: WordKinds, +} + +#[derive(Debug, PartialEq, Eq)] +enum State { + /// The parser has not reached the cursor location yet. + BeforeCursor, + /// The parser is at the cursor, i.e. the cursor touches the next + /// token the parser is about to consume. + /// + /// This is when we start collecting expected valid words from the parser. + AtCursor, + /// The parser has encountered an error at the cursor location. + /// Stop collecting expected valid words. + End, +} + +impl ValidWordCollector { + pub fn new(cursor_offset: u32) -> Self { + Self { + cursor_offset, + state: State::BeforeCursor, + collected: WordKinds::empty(), + } + } + + /// The parser expects the given word(s) at the next token. + pub fn expect(&mut self, expected: WordKinds) { + match self.state { + State::AtCursor => self.collected.extend(expected), + State::BeforeCursor | State::End => {} + } + } + + /// The parser has advanced. Update state. + pub fn did_advance(&mut self, next_token: &mut Token, scanner_offset: u32) { + match self.state { + State::BeforeCursor => { + if cursor_at_token(self.cursor_offset, *next_token, scanner_offset) { + self.state = State::AtCursor; + // Set the next token to be EOF. This will trick the parser into + // attempting to parse the token over and over again, + // collecting `WordKinds` in the process. + *next_token = eof(next_token.span.hi); + } + } + State::End | State::AtCursor => {} + } + } + + /// The parser reported an error. Update state. + pub fn did_error(&mut self) { + match self.state { + State::AtCursor => self.state = State::End, + State::BeforeCursor | State::End => {} + } + } + + /// Returns the collected valid words. + pub fn into_words(self) -> WordKinds { + self.collected + } +} + +/// Returns true if the cursor is at the given token. +/// +/// Cursor is considered to be at a token if it's just before +/// the token or in the middle of it. The only exception is when +/// the cursor is touching a word on the right side. In this +/// case, we want to count the cursor as being at that word. +/// +/// Touching the left side of a word: +/// def Foo(|int[64] x, int[64] y) : {} +/// - at `int` +/// +/// Touching the right side of a word: +/// `def Foo(int|[64] x, int[64] y) : {}` +/// - at `int` +/// +/// In the middle of a word: +/// `def Foo(in|t[64] x , int[64] y) : {}` +/// - at `int` +/// +/// Touching the right side of a non-word: +/// `def Foo(int[64]| x , int[64] y) : {}` +/// - at `x` +/// +/// Between a word and a non-word: +/// `def Foo(|int|[64] x , int[64] y) : {}` +/// - at `int` +/// +/// EOF: +/// `def Foo(|int[64] x , int[64] y) : {}|` +/// - at `EOF` +/// +fn cursor_at_token(cursor_offset: u32, next_token: Token, scanner_offset: u32) -> bool { + match next_token.kind { + // Order matters here as the cases overlap. + TokenKind::Identifier + | TokenKind::Keyword(_) + | TokenKind::GPhase + | TokenKind::DurationOf + | TokenKind::Eof => { + // next token is a word or eof, so count if cursor touches either side of the token + scanner_offset <= cursor_offset && cursor_offset <= next_token.span.hi + } + _ => { + // next token is not a word, so only count if cursor touches left side of token + scanner_offset <= cursor_offset && cursor_offset < next_token.span.hi + } + } +} + +fn eof(offset: u32) -> Token { + Token { + kind: TokenKind::Eof, + span: Span { + lo: offset, + hi: offset, + }, + } +} diff --git a/compiler/qsc_qasm/src/parser/completion/tests.rs b/compiler/qsc_qasm/src/parser/completion/tests.rs new file mode 100644 index 0000000000..fdb6784a94 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/completion/tests.rs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::completion::possible_words_at_offset_in_source; +use expect_test::expect; + +fn get_source_and_cursor(input: &str) -> (String, u32) { + let mut cursor = -1; + let mut source = String::new(); + for c in input.chars() { + if c == '|' { + cursor = i32::try_from(source.len()).expect("input length should fit into u32"); + } else { + source.push(c); + } + } + let cursor = u32::try_from(cursor).expect("missing cursor marker in input"); + (source, cursor) +} + +fn check_valid_words(input: &str, expect: &expect_test::Expect) { + let (input, cursor) = get_source_and_cursor(input); + let w = possible_words_at_offset_in_source(&input, cursor); + expect.assert_debug_eq(&w); +} + +#[test] +fn begin_document() { + check_valid_words( + "|OPENQASM 3;", + &expect![[r#" + WordKinds( + Annotation | Barrier | Box | Break | Cal | Const | Continue | CReg | Ctrl | Def | DefCal | DefCalGrammar | Delay | End | Extern | False | For | Gate | If | Include | Input | Inv | Let | Measure | NegCtrl | OpenQASM | Output | Pow | Pragma | QReg | Qubit | Reset | True | Return | Switch | While, + ) + "#]], + ); +} + +#[test] +fn end_of_version() { + check_valid_words( + "OPENQASM 3;|", + &expect![[r#" + WordKinds( + Annotation | Barrier | Box | Break | Cal | Const | Continue | CReg | Ctrl | Def | DefCal | DefCalGrammar | Delay | End | Extern | False | For | Gate | If | Include | Input | Inv | Let | Measure | NegCtrl | Output | Pow | Pragma | QReg | Qubit | Reset | True | Return | Switch | While, + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/completion/word_kinds.rs b/compiler/qsc_qasm/src/parser/completion/word_kinds.rs new file mode 100644 index 0000000000..fecf011fb8 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/completion/word_kinds.rs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::keyword::Keyword; +use bitflags::bitflags; +use enum_iterator::all; + +bitflags! { + /// + /// Words can be of these kinds: + /// - Names + /// - Hardcoded words: + /// - Keywords + /// - Hardcoded identifiers + /// + /// Names are identifiers or paths that can be resolved to a definition + /// in the code, e.g. callable names, type names, namespaces. + /// + /// Keywords are known words that are not allowed as identifiers, e.g. `function`, `if`. + /// + /// Hardcoded identifiers are treated as identifiers by the parser, but the + /// possible names are hardcoded into the language, e.g. "EntryPoint", "Qubit". + /// + /// IF UPDATING: If new values are added before the keyword range, + /// [`KEYWORDS_START`] *must* be updated. + /// + #[repr(transparent)] + #[derive(Default, PartialEq, Debug, Clone, Copy)] + pub struct WordKinds: u128 { + + // + // Begin names. + // + + /// A path in an expression. Namespaced annotations and pragmas + /// as suggested by the QASM3 spec. Examples: + /// `pragma qsharp.Profile.Base` + /// `@qsharp.SimulatableIntrinsic` + const PathExpr = 1 << 0; + + /// A path segment that follows a `.` + /// A more specific name kind can be inferred from a recovered AST. + const PathSegment = 1 << 1; + + // + // End names. + // + + // + // Begin hardcoded identifiers. + // + + /// An annotation, without the leading `@`. + const Annotation = 1 << 2; + + + // + // End hardcoded identifiers. + // + + // + // Begin keywords. + // + + const Barrier = keyword_bit(Keyword::Barrier); + const Box = keyword_bit(Keyword::Box); + const Break = keyword_bit(Keyword::Break); + const Cal = keyword_bit(Keyword::Cal); + const Case = keyword_bit(Keyword::Case); + const Const = keyword_bit(Keyword::Const); + const Continue = keyword_bit(Keyword::Continue); + const CReg = keyword_bit(Keyword::CReg); + const Ctrl = keyword_bit(Keyword::Ctrl); + const Def = keyword_bit(Keyword::Def); + const DefCal = keyword_bit(Keyword::DefCal); + const DefCalGrammar = keyword_bit(Keyword::DefCalGrammar); + const Default = keyword_bit(Keyword::Default); + const Delay = keyword_bit(Keyword::Delay); + const Else = keyword_bit(Keyword::Else); + const End = keyword_bit(Keyword::End); + const Extern = keyword_bit(Keyword::Extern); + const False = keyword_bit(Keyword::False); + const For = keyword_bit(Keyword::For); + const Gate = keyword_bit(Keyword::Gate); + const If = keyword_bit(Keyword::If); + const In = keyword_bit(Keyword::In); + const Include = keyword_bit(Keyword::Include); + const Input = keyword_bit(Keyword::Input); + const Inv = keyword_bit(Keyword::Inv); + const Let = keyword_bit(Keyword::Let); + const Measure = keyword_bit(Keyword::Measure); + const Mutable = keyword_bit(Keyword::Mutable); + const NegCtrl = keyword_bit(Keyword::NegCtrl); + const OpenQASM = keyword_bit(Keyword::OpenQASM); + const Output = keyword_bit(Keyword::Output); + const Pow = keyword_bit(Keyword::Pow); + const Pragma = keyword_bit(Keyword::Pragma); + const QReg = keyword_bit(Keyword::QReg); + const Qubit = keyword_bit(Keyword::Qubit); + const Reset = keyword_bit(Keyword::Reset); + const True = keyword_bit(Keyword::True); + const ReadOnly = keyword_bit(Keyword::ReadOnly); + const Return = keyword_bit(Keyword::Return); + const Switch = keyword_bit(Keyword::Switch); + const Void = keyword_bit(Keyword::Void); + const While = keyword_bit(Keyword::While); + } +} + +const KEYWORDS_START: u8 = 3; +const fn keyword_bit(k: Keyword) -> u128 { + 1 << (k as u8 + KEYWORDS_START) +} + +impl From for WordKinds { + fn from(k: Keyword) -> Self { + Self::from_bits_truncate(keyword_bit(k)) + } +} + +impl WordKinds { + /// Returns only the name kinds that this prediction set contains. + pub fn iter_name_kinds(&self) -> impl Iterator + '_ { + self.iter().filter_map(|p| match p { + WordKinds::PathExpr => Some(NameKind::Path(PathKind::Expr)), + WordKinds::PathSegment => Some(NameKind::PathSegment), + _ => None, + }) + } + + /// Returns only the hardcoded identifier kinds that this prediction set contains. + pub fn iter_hardcoded_ident_kinds(&self) -> impl Iterator + '_ { + self.iter().filter_map(|p| match p { + WordKinds::Annotation => Some(HardcodedIdentKind::Annotation), + _ => None, + }) + } + + /// Returns only the keywords that this prediction set contains. + pub fn iter_keywords(&self) -> impl Iterator + '_ { + all::().filter(|k| self.contains((*k).into())) + } +} + +/// A hardcoded identifier. +/// +/// Maps to a subset of values in [`Predictions`], but an enum +/// for friendly consumption. +pub enum HardcodedIdentKind { + /// An attribute, without the leading `@`. + Annotation, +} + +/// A name (see: [`Predictions`]) +/// +/// Maps to a subset of values in [`Predictions`], but an enum +/// for friendly consumption. +pub enum NameKind { + /// A path. + Path(PathKind), + /// A path segment that follows a `.` + /// A more specific name kind can only be inferred from a recovered AST. + PathSegment, +} + +/// A path (see: [`Predictions`]) +/// +/// Maps to a subset of values in [`Predictions`], but an enum +/// for friendly consumption. +#[derive(Debug, Clone, Copy)] +pub enum PathKind { + /// A path in an expression. Callables, UDT constructors, local variables. + Expr, +} diff --git a/compiler/qsc_qasm/src/parser/error.rs b/compiler/qsc_qasm/src/parser/error.rs new file mode 100644 index 0000000000..93d9112f93 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/error.rs @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use miette::Diagnostic; +use qsc_data_structures::span::Span; +use thiserror::Error; + +use crate::lex::{self, TokenKind}; + +#[derive(Clone, Eq, Error, PartialEq)] +pub struct Error(pub ErrorKind, pub Option); + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ErrorKind::fmt(&self.0, f) + } +} + +impl std::fmt::Debug for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut formatter = f.debug_tuple("Error"); + if self.1.is_some() { + formatter.field(&self.0).field(&self.1) + } else { + formatter.field(&self.0) + } + .finish() + } +} + +impl Diagnostic for Error { + fn code<'a>(&'a self) -> Option> { + self.0.code() + } + + fn severity(&self) -> Option { + self.0.severity() + } + + fn help<'a>(&'a self) -> Option> { + self.1 + .clone() + .map(|help| Box::new(help) as Box) + } + + fn url<'a>(&'a self) -> Option> { + self.0.url() + } + + fn source_code(&self) -> Option<&dyn miette::SourceCode> { + self.0.source_code() + } + + fn labels(&self) -> Option + '_>> { + self.0.labels() + } + + fn related<'a>(&'a self) -> Option + 'a>> { + self.0.related() + } + + fn diagnostic_source(&self) -> Option<&dyn Diagnostic> { + self.0.diagnostic_source() + } +} + +impl Error { + #[must_use] + pub fn with_offset(self, offset: u32) -> Self { + Self(self.0.with_offset(offset), self.1) + } + + #[must_use] + pub(crate) fn new(kind: ErrorKind) -> Self { + Self(kind, None) + } + + #[must_use] + pub fn with_help(self, help_text: impl Into) -> Self { + Self(self.0, Some(help_text.into())) + } +} + +#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] +pub enum ErrorKind { + #[error(transparent)] + #[diagnostic(transparent)] + Lex(lex::Error), + #[error("invalid {0} literal")] + #[diagnostic(code("Qasm.Parser.Literal"))] + Lit(&'static str, #[label] Span), + #[error("unknown escape sequence: `{0}`")] + #[diagnostic(code("Qasm.Parser.Escape"))] + Escape(char, #[label] Span), + #[error("expected {0}, found {1}")] + #[diagnostic(code("Qasm.Parser.Token"))] + Token(TokenKind, TokenKind, #[label] Span), + #[error("Empty statements are not supported")] + #[diagnostic(code("Qasm.Parser.EmptyStatement"))] + EmptyStatement(#[label] Span), + #[error("Annotation missing target statement.")] + #[diagnostic(code("Qasm.Parser.FloatingAnnotation"))] + FloatingAnnotation(#[label] Span), + #[error("expected {0}, found {1}")] + #[diagnostic(code("Qasm.Parser.Rule"))] + Rule(&'static str, TokenKind, #[label] Span), + #[error("expected {0}, found {1}")] + #[diagnostic(code("Qasm.Parser.Convert"))] + Convert(&'static str, &'static str, #[label] Span), + #[error("expected statement to end with a semicolon")] + #[diagnostic(code("Qasm.Parser.MissingSemi"))] + MissingSemi(#[label] Span), + #[error("expected inputs to be parenthesized")] + #[diagnostic(code("Qasm.Parser.MissingParens"))] + MissingParens(#[label] Span), + #[error("missing entry in sequence")] + #[diagnostic(code("Qasm.Parser.MissingSeqEntry"))] + MissingSeqEntry(#[label] Span), + #[error("missing switch statement cases")] + #[diagnostic(code("Qasm.Parser.MissingSwitchCases"))] + MissingSwitchCases(#[label] Span), + #[error("missing switch statement case labels")] + #[diagnostic(code("Qasm.Parser.MissingSwitchCaseLabels"))] + MissingSwitchCaseLabels(#[label] Span), + #[error("missing gate call operands")] + #[diagnostic(code("Qasm.Parser.MissingGateCallOperands"))] + MissingGateCallOperands(#[label] Span), + #[error("expected an item or closing brace, found {0}")] + #[diagnostic(code("Qasm.Parser.ExpectedItem"))] + ExpectedItem(TokenKind, #[label] Span), + #[error("gphase gate requires exactly one angle")] + #[diagnostic(code("Qasm.Parser.GPhaseInvalidArguments"))] + GPhaseInvalidArguments(#[label] Span), + #[error("invalid gate call designator")] + #[diagnostic(code("Qasm.Parser.InvalidGateCallDesignator"))] + InvalidGateCallDesignator(#[label] Span), + #[error("multiple index operators are only allowed in assignments")] + #[diagnostic(code("Qasm.Parser.MultipleIndexOperators"))] + MultipleIndexOperators(#[label] Span), + #[error(transparent)] + #[diagnostic(transparent)] + IO(#[from] crate::io::Error), +} + +impl ErrorKind { + fn with_offset(self, offset: u32) -> Self { + match self { + Self::Lex(error) => Self::Lex(error.with_offset(offset)), + Self::Lit(name, span) => Self::Lit(name, span + offset), + Self::Escape(ch, span) => Self::Escape(ch, span + offset), + Self::Token(expected, actual, span) => Self::Token(expected, actual, span + offset), + Self::EmptyStatement(span) => Self::EmptyStatement(span + offset), + Self::Rule(name, token, span) => Self::Rule(name, token, span + offset), + Self::Convert(expected, actual, span) => Self::Convert(expected, actual, span + offset), + Self::MissingSemi(span) => Self::MissingSemi(span + offset), + Self::MissingParens(span) => Self::MissingParens(span + offset), + Self::FloatingAnnotation(span) => Self::FloatingAnnotation(span + offset), + Self::MissingSeqEntry(span) => Self::MissingSeqEntry(span + offset), + Self::MissingSwitchCases(span) => Self::MissingSwitchCases(span + offset), + Self::MissingSwitchCaseLabels(span) => Self::MissingSwitchCaseLabels(span + offset), + Self::MissingGateCallOperands(span) => Self::MissingGateCallOperands(span + offset), + Self::ExpectedItem(token, span) => Self::ExpectedItem(token, span + offset), + Self::GPhaseInvalidArguments(span) => Self::GPhaseInvalidArguments(span + offset), + Self::InvalidGateCallDesignator(span) => Self::InvalidGateCallDesignator(span + offset), + Self::MultipleIndexOperators(span) => Self::MultipleIndexOperators(span + offset), + Self::IO(error) => Self::IO(error), + } + } +} + +impl From for crate::Error { + fn from(val: Error) -> Self { + crate::Error(crate::ErrorKind::Parser(val)) + } +} diff --git a/compiler/qsc_qasm/src/parser/expr.rs b/compiler/qsc_qasm/src/parser/expr.rs new file mode 100644 index 0000000000..a2c14317ae --- /dev/null +++ b/compiler/qsc_qasm/src/parser/expr.rs @@ -0,0 +1,820 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Expression parsing makes use of Pratt parsing (or “top-down operator-precedence parsing”) to handle +//! relative precedence of operators. + +#[cfg(test)] +pub(crate) mod tests; + +use num_bigint::BigInt; +use num_traits::Num; +use qsc_data_structures::span::Span; + +use crate::{ + keyword::Keyword, + lex::{ + cooked::{ComparisonOp, Literal, TimingLiteralKind}, + ClosedBinOp, Delim, Radix, Token, TokenKind, + }, +}; + +use crate::parser::Result; + +use super::{ + ast::{ + list_from_iter, BinOp, BinaryOpExpr, Cast, DiscreteSet, Expr, ExprKind, FunctionCall, + GateOperand, GateOperandKind, HardwareQubit, Ident, IndexElement, IndexExpr, IndexSet, + IndexSetItem, IndexedIdent, List, Lit, LiteralKind, MeasureExpr, RangeDefinition, TimeUnit, + TypeDef, UnaryOp, UnaryOpExpr, ValueExpr, Version, + }, + completion::word_kinds::WordKinds, + error::{Error, ErrorKind}, + prim::{ident, many, opt, recovering_token, seq, shorten, token, FinalSep}, + scan::ParserContext, + stmt::scalar_or_array_type, +}; + +struct PrefixOp { + kind: UnaryOp, + precedence: u8, +} + +struct InfixOp { + kind: OpKind, + precedence: u8, +} + +enum OpKind { + Binary(BinOp, Assoc), + Funcall, + Index, +} + +#[derive(Clone, Copy)] +enum OpName { + Token(TokenKind), + Keyword, +} + +#[derive(Clone, Copy)] +enum Assoc { + Left, + Right, +} + +pub(super) fn expr(s: &mut ParserContext) -> Result { + expr_op(s, 0) +} + +pub(super) fn expr_with_lhs(s: &mut ParserContext, lhs: Expr) -> Result { + expr_op_with_lhs(s, 0, lhs) +} + +fn expr_op(s: &mut ParserContext, min_precedence: u8) -> Result { + let lo = s.peek().span.lo; + let lhs = if let Some(op) = prefix_op(op_name(s)) { + s.advance(); + let rhs = expr_op(s, op.precedence)?; + Expr { + span: s.span(lo), + kind: Box::new(ExprKind::UnaryOp(UnaryOpExpr { + op: op.kind, + expr: rhs, + })), + } + } else { + expr_base(s)? + }; + + expr_op_with_lhs(s, min_precedence, lhs) +} + +fn expr_op_with_lhs(s: &mut ParserContext, min_precedence: u8, mut lhs: Expr) -> Result { + let lo = lhs.span.lo; + + while let Some(op) = infix_op(op_name(s)) { + if op.precedence < min_precedence { + break; + } + + s.advance(); + let kind = match op.kind { + OpKind::Binary(kind, assoc) => { + let precedence = next_precedence(op.precedence, assoc); + let rhs = expr_op(s, precedence)?; + Box::new(ExprKind::BinaryOp(BinaryOpExpr { op: kind, lhs, rhs })) + } + OpKind::Funcall => { + if let ExprKind::Ident(ident) = *lhs.kind { + Box::new(funcall(s, ident)?) + } else { + return Err(Error::new(ErrorKind::Convert("identifier", "", lhs.span))); + } + } + OpKind::Index => Box::new(index_expr(s, lhs)?), + }; + + lhs = Expr { + span: s.span(lo), + kind, + }; + } + + Ok(lhs) +} + +fn expr_base(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + if let Some(l) = lit(s)? { + Ok(Expr { + span: s.span(lo), + kind: Box::new(ExprKind::Lit(l)), + }) + } else if token(s, TokenKind::Open(Delim::Paren)).is_ok() { + paren_expr(s, lo) + } else { + match opt(s, scalar_or_array_type) { + Err(err) => Err(err), + Ok(Some(r#type)) => { + // If we have a type, we expect to see a + // parenthesized expression next. + let kind = Box::new(cast_op(s, r#type)?); + Ok(Expr { + span: s.span(lo), + kind, + }) + } + Ok(None) => { + if let Ok(id) = ident(s) { + Ok(Expr { + span: s.span(lo), + kind: Box::new(ExprKind::Ident(id)), + }) + } else { + Err(Error::new(ErrorKind::Rule( + "expression", + s.peek().kind, + s.peek().span, + ))) + } + } + } + } +} + +pub(super) fn lit(s: &mut ParserContext) -> Result> { + let lexeme = s.read(); + + s.expect(WordKinds::True | WordKinds::False); + + let token = s.peek(); + match lit_token(lexeme, token) { + Ok(Some(lit)) => { + s.advance(); + Ok(Some(lit)) + } + Ok(None) => Ok(None), + Err(err) => { + s.advance(); + Err(err) + } + } +} + +pub(super) fn version(s: &mut ParserContext) -> Result> { + let lexeme = s.read(); + let token = s.peek(); + match version_token(lexeme, token) { + Ok(Some(lit)) => { + s.advance(); + Ok(Some(lit)) + } + Ok(None) => Ok(None), + Err(err) => { + s.advance(); + Err(err) + } + } +} + +#[allow(clippy::inline_always)] +#[inline(always)] +fn lit_token(lexeme: &str, token: Token) -> Result> { + match token.kind { + TokenKind::Literal(literal) => match literal { + Literal::Integer(radix) => { + let offset = if radix == Radix::Decimal { 0 } else { 2 }; + let value = lit_int(&lexeme[offset..], radix.into()); + if let Some(value) = value { + Ok(Some(Lit { + kind: LiteralKind::Int(value), + span: token.span, + })) + } else if let Some(value) = lit_bigint(&lexeme[offset..], radix.into()) { + Ok(Some(Lit { + kind: LiteralKind::BigInt(value), + span: token.span, + })) + } else { + Err(Error::new(ErrorKind::Lit("integer", token.span))) + } + } + Literal::Float => { + let lexeme = lexeme.replace('_', ""); + let value = lexeme + .parse() + .map_err(|_| Error::new(ErrorKind::Lit("floating-point", token.span)))?; + Ok(Some(Lit { + kind: LiteralKind::Float(value), + span: token.span, + })) + } + Literal::String => { + let lexeme = shorten(1, 1, lexeme); + let string = unescape(lexeme).map_err(|index| { + let ch = lexeme[index + 1..] + .chars() + .next() + .expect("character should be found at index"); + let index: u32 = index.try_into().expect("index should fit into u32"); + let lo = token.span.lo + index + 2; + let span = Span { lo, hi: lo + 1 }; + Error::new(ErrorKind::Escape(ch, span)) + })?; + Ok(Some(Lit { + kind: LiteralKind::String(string.into()), + span: token.span, + })) + } + Literal::Bitstring => { + let lexeme = shorten(1, 1, lexeme); + let width = u32::try_from( + lexeme + .to_string() + .chars() + .filter(|c| *c == '0' || *c == '1') + .count(), + ) + .map_err(|_| Error::new(ErrorKind::Lit("bitstring", token.span)))?; + + // parse it to validate the bitstring + let value = BigInt::from_str_radix(lexeme, 2) + .map_err(|_| Error::new(ErrorKind::Lit("bitstring", token.span)))?; + + Ok(Some(Lit { + span: token.span, + kind: LiteralKind::Bitstring(value, width), + })) + } + Literal::Imaginary => { + let lexeme = lexeme + .chars() + .filter(|x| *x != '_') + .take_while(|x| x.is_numeric() || *x == '.') + .collect::(); + + let value = lexeme + .parse() + .map_err(|_| Error::new(ErrorKind::Lit("imaginary", token.span)))?; + Ok(Some(Lit { + kind: LiteralKind::Imaginary(value), + span: token.span, + })) + } + Literal::Timing(kind) => timing_literal(lexeme, token, kind), + }, + TokenKind::Keyword(Keyword::True) => Ok(Some(Lit { + kind: LiteralKind::Bool(true), + span: token.span, + })), + TokenKind::Keyword(Keyword::False) => Ok(Some(Lit { + kind: LiteralKind::Bool(false), + span: token.span, + })), + _ => Ok(None), + } +} + +pub(super) fn version_token(lexeme: &str, token: Token) -> Result> { + match token.kind { + TokenKind::Literal(literal) => { + if let Literal::Float = literal { + // validate the version number is in the form of `x.y` + let (major, minor) = split_and_parse_numbers(lexeme, token)?; + Ok(Some(Version { + major, + minor: Some(minor), + span: token.span, + })) + } else if let Literal::Integer(radix) = literal { + if radix != Radix::Decimal { + return Err(Error::new(ErrorKind::Lit("version", token.span))); + } + let major = lexeme + .parse::() + .map_err(|_| Error::new(ErrorKind::Lit("version", token.span)))?; + + Ok(Some(Version { + major, + minor: None, + span: token.span, + })) + } else { + Ok(None) + } + } + _ => Ok(None), + } +} + +fn split_and_parse_numbers(lexeme: &str, token: Token) -> Result<(u32, u32)> { + let parts: Vec<&str> = lexeme.split('.').collect(); + if parts.len() != 2 { + return Err(Error::new(ErrorKind::Lit("version", token.span))); + } + + let left = parts[0] + .parse::() + .map_err(|_| Error::new(ErrorKind::Lit("version major", token.span)))?; + let right = parts[1] + .parse::() + .map_err(|_| Error::new(ErrorKind::Lit("version minor", token.span)))?; + + Ok((left, right)) +} + +fn lit_int(lexeme: &str, radix: u32) -> Option { + let multiplier = i64::from(radix); + lexeme + .chars() + .filter(|&c| c != '_') + .try_rfold((0i64, 1i64, false), |(value, place, mut overflow), c| { + let (increment, over) = i64::from(c.to_digit(radix)?).overflowing_mul(place); + overflow |= over; + + let (new_value, over) = value.overflowing_add(increment); + overflow |= over; + + // Only treat as overflow if the value is not i64::MIN, since we need to allow once special + // case of overflow to allow for minimum value literals. + if overflow && new_value != i64::MIN { + return None; + } + + let (new_place, over) = place.overflowing_mul(multiplier); + overflow |= over; + + // If the place overflows, we can still accept the value as long as it's the last digit. + // Pass the overflow forward so that it fails if there are more digits. + Some((new_value, new_place, overflow)) + }) + .map(|(value, _, _)| value) +} + +fn lit_bigint(lexeme: &str, radix: u32) -> Option { + // from_str_radix does removes underscores as long as the lexeme + // doesn't start with an underscore. + BigInt::from_str_radix(lexeme, radix).ok() +} + +fn timing_literal(lexeme: &str, token: Token, kind: TimingLiteralKind) -> Result> { + let lexeme = lexeme + .chars() + .filter(|x| *x != '_') + .take_while(|x| x.is_numeric() || *x == '.') + .collect::(); + + let value = lexeme + .parse() + .map_err(|_| Error::new(ErrorKind::Lit("timing", token.span)))?; + + let unit = match kind { + TimingLiteralKind::Dt => TimeUnit::Dt, + TimingLiteralKind::Ns => TimeUnit::Ns, + TimingLiteralKind::Us => TimeUnit::Us, + TimingLiteralKind::Ms => TimeUnit::Ms, + TimingLiteralKind::S => TimeUnit::S, + }; + + Ok(Some(Lit { + span: token.span, + kind: LiteralKind::Duration(value, unit), + })) +} + +pub(crate) fn paren_expr(s: &mut ParserContext, lo: u32) -> Result { + let (mut exprs, final_sep) = seq(s, expr)?; + token(s, TokenKind::Close(Delim::Paren))?; + + let kind = if final_sep == FinalSep::Missing && exprs.len() == 1 { + ExprKind::Paren(exprs.pop().expect("vector should have exactly one item")) + } else { + return Err(Error::new(ErrorKind::Convert( + "parenthesized expression", + "expression list", + s.span(lo), + ))); + }; + + Ok(Expr { + span: s.span(lo), + kind: Box::new(kind), + }) +} + +fn funcall(s: &mut ParserContext, ident: Ident) -> Result { + let lo = ident.span.lo; + let (args, _) = seq(s, expr)?; + token(s, TokenKind::Close(Delim::Paren))?; + Ok(ExprKind::FunctionCall(FunctionCall { + span: s.span(lo), + name: ident, + args: args.into_iter().map(Box::new).collect(), + })) +} + +fn cast_op(s: &mut ParserContext, r#type: TypeDef) -> Result { + let lo = r#type.span().lo; + token(s, TokenKind::Open(Delim::Paren))?; + let arg = expr(s)?; + recovering_token(s, TokenKind::Close(Delim::Paren)); + Ok(ExprKind::Cast(Cast { + span: s.span(lo), + ty: r#type, + arg, + })) +} + +fn index_expr(s: &mut ParserContext, lhs: Expr) -> Result { + let lo = lhs.span.lo; + let index = index_element(s)?; + recovering_token(s, TokenKind::Close(Delim::Bracket)); + Ok(ExprKind::IndexExpr(IndexExpr { + span: s.span(lo), + collection: lhs, + index, + })) +} + +fn index_element(s: &mut ParserContext) -> Result { + let index = match opt(s, set_expr) { + Ok(Some(v)) => IndexElement::DiscreteSet(v), + Err(err) => return Err(err), + Ok(None) => { + let lo = s.peek().span.lo; + let (exprs, _) = seq(s, index_set_item)?; + let exprs = list_from_iter(exprs); + IndexElement::IndexSet(IndexSet { + span: s.span(lo), + values: exprs, + }) + } + }; + Ok(index) +} + +/// QASM3 index set items can either of: +/// 1. An expression: arr[2] +/// 2. A range with start and end: arr[start : end] +/// 3. A range with start, step, and end: arr[start : step : end] +/// 4. Additionally, points 2. and 3. can have missing start, step, or step. +/// here are some examples: arr[:], arr[: step :], arr[: step : end] +fn index_set_item(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let start = opt(s, expr)?; + + // If no colon, return the expr as a normal index. + if token(s, TokenKind::Colon).is_err() { + let expr = start.ok_or(Error::new(ErrorKind::Rule( + "expression", + s.peek().kind, + s.span(lo), + )))?; + return Ok(IndexSetItem::Expr(expr)); + } + + // We assume the second expr is the `end`. + let end = opt(s, expr)?; + + // If no colon, return a range with start and end: [start : end]. + if token(s, TokenKind::Colon).is_err() { + return Ok(IndexSetItem::RangeDefinition(RangeDefinition { + span: s.span(lo), + start, + end, + step: None, + })); + } + + // If there was a second colon, the second expression was the step. + let step = end; + let end = opt(s, expr)?; + + Ok(IndexSetItem::RangeDefinition(RangeDefinition { + span: s.span(lo), + start, + end, + step, + })) +} + +pub(crate) fn set_expr(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Open(Delim::Brace))?; + let exprs = expr_list(s)?; + recovering_token(s, TokenKind::Close(Delim::Brace)); + Ok(DiscreteSet { + span: s.span(lo), + values: list_from_iter(exprs), + }) +} + +fn op_name(s: &ParserContext) -> OpName { + match s.peek().kind { + TokenKind::Keyword(_) => OpName::Keyword, + kind => OpName::Token(kind), + } +} + +fn next_precedence(precedence: u8, assoc: Assoc) -> u8 { + match assoc { + Assoc::Left => precedence + 1, + Assoc::Right => precedence, + } +} + +/// The operation precedence table is at +/// . +fn prefix_op(name: OpName) -> Option { + match name { + OpName::Token(TokenKind::Bang) => Some(PrefixOp { + kind: UnaryOp::NotL, + precedence: 11, + }), + OpName::Token(TokenKind::Tilde) => Some(PrefixOp { + kind: UnaryOp::NotB, + precedence: 11, + }), + OpName::Token(TokenKind::ClosedBinOp(ClosedBinOp::Minus)) => Some(PrefixOp { + kind: UnaryOp::Neg, + precedence: 11, + }), + + _ => None, + } +} + +/// The operation precedence table is at +/// . +fn infix_op(name: OpName) -> Option { + fn left_assoc(op: BinOp, precedence: u8) -> Option { + Some(InfixOp { + kind: OpKind::Binary(op, Assoc::Left), + precedence, + }) + } + + let OpName::Token(kind) = name else { + return None; + }; + + match kind { + TokenKind::ClosedBinOp(token) => match token { + ClosedBinOp::StarStar => Some(InfixOp { + kind: OpKind::Binary(BinOp::Exp, Assoc::Right), + precedence: 12, + }), + ClosedBinOp::Star => left_assoc(BinOp::Mul, 10), + ClosedBinOp::Slash => left_assoc(BinOp::Div, 10), + ClosedBinOp::Percent => left_assoc(BinOp::Mod, 10), + ClosedBinOp::Minus => left_assoc(BinOp::Sub, 9), + ClosedBinOp::Plus => left_assoc(BinOp::Add, 9), + ClosedBinOp::LtLt => left_assoc(BinOp::Shl, 8), + ClosedBinOp::GtGt => left_assoc(BinOp::Shr, 8), + ClosedBinOp::Amp => left_assoc(BinOp::AndB, 5), + ClosedBinOp::Bar => left_assoc(BinOp::OrB, 4), + ClosedBinOp::Caret => left_assoc(BinOp::XorB, 3), + ClosedBinOp::AmpAmp => left_assoc(BinOp::AndL, 2), + ClosedBinOp::BarBar => left_assoc(BinOp::OrL, 1), + }, + TokenKind::ComparisonOp(token) => match token { + ComparisonOp::Gt => left_assoc(BinOp::Gt, 7), + ComparisonOp::GtEq => left_assoc(BinOp::Gte, 7), + ComparisonOp::Lt => left_assoc(BinOp::Lt, 7), + ComparisonOp::LtEq => left_assoc(BinOp::Lte, 7), + ComparisonOp::BangEq => left_assoc(BinOp::Neq, 6), + ComparisonOp::EqEq => left_assoc(BinOp::Eq, 6), + }, + TokenKind::Open(Delim::Paren) => Some(InfixOp { + kind: OpKind::Funcall, + precedence: 13, + }), + TokenKind::Open(Delim::Bracket) => Some(InfixOp { + kind: OpKind::Index, + precedence: 13, + }), + _ => None, + } +} + +pub(crate) fn closed_bin_op(op: ClosedBinOp) -> BinOp { + match op { + ClosedBinOp::Amp => BinOp::AndB, + ClosedBinOp::AmpAmp => BinOp::AndL, + ClosedBinOp::Bar => BinOp::OrB, + ClosedBinOp::StarStar => BinOp::Exp, + ClosedBinOp::Caret => BinOp::XorB, + ClosedBinOp::GtGt => BinOp::Shr, + ClosedBinOp::LtLt => BinOp::Shl, + ClosedBinOp::Minus => BinOp::Sub, + ClosedBinOp::BarBar => BinOp::OrL, + ClosedBinOp::Percent => BinOp::Mod, + ClosedBinOp::Plus => BinOp::Add, + ClosedBinOp::Slash => BinOp::Div, + ClosedBinOp::Star => BinOp::Mul, + } +} + +fn unescape(s: &str) -> std::result::Result { + let mut chars = s.char_indices(); + let mut buf = String::with_capacity(s.len()); + while let Some((index, ch)) = chars.next() { + buf.push(if ch == '\\' { + let escape = chars.next().expect("escape should not be empty").1; + match escape { + '\\' => '\\', + '\'' => '\'', + '"' => '"', + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + _ => return Err(index), + } + } else { + ch + }); + } + + Ok(buf) +} + +/// Grammar: `LBRACKET expression RBRACKET`. +pub(super) fn designator(s: &mut ParserContext) -> Result { + token(s, TokenKind::Open(Delim::Bracket))?; + let expr = expr(s)?; + recovering_token(s, TokenKind::Close(Delim::Bracket)); + Ok(expr) +} + +/// A literal array is a list of literal array elements. +fn lit_array(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Open(Delim::Brace))?; + let elements = seq(s, lit_array_element).map(|pair| pair.0)?; + recovering_token(s, TokenKind::Close(Delim::Brace)); + Ok(Expr { + span: s.span(lo), + kind: Box::new(ExprKind::Lit(Lit { + span: s.span(lo), + kind: LiteralKind::Array(list_from_iter(elements)), + })), + }) +} + +/// A literal array element can be an expression, or a literal array element. +fn lit_array_element(s: &mut ParserContext) -> Result { + if let Some(elt) = opt(s, expr)? { + return Ok(elt); + } + lit_array(s) +} + +/// These are expressions allowed in classical declarations. +/// Grammar: `arrayLiteral | expression | measureExpression`. +pub(super) fn declaration_expr(s: &mut ParserContext) -> Result { + if let Some(measurement) = opt(s, measure_expr)? { + return Ok(ValueExpr::Measurement(measurement)); + } + + let expr = if let Some(expr) = opt(s, expr)? { + expr + } else { + lit_array(s)? + }; + + Ok(ValueExpr::Expr(expr)) +} + +/// These are expressions allowed in constant classical declarations. +/// Note, that the spec doesn't specify that measurements are not allowed +/// here, but this is a spec bug, since measuremnts can't be performed at +/// compile time. +pub(super) fn const_declaration_expr(s: &mut ParserContext) -> Result { + let expr = if let Some(expr) = opt(s, expr)? { + expr + } else { + lit_array(s)? + }; + + Ok(ValueExpr::Expr(expr)) +} + +/// These are expressions allowed in `Assign`, `AssignOp`, and return stmts. +/// Grammar: `expression | measureExpression`. +pub(super) fn expr_or_measurement(s: &mut ParserContext) -> Result { + if let Some(measurement) = opt(s, measure_expr)? { + return Ok(ValueExpr::Measurement(measurement)); + } + + Ok(ValueExpr::Expr(expr(s)?)) +} + +pub(crate) fn expr_list(s: &mut ParserContext) -> Result> { + seq(s, expr).map(|pair| pair.0) +} + +pub(crate) fn measure_expr(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::Measure))?; + let measure_token_span = s.span(lo); + let operand = gate_operand(s)?; + + Ok(MeasureExpr { + span: s.span(lo), + measure_token_span, + operand, + }) +} + +pub(crate) fn gate_operand(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let kind = if let Some(indexed_ident) = opt(s, indexed_identifier)? { + GateOperandKind::IndexedIdent(Box::new(indexed_ident)) + } else { + GateOperandKind::HardwareQubit(Box::new(hardware_qubit(s)?)) + }; + + Ok(GateOperand { + span: s.span(lo), + kind, + }) +} + +fn hardware_qubit(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let hardware_qubit = s.read(); + token(s, TokenKind::HardwareQubit)?; + + Ok(HardwareQubit { + span: s.span(lo), + name: hardware_qubit[1..].into(), + }) +} + +/// Grammar: `Identifier indexOperator*`. +pub(crate) fn indexed_identifier(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let name: Ident = ident(s)?; + let index_lo = s.peek().span.lo; + let indices = list_from_iter(many(s, index_operand)?); + let index_span = if indices.is_empty() { + Span::default() + } else { + s.span(index_lo) + }; + Ok(IndexedIdent { + span: s.span(lo), + index_span, + name, + indices, + }) +} + +/// Grammar: +/// ```g4 +/// LBRACKET +/// ( +/// setExpression +/// | (expression | rangeExpression) (COMMA (expression | rangeExpression))* COMMA? +/// ) +/// RBRACKET +/// ``` +fn index_operand(s: &mut ParserContext) -> Result { + token(s, TokenKind::Open(Delim::Bracket))?; + let index = index_element(s)?; + recovering_token(s, TokenKind::Close(Delim::Bracket)); + Ok(index) +} + +/// This expressions are not part of the expression tree +/// and are only used in alias statements. +/// Grammar: `expression (DOUBLE_PLUS expression)*`. +pub fn alias_expr(s: &mut ParserContext) -> Result> { + let mut exprs = Vec::new(); + exprs.push(expr(s)?); + while opt(s, |s| token(s, TokenKind::PlusPlus))?.is_some() { + exprs.push(expr(s)?); + } + Ok(list_from_iter(exprs)) +} diff --git a/compiler/qsc_qasm/src/parser/expr/tests.rs b/compiler/qsc_qasm/src/parser/expr/tests.rs new file mode 100644 index 0000000000..29e92bb9cd --- /dev/null +++ b/compiler/qsc_qasm/src/parser/expr/tests.rs @@ -0,0 +1,1192 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use super::expr; +use crate::{ + parser::ast::StmtKind, + parser::{scan::ParserContext, stmt, tests::check}, +}; +use expect_test::{expect, Expect}; + +/// This function checks two things: +/// 1. That the input `Expr` is parsed correctly. +/// 2. That if we add a semicolon at the end it parses correctly as a `ExprStmt` +/// containing the same `Expr` inside. +fn check_expr(input: &str, expect: &Expect) { + // Do the usual expect test check. + check(expr, input, expect); + + // Parse the expr with the expr parser. + let expr = expr(&mut ParserContext::new(input)).map(Some); + + // Add a semicolon and parser with the stmt parser. + let expr_stmt = stmt::parse(&mut ParserContext::new(&format!("{input};"))); + + // Extract the inner expr. + let inner_expr = expr_stmt.map(|ok| match *ok.kind { + StmtKind::ExprStmt(expr) => Some(expr.expr), + _ => None, + }); + + // Check that they are equal. + assert_eq!(format!("{expr:?}"), format!("{inner_expr:?}")); +} + +#[test] +fn lit_int() { + check_expr("123", &expect!["Expr [0-3]: Lit: Int(123)"]); +} + +#[test] +fn lit_int_underscore() { + check_expr("123_456", &expect!["Expr [0-7]: Lit: Int(123456)"]); +} + +#[test] +fn lit_int_leading_zero() { + check_expr("0123", &expect!["Expr [0-4]: Lit: Int(123)"]); +} + +#[test] +fn lit_int_max() { + check_expr( + "9_223_372_036_854_775_807", + &expect!["Expr [0-25]: Lit: Int(9223372036854775807)"], + ); +} + +// NOTE: Since we need to support literals of value i64::MIN while also parsing the negative sign +// as a unary operator, we need to allow one special case of overflow that is the absolute value +// of i64::MIN. This will wrap to a negative value. See the `lit_int_min` test below. +// To check for other issues with handling i64::MIN, hexadecimal and binary literals +// of i64::MIN also need to be tested. +#[test] +fn lit_int_overflow_min() { + check_expr( + "9_223_372_036_854_775_808", + &expect!["Expr [0-25]: Lit: Int(-9223372036854775808)"], + ); +} + +#[test] +fn lit_int_overflow_min_hexadecimal() { + check_expr( + "0x8000000000000000", + &expect!["Expr [0-18]: Lit: Int(-9223372036854775808)"], + ); +} + +#[test] +fn lit_int_overflow_min_binary() { + check_expr( + "0b1000000000000000000000000000000000000000000000000000000000000000", + &expect!["Expr [0-66]: Lit: Int(-9223372036854775808)"], + ); +} + +#[test] +fn lit_int_too_big_for_i64() { + check_expr( + "9_223_372_036_854_775_809", + &expect!["Expr [0-25]: Lit: BigInt(9223372036854775809)"], + ); +} + +#[test] +fn lit_int_too_big_hexadecimal_promotes_to_bigint() { + check_expr( + "0x8000000000000001", + &expect!["Expr [0-18]: Lit: BigInt(9223372036854775809)"], + ); +} + +#[test] +fn lit_int_too_big_binary_promotes_to_bigint() { + check_expr( + "0b1000000000000000000000000000000000000000000000000000000000000001", + &expect!["Expr [0-66]: Lit: BigInt(9223372036854775809)"], + ); +} + +// NOTE: Since we need to support literals of value i64::MIN while also parsing the negative sign +// as a unary operator, we need to allow one special case of overflow that is the absolute value +// of i64::MIN. This will wrap to a negative value, and then negate of i64::MIN is i64::MIN, so +// the correct value is achieved at runtime. +#[test] +fn lit_int_min() { + check_expr( + "-9_223_372_036_854_775_808", + &expect![[r#" + Expr [0-26]: UnaryOpExpr: + op: Neg + expr: Expr [1-26]: Lit: Int(-9223372036854775808)"#]], + ); +} + +#[test] +fn lit_int_hexadecimal() { + check_expr("0x1a2b3c", &expect!["Expr [0-8]: Lit: Int(1715004)"]); +} + +#[test] +fn lit_int_octal() { + check_expr("0o1234567", &expect!["Expr [0-9]: Lit: Int(342391)"]); +} + +#[test] +fn lit_int_binary() { + check_expr("0b10110", &expect!["Expr [0-7]: Lit: Int(22)"]); +} + +#[test] +fn lit_bigint_hexadecimal() { + check_expr( + "0x1a2b3c1a2b3c1a2b3c1a", + &expect!["Expr [0-22]: Lit: BigInt(123579069371309093501978)"], + ); +} + +#[test] +fn lit_bigint_hexadecimal_capital_x() { + check_expr( + "0X1a2b3c1a2b3c1a2b3c1a", + &expect!["Expr [0-22]: Lit: BigInt(123579069371309093501978)"], + ); +} + +#[test] +fn lit_bigint_octal() { + check_expr( + "0o1234567123456712345671234", + &expect!["Expr [0-27]: Lit: BigInt(6167970861177743307420)"], + ); +} + +#[test] +fn lit_bigint_octal_capital_o() { + check_expr( + "0O1234567123456712345671234", + &expect!["Expr [0-27]: Lit: BigInt(6167970861177743307420)"], + ); +} + +#[test] +fn lit_bigint_binary() { + check_expr( + "0b1011010110101101011010110101101011010110101101011010110101101011", + &expect!["Expr [0-66]: Lit: BigInt(13091237729729359211)"], + ); +} + +#[test] +fn lit_bigint_binary_capital_b() { + check_expr( + "0B1011010110101101011010110101101011010110101101011010110101101011", + &expect!["Expr [0-66]: Lit: BigInt(13091237729729359211)"], + ); +} + +#[test] +fn lit_float() { + check_expr("1.23", &expect!["Expr [0-4]: Lit: Float(1.23)"]); +} + +#[test] +fn lit_float_leading_dot() { + check_expr(".23", &expect!["Expr [0-3]: Lit: Float(0.23)"]); +} + +#[test] +fn lit_float_trailing_dot() { + check_expr("1.", &expect!["Expr [0-2]: Lit: Float(1.0)"]); +} + +#[test] +fn lit_float_underscore() { + check_expr("123_456.78", &expect!["Expr [0-10]: Lit: Float(123456.78)"]); +} + +#[test] +fn lit_float_leading_zero() { + check_expr("0.23", &expect!["Expr [0-4]: Lit: Float(0.23)"]); +} + +#[test] +fn lit_float_trailing_exp_0() { + check_expr( + "0e", + &expect![[r#" + Error( + Lit( + "floating-point", + Span { + lo: 0, + hi: 2, + }, + ), + ) + "#]], + ); +} + +#[test] +fn lit_float_trailing_exp_1() { + check_expr( + "1e", + &expect![[r#" + Error( + Lit( + "floating-point", + Span { + lo: 0, + hi: 2, + }, + ), + ) + "#]], + ); +} + +#[test] +fn lit_float_trailing_dot_trailing_exp() { + check_expr( + "1.e", + &expect![[r#" + Error( + Lit( + "floating-point", + Span { + lo: 0, + hi: 3, + }, + ), + ) + "#]], + ); +} + +#[test] +fn lit_float_dot_trailing_exp() { + check_expr( + "1.2e", + &expect![[r#" + Error( + Lit( + "floating-point", + Span { + lo: 0, + hi: 4, + }, + ), + ) + "#]], + ); +} + +#[test] +fn lit_float_trailing_exp_dot() { + check_expr( + "1e.", + &expect![[r#" + Error( + Lit( + "floating-point", + Span { + lo: 0, + hi: 2, + }, + ), + ) + "#]], + ); +} + +#[test] +fn lit_int_hexadecimal_dot() { + check_expr("0x123.45", &expect!["Expr [0-5]: Lit: Int(291)"]); +} + +#[test] +fn lit_string() { + check_expr(r#""foo""#, &expect![[r#"Expr [0-5]: Lit: String("foo")"#]]); +} + +#[test] +fn lit_string_single_quote() { + check_expr(r#"'foo'"#, &expect![[r#"Expr [0-5]: Lit: String("foo")"#]]); +} + +#[test] +fn lit_string_escape_quote() { + check_expr( + r#""foo\"bar""#, + &expect![[r#"Expr [0-10]: Lit: String("foo\"bar")"#]], + ); +} + +#[test] +fn lit_string_single_quote_escape_double_quote() { + check_expr( + r#"'foo\"bar'"#, + &expect![[r#"Expr [0-10]: Lit: String("foo\"bar")"#]], + ); +} + +#[test] +fn lit_string_escape_backslash() { + check_expr(r#""\\""#, &expect![[r#"Expr [0-4]: Lit: String("\\")"#]]); +} + +#[test] +fn lit_string_single_quote_escape_backslash() { + check_expr(r#"'\\'"#, &expect![[r#"Expr [0-4]: Lit: String("\\")"#]]); +} + +#[test] +fn lit_string_escape_newline() { + check_expr(r#""\n""#, &expect![[r#"Expr [0-4]: Lit: String("\n")"#]]); +} + +#[test] +fn lit_string_single_quote_escape_newline() { + check_expr(r#"'\n'"#, &expect![[r#"Expr [0-4]: Lit: String("\n")"#]]); +} + +#[test] +fn lit_string_escape_carriage_return() { + check_expr(r#""\r""#, &expect![[r#"Expr [0-4]: Lit: String("\r")"#]]); +} + +#[test] +fn lit_string_single_quote_escape_carriage_return() { + check_expr(r#"'\r'"#, &expect![[r#"Expr [0-4]: Lit: String("\r")"#]]); +} + +#[test] +fn lit_string_escape_tab() { + check_expr(r#""\t""#, &expect![[r#"Expr [0-4]: Lit: String("\t")"#]]); +} + +#[test] +fn lit_string_single_quote_escape_tab() { + check_expr(r#"'\t'"#, &expect![[r#"Expr [0-4]: Lit: String("\t")"#]]); +} + +#[test] +fn lit_string_unknown_escape() { + check_expr( + r#""\x""#, + &expect![[r#" + Error( + Escape( + 'x', + Span { + lo: 2, + hi: 3, + }, + ), + ) + "#]], + ); +} + +#[test] +fn lit_string_unmatched_quote() { + check( + expr, + r#""Uh oh.."#, + &expect![[r#" + Error( + Rule( + "expression", + Eof, + Span { + lo: 8, + hi: 8, + }, + ), + ) + + [ + Error( + Lex( + UnterminatedString( + Span { + lo: 0, + hi: 0, + }, + ), + ), + ), + ]"#]], + ); +} + +#[test] +fn lit_string_empty() { + check_expr(r#""""#, &expect![[r#"Expr [0-2]: Lit: String("")"#]]); +} + +#[test] +fn lit_false() { + check_expr("false", &expect!["Expr [0-5]: Lit: Bool(false)"]); +} + +#[test] +fn lit_true() { + check_expr("true", &expect!["Expr [0-4]: Lit: Bool(true)"]); +} + +#[test] +fn lit_bitstring() { + check_expr( + r#""101010101""#, + &expect![[r#"Expr [0-11]: Lit: Bitstring("101010101")"#]], + ); +} + +#[test] +fn lit_bitstring_preserves_leading_zeroes() { + check_expr( + r#""00011000""#, + &expect![[r#"Expr [0-10]: Lit: Bitstring("00011000")"#]], + ); +} + +#[test] +fn lit_bitstring_separators() { + check_expr( + r#""10_10_10_101""#, + &expect![[r#"Expr [0-14]: Lit: Bitstring("101010101")"#]], + ); +} + +#[test] +fn lit_bitstring_unmatched_quote() { + check( + expr, + r#""101010101"#, + &expect![[r#" + Error( + Rule( + "expression", + Eof, + Span { + lo: 10, + hi: 10, + }, + ), + ) + + [ + Error( + Lex( + UnterminatedString( + Span { + lo: 0, + hi: 0, + }, + ), + ), + ), + ]"#]], + ); +} + +#[test] +fn lit_float_imag() { + check_expr(r#"10.3im"#, &expect!["Expr [0-6]: Lit: Imaginary(10.3)"]); +} + +#[test] +fn lit_float_imag_with_spacing() { + check_expr(r#"10.3 im"#, &expect!["Expr [0-8]: Lit: Imaginary(10.3)"]); +} + +#[test] +fn lit_int_imag() { + check_expr(r#"10im"#, &expect!["Expr [0-4]: Lit: Imaginary(10.0)"]); +} + +#[test] +fn lit_int_imag_with_spacing() { + check_expr(r#"10 im"#, &expect!["Expr [0-6]: Lit: Imaginary(10.0)"]); +} + +#[test] +fn lit_float_imag_leading_dot() { + check_expr(".23im", &expect!["Expr [0-5]: Lit: Imaginary(0.23)"]); +} + +#[test] +fn lit_float_imag_trailing_dot() { + check_expr("1.im", &expect!["Expr [0-4]: Lit: Imaginary(1.0)"]); +} + +#[test] +fn lit_float_imag_underscore() { + check_expr( + "123_456.78im", + &expect!["Expr [0-12]: Lit: Imaginary(123456.78)"], + ); +} + +#[test] +fn lit_float_imag_leading_zero() { + check_expr("0.23im", &expect!["Expr [0-6]: Lit: Imaginary(0.23)"]); +} + +#[test] +fn pratt_parsing_binary_expr() { + check_expr( + "1 + 2", + &expect![[r#" + Expr [0-5]: BinaryOpExpr: + op: Add + lhs: Expr [0-1]: Lit: Int(1) + rhs: Expr [4-5]: Lit: Int(2)"#]], + ); +} + +#[test] +fn pratt_parsing_mul_add() { + check_expr( + "1 + 2 * 3", + &expect![[r#" + Expr [0-9]: BinaryOpExpr: + op: Add + lhs: Expr [0-1]: Lit: Int(1) + rhs: Expr [4-9]: BinaryOpExpr: + op: Mul + lhs: Expr [4-5]: Lit: Int(2) + rhs: Expr [8-9]: Lit: Int(3)"#]], + ); +} + +#[test] +fn pratt_parsing_parens() { + check_expr( + "(1 + 2) * 3", + &expect![[r#" + Expr [0-11]: BinaryOpExpr: + op: Mul + lhs: Expr [0-7]: Paren Expr [1-6]: BinaryOpExpr: + op: Add + lhs: Expr [1-2]: Lit: Int(1) + rhs: Expr [5-6]: Lit: Int(2) + rhs: Expr [10-11]: Lit: Int(3)"#]], + ); +} + +#[test] +fn prat_parsing_mul_unary() { + check_expr( + "2 * -3", + &expect![[r#" + Expr [0-6]: BinaryOpExpr: + op: Mul + lhs: Expr [0-1]: Lit: Int(2) + rhs: Expr [4-6]: UnaryOpExpr: + op: Neg + expr: Expr [5-6]: Lit: Int(3)"#]], + ); +} + +#[test] +fn prat_parsing_unary_mul() { + check_expr( + "-2 * 3", + &expect![[r#" + Expr [0-6]: BinaryOpExpr: + op: Mul + lhs: Expr [0-2]: UnaryOpExpr: + op: Neg + expr: Expr [1-2]: Lit: Int(2) + rhs: Expr [5-6]: Lit: Int(3)"#]], + ); +} + +#[test] +fn prat_parsing_exp_funcall() { + check_expr( + "2 ** square(3)", + &expect![[r#" + Expr [0-14]: BinaryOpExpr: + op: Exp + lhs: Expr [0-1]: Lit: Int(2) + rhs: Expr [5-14]: FunctionCall [5-14]: + name: Ident [5-11] "square" + args: + Expr [12-13]: Lit: Int(3)"#]], + ); +} + +#[test] +fn prat_parsing_funcall_exp() { + check_expr( + "square(2) ** 3", + &expect![[r#" + Expr [0-14]: BinaryOpExpr: + op: Exp + lhs: Expr [0-9]: FunctionCall [0-9]: + name: Ident [0-6] "square" + args: + Expr [7-8]: Lit: Int(2) + rhs: Expr [13-14]: Lit: Int(3)"#]], + ); +} + +#[test] +fn prat_parsing_funcall_exp_arg() { + check_expr( + "square(2 ** 3)", + &expect![[r#" + Expr [0-14]: FunctionCall [0-14]: + name: Ident [0-6] "square" + args: + Expr [7-13]: BinaryOpExpr: + op: Exp + lhs: Expr [7-8]: Lit: Int(2) + rhs: Expr [12-13]: Lit: Int(3)"#]], + ); +} + +#[test] +fn funcall() { + check_expr( + "square(2)", + &expect![[r#" + Expr [0-9]: FunctionCall [0-9]: + name: Ident [0-6] "square" + args: + Expr [7-8]: Lit: Int(2)"#]], + ); +} + +#[test] +fn funcall_multiple_args() { + check_expr( + "square(2, 3)", + &expect![[r#" + Expr [0-12]: FunctionCall [0-12]: + name: Ident [0-6] "square" + args: + Expr [7-8]: Lit: Int(2) + Expr [10-11]: Lit: Int(3)"#]], + ); +} + +#[test] +fn funcall_multiple_args_trailing_comma() { + check_expr( + "square(2, 3,)", + &expect![[r#" + Expr [0-13]: FunctionCall [0-13]: + name: Ident [0-6] "square" + args: + Expr [7-8]: Lit: Int(2) + Expr [10-11]: Lit: Int(3)"#]], + ); +} + +#[test] +fn cast_to_bit() { + check_expr( + "bit(0)", + &expect![[r#" + Expr [0-6]: Cast [0-6]: + type: ScalarType [0-3]: BitType [0-3]: + size: + arg: Expr [4-5]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_bit_with_designator() { + check_expr( + "bit[4](0)", + &expect![[r#" + Expr [0-9]: Cast [0-9]: + type: ScalarType [0-6]: BitType [0-6]: + size: Expr [4-5]: Lit: Int(4) + arg: Expr [7-8]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_int() { + check_expr( + "int(0)", + &expect![[r#" + Expr [0-6]: Cast [0-6]: + type: ScalarType [0-3]: IntType [0-3]: + size: + arg: Expr [4-5]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_int_with_designator() { + check_expr( + "int[64](0)", + &expect![[r#" + Expr [0-10]: Cast [0-10]: + type: ScalarType [0-7]: IntType [0-7]: + size: Expr [4-6]: Lit: Int(64) + arg: Expr [8-9]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_uint() { + check_expr( + "uint(0)", + &expect![[r#" + Expr [0-7]: Cast [0-7]: + type: ScalarType [0-4]: UIntType [0-4]: + size: + arg: Expr [5-6]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_uint_with_designator() { + check_expr( + "uint[64](0)", + &expect![[r#" + Expr [0-11]: Cast [0-11]: + type: ScalarType [0-8]: UIntType [0-8]: + size: Expr [5-7]: Lit: Int(64) + arg: Expr [9-10]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_float() { + check_expr( + "float(0)", + &expect![[r#" + Expr [0-8]: Cast [0-8]: + type: ScalarType [0-5]: FloatType [0-5]: + size: + arg: Expr [6-7]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_float_with_designator() { + check_expr( + "float[64](0)", + &expect![[r#" + Expr [0-12]: Cast [0-12]: + type: ScalarType [0-9]: FloatType [0-9]: + size: Expr [6-8]: Lit: Int(64) + arg: Expr [10-11]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_complex() { + check_expr( + "complex[float](0)", + &expect![[r#" + Expr [0-17]: Cast [0-17]: + type: ScalarType [0-14]: ComplexType [0-14]: + base_size: FloatType [8-13]: + size: + arg: Expr [15-16]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_complex_with_designator() { + check_expr( + "complex[float[64]](0)", + &expect![[r#" + Expr [0-21]: Cast [0-21]: + type: ScalarType [0-18]: ComplexType [0-18]: + base_size: FloatType [8-17]: + size: Expr [14-16]: Lit: Int(64) + arg: Expr [19-20]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_bool() { + check_expr( + "bool(0)", + &expect![[r#" + Expr [0-7]: Cast [0-7]: + type: ScalarType [0-4]: BoolType + arg: Expr [5-6]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_duration() { + check_expr( + "duration(0)", + &expect![[r#" + Expr [0-11]: Cast [0-11]: + type: ScalarType [0-8]: Duration + arg: Expr [9-10]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_stretch() { + check_expr( + "stretch(0)", + &expect![[r#" + Expr [0-10]: Cast [0-10]: + type: ScalarType [0-7]: Stretch + arg: Expr [8-9]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_int_array() { + check_expr( + "array[int[64], 4](0)", + &expect![[r#" + Expr [0-20]: Cast [0-20]: + type: ArrayType [0-17]: + base_type: ArrayBaseTypeKind IntType [6-13]: + size: Expr [10-12]: Lit: Int(64) + dimensions: + Expr [15-16]: Lit: Int(4) + arg: Expr [18-19]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_uint_array() { + check_expr( + "array[uint[64], 4](0)", + &expect![[r#" + Expr [0-21]: Cast [0-21]: + type: ArrayType [0-18]: + base_type: ArrayBaseTypeKind UIntType [6-14]: + size: Expr [11-13]: Lit: Int(64) + dimensions: + Expr [16-17]: Lit: Int(4) + arg: Expr [19-20]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_float_array() { + check_expr( + "array[float[64], 4](0)", + &expect![[r#" + Expr [0-22]: Cast [0-22]: + type: ArrayType [0-19]: + base_type: ArrayBaseTypeKind FloatType [6-15]: + size: Expr [12-14]: Lit: Int(64) + dimensions: + Expr [17-18]: Lit: Int(4) + arg: Expr [20-21]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_angle_array() { + check_expr( + "array[angle[64], 4](0)", + &expect![[r#" + Expr [0-22]: Cast [0-22]: + type: ArrayType [0-19]: + base_type: ArrayBaseTypeKind AngleType [6-15]: + size: Expr [12-14]: Lit: Int(64) + dimensions: + Expr [17-18]: Lit: Int(4) + arg: Expr [20-21]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_bool_array() { + check_expr( + "array[bool, 4](0)", + &expect![[r#" + Expr [0-17]: Cast [0-17]: + type: ArrayType [0-14]: + base_type: ArrayBaseTypeKind BoolType + dimensions: + Expr [12-13]: Lit: Int(4) + arg: Expr [15-16]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_duration_array() { + check_expr( + "array[duration, 4](0)", + &expect![[r#" + Expr [0-21]: Cast [0-21]: + type: ArrayType [0-18]: + base_type: ArrayBaseTypeKind DurationType + dimensions: + Expr [16-17]: Lit: Int(4) + arg: Expr [19-20]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_complex_array() { + check_expr( + "array[complex[float[32]], 4](0)", + &expect![[r#" + Expr [0-31]: Cast [0-31]: + type: ArrayType [0-28]: + base_type: ArrayBaseTypeKind ComplexType [6-24]: + base_size: FloatType [14-23]: + size: Expr [20-22]: Lit: Int(32) + dimensions: + Expr [26-27]: Lit: Int(4) + arg: Expr [29-30]: Lit: Int(0)"#]], + ); +} + +#[test] +fn index_expr() { + check_expr( + "foo[1]", + &expect![[r#" + Expr [0-6]: IndexExpr [0-6]: + collection: Expr [0-3]: Ident [0-3] "foo" + index: IndexSet [4-5]: + values: + Expr [4-5]: Lit: Int(1)"#]], + ); +} + +#[test] +fn index_set() { + check_expr( + "foo[{1, 4, 5}]", + &expect![[r#" + Expr [0-14]: IndexExpr [0-14]: + collection: Expr [0-3]: Ident [0-3] "foo" + index: DiscreteSet [4-13]: + values: + Expr [5-6]: Lit: Int(1) + Expr [8-9]: Lit: Int(4) + Expr [11-12]: Lit: Int(5)"#]], + ); +} + +#[test] +fn index_multiple_ranges() { + check_expr( + "foo[1:5, 3:7, 4:8]", + &expect![[r#" + Expr [0-18]: IndexExpr [0-18]: + collection: Expr [0-3]: Ident [0-3] "foo" + index: IndexSet [4-17]: + values: + RangeDefinition [4-7]: + start: Expr [4-5]: Lit: Int(1) + step: + end: Expr [6-7]: Lit: Int(5) + RangeDefinition [9-12]: + start: Expr [9-10]: Lit: Int(3) + step: + end: Expr [11-12]: Lit: Int(7) + RangeDefinition [14-17]: + start: Expr [14-15]: Lit: Int(4) + step: + end: Expr [16-17]: Lit: Int(8)"#]], + ); +} + +#[test] +fn index_range() { + check_expr( + "foo[1:5:2]", + &expect![[r#" + Expr [0-10]: IndexExpr [0-10]: + collection: Expr [0-3]: Ident [0-3] "foo" + index: IndexSet [4-9]: + values: + RangeDefinition [4-9]: + start: Expr [4-5]: Lit: Int(1) + step: Expr [6-7]: Lit: Int(5) + end: Expr [8-9]: Lit: Int(2)"#]], + ); +} + +#[test] +fn index_full_range() { + check_expr( + "foo[:]", + &expect![[r#" + Expr [0-6]: IndexExpr [0-6]: + collection: Expr [0-3]: Ident [0-3] "foo" + index: IndexSet [4-5]: + values: + RangeDefinition [4-5]: + start: + step: + end: "#]], + ); +} + +#[test] +fn index_range_start() { + check_expr( + "foo[1:]", + &expect![[r#" + Expr [0-7]: IndexExpr [0-7]: + collection: Expr [0-3]: Ident [0-3] "foo" + index: IndexSet [4-6]: + values: + RangeDefinition [4-6]: + start: Expr [4-5]: Lit: Int(1) + step: + end: "#]], + ); +} + +#[test] +fn index_range_end() { + check_expr( + "foo[:5]", + &expect![[r#" + Expr [0-7]: IndexExpr [0-7]: + collection: Expr [0-3]: Ident [0-3] "foo" + index: IndexSet [4-6]: + values: + RangeDefinition [4-6]: + start: + step: + end: Expr [5-6]: Lit: Int(5)"#]], + ); +} + +#[test] +fn index_range_step() { + check_expr( + "foo[:2:]", + &expect![[r#" + Expr [0-8]: IndexExpr [0-8]: + collection: Expr [0-3]: Ident [0-3] "foo" + index: IndexSet [4-7]: + values: + RangeDefinition [4-7]: + start: + step: Expr [5-6]: Lit: Int(2) + end: "#]], + ); +} + +#[test] +fn set_expr() { + check( + super::set_expr, + "{2, 3, 4}", + &expect![[r#" + DiscreteSet [0-9]: + values: + Expr [1-2]: Lit: Int(2) + Expr [4-5]: Lit: Int(3) + Expr [7-8]: Lit: Int(4)"#]], + ); +} + +#[test] +fn lit_array() { + check( + super::lit_array, + "{{2, {5}}, 1 + z}", + &expect![[r#" + Expr [0-17]: Lit: Array: + Expr [1-9]: Lit: Array: + Expr [2-3]: Lit: Int(2) + Expr [5-8]: Lit: Array: + Expr [6-7]: Lit: Int(5) + Expr [11-16]: BinaryOpExpr: + op: Add + lhs: Expr [11-12]: Lit: Int(1) + rhs: Expr [15-16]: Ident [15-16] "z""#]], + ); +} + +#[test] +fn hardware_qubit() { + check( + super::hardware_qubit, + "$12", + &expect!["HardwareQubit [0-3]: 12"], + ); +} + +#[test] +fn indexed_identifier() { + check( + super::indexed_identifier, + "arr[1][2]", + &expect![[r#" + IndexedIdent [0-9]: + name: Ident [0-3] "arr" + index_span: [3-9] + indices: + IndexSet [4-5]: + values: + Expr [4-5]: Lit: Int(1) + IndexSet [7-8]: + values: + Expr [7-8]: Lit: Int(2)"#]], + ); +} + +#[test] +fn measure_hardware_qubit() { + check( + super::measure_expr, + "measure $12", + &expect![[r#" + MeasureExpr [0-11]: + operand: GateOperand [8-11]: + kind: HardwareQubit [8-11]: 12"#]], + ); +} + +#[test] +fn measure_indexed_identifier() { + check( + super::measure_expr, + "measure qubits[1][2]", + &expect![[r#" + MeasureExpr [0-20]: + operand: GateOperand [8-20]: + kind: IndexedIdent [8-20]: + name: Ident [8-14] "qubits" + index_span: [14-20] + indices: + IndexSet [15-16]: + values: + Expr [15-16]: Lit: Int(1) + IndexSet [18-19]: + values: + Expr [18-19]: Lit: Int(2)"#]], + ); +} + +#[test] +fn addition_of_casts() { + check_expr( + "bit(0) + bit(1)", + &expect![[r#" + Expr [0-15]: BinaryOpExpr: + op: Add + lhs: Expr [0-6]: Cast [0-6]: + type: ScalarType [0-3]: BitType [0-3]: + size: + arg: Expr [4-5]: Lit: Int(0) + rhs: Expr [9-15]: Cast [9-15]: + type: ScalarType [9-12]: BitType [9-12]: + size: + arg: Expr [13-14]: Lit: Int(1)"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/mut_visit.rs b/compiler/qsc_qasm/src/parser/mut_visit.rs new file mode 100644 index 0000000000..968ff07f83 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/mut_visit.rs @@ -0,0 +1,847 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use qsc_data_structures::span::Span; + +use super::ast::{ + AccessControl, AliasDeclStmt, Annotation, ArrayBaseTypeKind, ArrayReferenceType, ArrayType, + ArrayTypedParameter, AssignOpStmt, AssignStmt, BarrierStmt, BinOp, BinaryOpExpr, Block, + BoxStmt, BreakStmt, CalibrationGrammarStmt, CalibrationStmt, Cast, ClassicalDeclarationStmt, + ConstantDeclStmt, ContinueStmt, DefCalStmt, DefStmt, DelayStmt, DiscreteSet, EndStmt, + EnumerableSet, Expr, ExprStmt, ExternDecl, ExternParameter, ForStmt, FunctionCall, GPhase, + GateCall, GateModifierKind, GateOperand, GateOperandKind, HardwareQubit, IODeclaration, Ident, + Identifier, IfStmt, IncludeStmt, IndexElement, IndexExpr, IndexSet, IndexSetItem, IndexedIdent, + Lit, LiteralKind, MeasureArrowStmt, MeasureExpr, Pragma, Program, QuantumGateDefinition, + QuantumGateModifier, QuantumTypedParameter, QubitDeclaration, RangeDefinition, ResetStmt, + ReturnStmt, ScalarType, ScalarTypedParameter, Stmt, StmtKind, SwitchCase, SwitchStmt, TypeDef, + TypedParameter, UnaryOp, UnaryOpExpr, ValueExpr, Version, WhileLoop, +}; + +pub trait MutVisitor: Sized { + fn visit_program(&mut self, program: &mut Program) { + walk_program(self, program); + } + + fn visit_block(&mut self, block: &mut Block) { + walk_block(self, block); + } + + fn visit_annotation(&mut self, annotation: &mut Annotation) { + walk_annotation(self, annotation); + } + + fn visit_stmt(&mut self, stmt: &mut Stmt) { + walk_stmt(self, stmt); + } + + fn visit_alias_decl_stmt(&mut self, stmt: &mut AliasDeclStmt) { + walk_alias_decl_stmt(self, stmt); + } + + fn visit_assign_stmt(&mut self, stmt: &mut AssignStmt) { + walk_assign_stmt(self, stmt); + } + + fn visit_assign_op_stmt(&mut self, stmt: &mut AssignOpStmt) { + walk_assign_op_stmt(self, stmt); + } + + fn visit_barrier_stmt(&mut self, stmt: &mut BarrierStmt) { + walk_barrier_stmt(self, stmt); + } + + fn visit_box_stmt(&mut self, stmt: &mut BoxStmt) { + walk_box_stmt(self, stmt); + } + + fn visit_break_stmt(&mut self, stmt: &mut BreakStmt) { + walk_break_stmt(self, stmt); + } + + fn visit_block_stmt(&mut self, stmt: &mut Block) { + walk_block_stmt(self, stmt); + } + + fn visit_cal_stmt(&mut self, stmt: &mut CalibrationStmt) { + walk_cal_stmt(self, stmt); + } + + fn visit_calibration_grammar_stmt(&mut self, stmt: &mut CalibrationGrammarStmt) { + walk_calibration_grammar_stmt(self, stmt); + } + + fn visit_classical_decl_stmt(&mut self, stmt: &mut ClassicalDeclarationStmt) { + walk_classical_decl_stmt(self, stmt); + } + + fn visit_const_decl_stmt(&mut self, stmt: &mut ConstantDeclStmt) { + walk_const_decl_stmt(self, stmt); + } + + fn visit_continue_stmt(&mut self, stmt: &mut ContinueStmt) { + walk_continue_stmt(self, stmt); + } + + fn visit_def_stmt(&mut self, stmt: &mut DefStmt) { + walk_def_stmt(self, stmt); + } + + fn visit_def_cal_stmt(&mut self, stmt: &mut DefCalStmt) { + walk_def_cal_stmt(self, stmt); + } + + fn visit_delay_stmt(&mut self, stmt: &mut DelayStmt) { + walk_delay_stmt(self, stmt); + } + + fn visit_end_stmt(&mut self, stmt: &mut EndStmt) { + walk_end_stmt(self, stmt); + } + + fn visit_expr_stmt(&mut self, stmt: &mut ExprStmt) { + walk_expr_stmt(self, stmt); + } + + fn visit_extern_decl_stmt(&mut self, stmt: &mut ExternDecl) { + walk_extern_stmt(self, stmt); + } + + fn visit_for_stmt(&mut self, stmt: &mut ForStmt) { + walk_for_stmt(self, stmt); + } + + fn visit_if_stmt(&mut self, stmt: &mut IfStmt) { + walk_if_stmt(self, stmt); + } + + fn visit_gate_call_stmt(&mut self, stmt: &mut GateCall) { + walk_gate_call_stmt(self, stmt); + } + + fn visit_gphase_stmt(&mut self, stmt: &mut GPhase) { + walk_gphase_stmt(self, stmt); + } + + fn visit_include_stmt(&mut self, stmt: &mut IncludeStmt) { + walk_include_stmt(self, stmt); + } + + fn visit_io_declaration_stmt(&mut self, stmt: &mut IODeclaration) { + walk_io_declaration_stmt(self, stmt); + } + + fn visit_measure_stmt(&mut self, stmt: &mut MeasureArrowStmt) { + walk_measure_stmt(self, stmt); + } + + fn visit_pragma_stmt(&mut self, stmt: &mut Pragma) { + walk_pragma_stmt(self, stmt); + } + + fn visit_quantum_gate_definition_stmt(&mut self, stmt: &mut QuantumGateDefinition) { + walk_quantum_gate_definition_stmt(self, stmt); + } + + fn visit_quantum_decl_stmt(&mut self, stmt: &mut QubitDeclaration) { + walk_quantum_decl_stmt(self, stmt); + } + + fn visit_reset_stmt(&mut self, stmt: &mut ResetStmt) { + walk_reset_stmt(self, stmt); + } + + fn visit_return_stmt(&mut self, stmt: &mut ReturnStmt) { + walk_return_stmt(self, stmt); + } + + fn visit_switch_stmt(&mut self, stmt: &mut SwitchStmt) { + walk_switch_stmt(self, stmt); + } + + fn visit_while_loop_stmt(&mut self, stmt: &mut WhileLoop) { + walk_while_loop_stmt(self, stmt); + } + + fn visit_expr(&mut self, expr: &mut Expr) { + walk_expr(self, expr); + } + + fn visit_unary_op_expr(&mut self, expr: &mut UnaryOpExpr) { + walk_unary_op_expr(self, expr); + } + + fn visit_binary_op_expr(&mut self, expr: &mut BinaryOpExpr) { + walk_binary_op_expr(self, expr); + } + + fn visit_lit_expr(&mut self, expr: &mut Lit) { + walk_lit_expr(self, expr); + } + + fn visit_function_call_expr(&mut self, expr: &mut FunctionCall) { + walk_function_call_expr(self, expr); + } + + fn visit_cast_expr(&mut self, expr: &mut Cast) { + walk_cast_expr(self, expr); + } + + fn visit_index_expr(&mut self, expr: &mut IndexExpr) { + walk_index_expr(self, expr); + } + + fn visit_value_expr(&mut self, expr: &mut ValueExpr) { + walk_value_expr(self, expr); + } + + fn visit_measure_expr(&mut self, expr: &mut MeasureExpr) { + walk_measure_expr(self, expr); + } + + fn visit_identifier(&mut self, ident: &mut Identifier) { + walk_identifier(self, ident); + } + + fn visit_indexed_ident(&mut self, ident: &mut IndexedIdent) { + walk_indexed_ident(self, ident); + } + + fn visit_ident(&mut self, ident: &mut Ident) { + walk_ident(self, ident); + } + + fn visit_index_element(&mut self, elem: &mut IndexElement) { + walk_index_element(self, elem); + } + + fn visit_discrete_set(&mut self, set: &mut DiscreteSet) { + walk_discrete_set(self, set); + } + + fn visit_index_set(&mut self, set: &mut IndexSet) { + walk_index_set(self, set); + } + + fn visit_index_set_item(&mut self, item: &mut IndexSetItem) { + walk_index_set_item(self, item); + } + + fn visit_range_definition(&mut self, range: &mut RangeDefinition) { + walk_range_definition(self, range); + } + + fn visit_gate_operand(&mut self, operand: &mut GateOperand) { + walk_gate_operand(self, operand); + } + + fn visit_hardware_qubit(&mut self, qubit: &mut HardwareQubit) { + walk_hardware_qubit(self, qubit); + } + + fn visit_tydef(&mut self, ty: &mut TypeDef) { + walk_tydef(self, ty); + } + + fn visit_array_type(&mut self, ty: &mut ArrayType) { + walk_array_type(self, ty); + } + + fn visit_array_ref_type(&mut self, ty: &mut ArrayReferenceType) { + walk_array_ref_type(self, ty); + } + + fn visit_array_base_type(&mut self, ty: &mut ArrayBaseTypeKind) { + walk_array_base_type(self, ty); + } + + fn visit_scalar_type(&mut self, ty: &mut ScalarType) { + walk_scalar_type(self, ty); + } + + fn visit_typed_parameter(&mut self, param: &mut TypedParameter) { + walk_typed_parameter(self, param); + } + + fn visit_array_typed_parameter(&mut self, param: &mut ArrayTypedParameter) { + walk_array_typed_parameter(self, param); + } + + fn visit_quantum_typed_parameter(&mut self, param: &mut QuantumTypedParameter) { + walk_quantum_typed_parameter(self, param); + } + + fn visit_scalar_typed_parameter(&mut self, param: &mut ScalarTypedParameter) { + walk_scalar_typed_parameter(self, param); + } + + fn visit_extern_parameter(&mut self, param: &mut ExternParameter) { + walk_extern_parameter(self, param); + } + + fn visit_enumerable_set(&mut self, set: &mut EnumerableSet) { + walk_enumerable_set(self, set); + } + + fn visit_gate_modifier(&mut self, set: &mut QuantumGateModifier) { + walk_gate_modifier(self, set); + } + + fn visit_switch_case(&mut self, case: &mut SwitchCase) { + walk_switch_case(self, case); + } + + fn visit_access_control(&mut self, _: &mut AccessControl) {} + + fn visit_version(&mut self, _: &mut Version) {} + + fn visit_span(&mut self, _: &mut Span) {} + + fn visit_binop(&mut self, _: &mut BinOp) {} + + fn visit_unary_op(&mut self, _: &mut UnaryOp) {} +} + +pub fn walk_program(vis: &mut impl MutVisitor, program: &mut Program) { + vis.visit_span(&mut program.span); + program + .version + .iter_mut() + .for_each(|v| vis.visit_version(v)); + program + .statements + .iter_mut() + .for_each(|s| vis.visit_stmt(s)); +} + +pub fn walk_block(vis: &mut impl MutVisitor, block: &mut Block) { + vis.visit_span(&mut block.span); + block.stmts.iter_mut().for_each(|s| vis.visit_stmt(s)); +} + +pub fn walk_annotation(vis: &mut impl MutVisitor, annotation: &mut Annotation) { + vis.visit_span(&mut annotation.span); +} + +pub fn walk_stmt(vis: &mut impl MutVisitor, stmt: &mut Stmt) { + vis.visit_span(&mut stmt.span); + stmt.annotations + .iter_mut() + .for_each(|s| vis.visit_annotation(s)); + match &mut *stmt.kind { + StmtKind::Err => {} + StmtKind::Alias(alias_decl_stmt) => vis.visit_alias_decl_stmt(alias_decl_stmt), + StmtKind::Assign(assign_stmt) => vis.visit_assign_stmt(assign_stmt), + StmtKind::AssignOp(assign_op_stmt) => vis.visit_assign_op_stmt(assign_op_stmt), + StmtKind::Barrier(barrier_stmt) => vis.visit_barrier_stmt(barrier_stmt), + StmtKind::Box(box_stmt) => vis.visit_box_stmt(box_stmt), + StmtKind::Break(break_stmt) => vis.visit_break_stmt(break_stmt), + StmtKind::Block(block) => vis.visit_block_stmt(block), + StmtKind::Cal(calibration_stmt) => vis.visit_cal_stmt(calibration_stmt), + StmtKind::CalibrationGrammar(calibration_grammar_stmt) => { + vis.visit_calibration_grammar_stmt(calibration_grammar_stmt); + } + StmtKind::ClassicalDecl(classical_declaration_stmt) => { + vis.visit_classical_decl_stmt(classical_declaration_stmt); + } + StmtKind::ConstDecl(constant_decl_stmt) => vis.visit_const_decl_stmt(constant_decl_stmt), + StmtKind::Continue(continue_stmt) => vis.visit_continue_stmt(continue_stmt), + StmtKind::Def(def_stmt) => vis.visit_def_stmt(def_stmt), + StmtKind::DefCal(def_cal_stmt) => vis.visit_def_cal_stmt(def_cal_stmt), + StmtKind::Delay(delay_stmt) => vis.visit_delay_stmt(delay_stmt), + StmtKind::End(end_stmt) => vis.visit_end_stmt(end_stmt), + StmtKind::ExprStmt(expr_stmt) => vis.visit_expr_stmt(expr_stmt), + StmtKind::ExternDecl(extern_decl) => vis.visit_extern_decl_stmt(extern_decl), + StmtKind::For(for_stmt) => vis.visit_for_stmt(for_stmt), + StmtKind::If(if_stmt) => vis.visit_if_stmt(if_stmt), + StmtKind::GateCall(gate_call) => vis.visit_gate_call_stmt(gate_call), + StmtKind::GPhase(gphase) => vis.visit_gphase_stmt(gphase), + StmtKind::Include(include_stmt) => vis.visit_include_stmt(include_stmt), + StmtKind::IODeclaration(iodeclaration) => vis.visit_io_declaration_stmt(iodeclaration), + StmtKind::Measure(measure_stmt) => vis.visit_measure_stmt(measure_stmt), + StmtKind::Pragma(pragma) => vis.visit_pragma_stmt(pragma), + StmtKind::QuantumGateDefinition(quantum_gate_definition) => { + vis.visit_quantum_gate_definition_stmt(quantum_gate_definition); + } + StmtKind::QuantumDecl(qubit_declaration) => vis.visit_quantum_decl_stmt(qubit_declaration), + StmtKind::Reset(reset_stmt) => vis.visit_reset_stmt(reset_stmt), + StmtKind::Return(return_stmt) => vis.visit_return_stmt(return_stmt), + StmtKind::Switch(switch_stmt) => vis.visit_switch_stmt(switch_stmt), + StmtKind::WhileLoop(while_loop) => vis.visit_while_loop_stmt(while_loop), + } +} + +fn walk_alias_decl_stmt(vis: &mut impl MutVisitor, stmt: &mut AliasDeclStmt) { + vis.visit_span(&mut stmt.span); + vis.visit_identifier(&mut stmt.ident); + stmt.exprs.iter_mut().for_each(|e| vis.visit_expr(e)); +} + +fn walk_assign_stmt(vis: &mut impl MutVisitor, stmt: &mut AssignStmt) { + vis.visit_span(&mut stmt.span); + vis.visit_indexed_ident(&mut stmt.lhs); + vis.visit_value_expr(&mut stmt.rhs); +} + +fn walk_assign_op_stmt(vis: &mut impl MutVisitor, stmt: &mut AssignOpStmt) { + vis.visit_span(&mut stmt.span); + vis.visit_indexed_ident(&mut stmt.lhs); + vis.visit_binop(&mut stmt.op); + vis.visit_value_expr(&mut stmt.rhs); +} + +fn walk_barrier_stmt(vis: &mut impl MutVisitor, stmt: &mut BarrierStmt) { + vis.visit_span(&mut stmt.span); + stmt.qubits + .iter_mut() + .for_each(|operand| vis.visit_gate_operand(operand)); +} + +fn walk_box_stmt(vis: &mut impl MutVisitor, stmt: &mut BoxStmt) { + vis.visit_span(&mut stmt.span); + stmt.duration.iter_mut().for_each(|d| vis.visit_expr(d)); + stmt.body.iter_mut().for_each(|stmt| vis.visit_stmt(stmt)); +} + +fn walk_break_stmt(vis: &mut impl MutVisitor, stmt: &mut BreakStmt) { + vis.visit_span(&mut stmt.span); +} + +fn walk_block_stmt(vis: &mut impl MutVisitor, stmt: &mut Block) { + vis.visit_span(&mut stmt.span); + stmt.stmts.iter_mut().for_each(|stmt| vis.visit_stmt(stmt)); +} + +fn walk_cal_stmt(vis: &mut impl MutVisitor, stmt: &mut CalibrationStmt) { + vis.visit_span(&mut stmt.span); +} + +fn walk_calibration_grammar_stmt(vis: &mut impl MutVisitor, stmt: &mut CalibrationGrammarStmt) { + vis.visit_span(&mut stmt.span); +} + +fn walk_classical_decl_stmt(vis: &mut impl MutVisitor, stmt: &mut ClassicalDeclarationStmt) { + vis.visit_span(&mut stmt.span); + vis.visit_tydef(&mut stmt.ty); + vis.visit_ident(&mut stmt.identifier); + stmt.init_expr + .iter_mut() + .for_each(|e| vis.visit_value_expr(e)); +} + +fn walk_const_decl_stmt(vis: &mut impl MutVisitor, stmt: &mut ConstantDeclStmt) { + vis.visit_span(&mut stmt.span); + vis.visit_tydef(&mut stmt.ty); + vis.visit_ident(&mut stmt.identifier); + vis.visit_value_expr(&mut stmt.init_expr); +} + +fn walk_continue_stmt(vis: &mut impl MutVisitor, stmt: &mut ContinueStmt) { + vis.visit_span(&mut stmt.span); +} + +fn walk_def_stmt(vis: &mut impl MutVisitor, stmt: &mut DefStmt) { + vis.visit_span(&mut stmt.span); + vis.visit_ident(&mut stmt.name); + stmt.params + .iter_mut() + .for_each(|p| vis.visit_typed_parameter(p)); + stmt.return_type + .iter_mut() + .for_each(|ty| vis.visit_scalar_type(ty)); + vis.visit_block(&mut stmt.body); +} + +fn walk_def_cal_stmt(vis: &mut impl MutVisitor, stmt: &mut DefCalStmt) { + vis.visit_span(&mut stmt.span); +} + +fn walk_delay_stmt(vis: &mut impl MutVisitor, stmt: &mut DelayStmt) { + vis.visit_span(&mut stmt.span); + vis.visit_expr(&mut stmt.duration); + stmt.qubits + .iter_mut() + .for_each(|operand| vis.visit_gate_operand(operand)); +} + +fn walk_end_stmt(vis: &mut impl MutVisitor, stmt: &mut EndStmt) { + vis.visit_span(&mut stmt.span); +} + +fn walk_expr_stmt(vis: &mut impl MutVisitor, stmt: &mut ExprStmt) { + vis.visit_span(&mut stmt.span); + vis.visit_expr(&mut stmt.expr); +} + +fn walk_extern_stmt(vis: &mut impl MutVisitor, stmt: &mut ExternDecl) { + vis.visit_span(&mut stmt.span); + vis.visit_ident(&mut stmt.ident); + stmt.params + .iter_mut() + .for_each(|p| vis.visit_extern_parameter(p)); + stmt.return_type + .iter_mut() + .for_each(|ty| vis.visit_scalar_type(ty)); +} + +fn walk_for_stmt(vis: &mut impl MutVisitor, stmt: &mut ForStmt) { + vis.visit_span(&mut stmt.span); + vis.visit_scalar_type(&mut stmt.ty); + vis.visit_ident(&mut stmt.ident); + vis.visit_enumerable_set(&mut stmt.set_declaration); + vis.visit_stmt(&mut stmt.body); +} + +fn walk_if_stmt(vis: &mut impl MutVisitor, stmt: &mut IfStmt) { + vis.visit_span(&mut stmt.span); + vis.visit_expr(&mut stmt.condition); + vis.visit_stmt(&mut stmt.if_body); + stmt.else_body + .iter_mut() + .for_each(|else_body| vis.visit_stmt(else_body)); +} + +fn walk_gate_call_stmt(vis: &mut impl MutVisitor, stmt: &mut GateCall) { + vis.visit_span(&mut stmt.span); + vis.visit_ident(&mut stmt.name); + stmt.modifiers + .iter_mut() + .for_each(|m| vis.visit_gate_modifier(m)); + stmt.args.iter_mut().for_each(|arg| vis.visit_expr(arg)); + stmt.duration.iter_mut().for_each(|d| vis.visit_expr(d)); + stmt.qubits + .iter_mut() + .for_each(|q| vis.visit_gate_operand(q)); +} + +fn walk_gphase_stmt(vis: &mut impl MutVisitor, stmt: &mut GPhase) { + vis.visit_span(&mut stmt.span); + vis.visit_span(&mut stmt.gphase_token_span); + stmt.modifiers + .iter_mut() + .for_each(|m| vis.visit_gate_modifier(m)); + stmt.args.iter_mut().for_each(|arg| vis.visit_expr(arg)); + stmt.duration.iter_mut().for_each(|d| vis.visit_expr(d)); + stmt.qubits + .iter_mut() + .for_each(|q| vis.visit_gate_operand(q)); +} + +fn walk_include_stmt(vis: &mut impl MutVisitor, stmt: &mut IncludeStmt) { + vis.visit_span(&mut stmt.span); +} + +fn walk_io_declaration_stmt(vis: &mut impl MutVisitor, stmt: &mut IODeclaration) { + vis.visit_span(&mut stmt.span); + vis.visit_tydef(&mut stmt.ty); + vis.visit_ident(&mut stmt.ident); +} + +fn walk_measure_stmt(vis: &mut impl MutVisitor, stmt: &mut MeasureArrowStmt) { + vis.visit_span(&mut stmt.span); + stmt.target + .iter_mut() + .for_each(|t| vis.visit_indexed_ident(t)); + vis.visit_measure_expr(&mut stmt.measurement); +} + +fn walk_pragma_stmt(vis: &mut impl MutVisitor, stmt: &mut Pragma) { + vis.visit_span(&mut stmt.span); +} + +fn walk_quantum_gate_definition_stmt(vis: &mut impl MutVisitor, stmt: &mut QuantumGateDefinition) { + vis.visit_span(&mut stmt.span); + vis.visit_ident(&mut stmt.ident); + stmt.params.iter_mut().for_each(|p| match &mut **p { + super::prim::SeqItem::Item(i) => vis.visit_ident(i), + super::prim::SeqItem::Missing(span) => vis.visit_span(span), + }); + stmt.qubits.iter_mut().for_each(|p| match &mut **p { + super::prim::SeqItem::Item(i) => vis.visit_ident(i), + super::prim::SeqItem::Missing(span) => vis.visit_span(span), + }); + vis.visit_block(&mut stmt.body); +} + +fn walk_quantum_decl_stmt(vis: &mut impl MutVisitor, stmt: &mut QubitDeclaration) { + vis.visit_span(&mut stmt.span); + vis.visit_span(&mut stmt.ty_span); + vis.visit_ident(&mut stmt.qubit); + stmt.size.iter_mut().for_each(|s| vis.visit_expr(s)); +} + +fn walk_reset_stmt(vis: &mut impl MutVisitor, stmt: &mut ResetStmt) { + vis.visit_span(&mut stmt.span); + vis.visit_span(&mut stmt.reset_token_span); + vis.visit_gate_operand(&mut stmt.operand); +} + +fn walk_return_stmt(vis: &mut impl MutVisitor, stmt: &mut ReturnStmt) { + vis.visit_span(&mut stmt.span); + stmt.expr.iter_mut().for_each(|e| vis.visit_value_expr(e)); +} + +fn walk_switch_stmt(vis: &mut impl MutVisitor, stmt: &mut SwitchStmt) { + vis.visit_span(&mut stmt.span); + vis.visit_expr(&mut stmt.target); + stmt.cases.iter_mut().for_each(|c| vis.visit_switch_case(c)); + stmt.default.iter_mut().for_each(|d| vis.visit_block(d)); +} + +fn walk_while_loop_stmt(vis: &mut impl MutVisitor, stmt: &mut WhileLoop) { + vis.visit_span(&mut stmt.span); + vis.visit_expr(&mut stmt.while_condition); + vis.visit_stmt(&mut stmt.body); +} + +fn walk_switch_case(vis: &mut impl MutVisitor, case: &mut SwitchCase) { + vis.visit_span(&mut case.span); + case.labels.iter_mut().for_each(|l| vis.visit_expr(l)); + vis.visit_block(&mut case.block); +} + +pub fn walk_expr(vis: &mut impl MutVisitor, expr: &mut Expr) { + vis.visit_span(&mut expr.span); + + match &mut *expr.kind { + super::ast::ExprKind::Err => {} + super::ast::ExprKind::Ident(ident) => vis.visit_ident(ident), + super::ast::ExprKind::UnaryOp(unary_op_expr) => vis.visit_unary_op_expr(unary_op_expr), + super::ast::ExprKind::BinaryOp(binary_op_expr) => vis.visit_binary_op_expr(binary_op_expr), + super::ast::ExprKind::Lit(lit) => vis.visit_lit_expr(lit), + super::ast::ExprKind::FunctionCall(function_call) => { + vis.visit_function_call_expr(function_call); + } + super::ast::ExprKind::Cast(cast) => vis.visit_cast_expr(cast), + super::ast::ExprKind::IndexExpr(index_expr) => vis.visit_index_expr(index_expr), + super::ast::ExprKind::Paren(expr) => vis.visit_expr(expr), + } +} + +pub fn walk_unary_op_expr(vis: &mut impl MutVisitor, expr: &mut UnaryOpExpr) { + vis.visit_unary_op(&mut expr.op); + vis.visit_expr(&mut expr.expr); +} + +pub fn walk_binary_op_expr(vis: &mut impl MutVisitor, expr: &mut BinaryOpExpr) { + vis.visit_expr(&mut expr.lhs); + vis.visit_binop(&mut expr.op); + vis.visit_expr(&mut expr.rhs); +} + +pub fn walk_lit_expr(vis: &mut impl MutVisitor, lit: &mut Lit) { + vis.visit_span(&mut lit.span); + if let LiteralKind::Array(exprs) = &mut lit.kind { + exprs.iter_mut().for_each(|e| vis.visit_expr(e)); + } +} + +pub fn walk_function_call_expr(vis: &mut impl MutVisitor, expr: &mut FunctionCall) { + vis.visit_span(&mut expr.span); + vis.visit_ident(&mut expr.name); + expr.args.iter_mut().for_each(|arg| vis.visit_expr(arg)); +} + +pub fn walk_cast_expr(vis: &mut impl MutVisitor, expr: &mut Cast) { + vis.visit_span(&mut expr.span); + vis.visit_tydef(&mut expr.ty); + vis.visit_expr(&mut expr.arg); +} + +pub fn walk_index_expr(vis: &mut impl MutVisitor, expr: &mut IndexExpr) { + vis.visit_span(&mut expr.span); + vis.visit_expr(&mut expr.collection); + vis.visit_index_element(&mut expr.index); +} + +pub fn walk_value_expr(vis: &mut impl MutVisitor, expr: &mut ValueExpr) { + match &mut *expr { + ValueExpr::Expr(expr) => vis.visit_expr(expr), + ValueExpr::Measurement(measure_expr) => vis.visit_measure_expr(measure_expr), + } +} + +pub fn walk_measure_expr(vis: &mut impl MutVisitor, expr: &mut MeasureExpr) { + vis.visit_span(&mut expr.span); + vis.visit_span(&mut expr.measure_token_span); + vis.visit_gate_operand(&mut expr.operand); +} + +pub fn walk_identifier(vis: &mut impl MutVisitor, ident: &mut Identifier) { + match ident { + Identifier::Ident(ident) => vis.visit_ident(ident), + Identifier::IndexedIdent(indexed_ident) => vis.visit_indexed_ident(indexed_ident), + } +} + +pub fn walk_indexed_ident(vis: &mut impl MutVisitor, ident: &mut IndexedIdent) { + vis.visit_span(&mut ident.span); + vis.visit_ident(&mut ident.name); + ident + .indices + .iter_mut() + .for_each(|elem| vis.visit_index_element(elem)); +} + +pub fn walk_ident(vis: &mut impl MutVisitor, ident: &mut Ident) { + vis.visit_span(&mut ident.span); +} + +pub fn walk_index_element(vis: &mut impl MutVisitor, elem: &mut IndexElement) { + match elem { + IndexElement::DiscreteSet(discrete_set) => vis.visit_discrete_set(discrete_set), + IndexElement::IndexSet(index_set) => vis.visit_index_set(index_set), + } +} + +pub fn walk_discrete_set(vis: &mut impl MutVisitor, set: &mut DiscreteSet) { + vis.visit_span(&mut set.span); + set.values.iter_mut().for_each(|e| vis.visit_expr(e)); +} + +pub fn walk_index_set(vis: &mut impl MutVisitor, set: &mut IndexSet) { + vis.visit_span(&mut set.span); + set.values + .iter_mut() + .for_each(|item| vis.visit_index_set_item(item)); +} + +pub fn walk_index_set_item(vis: &mut impl MutVisitor, item: &mut IndexSetItem) { + match item { + IndexSetItem::RangeDefinition(range_definition) => { + vis.visit_range_definition(range_definition); + } + IndexSetItem::Expr(expr) => vis.visit_expr(expr), + IndexSetItem::Err => {} + } +} + +pub fn walk_gate_operand(vis: &mut impl MutVisitor, operand: &mut GateOperand) { + vis.visit_span(&mut operand.span); + match &mut operand.kind { + GateOperandKind::IndexedIdent(ident) => vis.visit_indexed_ident(ident), + GateOperandKind::HardwareQubit(hardware_qubit) => vis.visit_hardware_qubit(hardware_qubit), + GateOperandKind::Err => {} + } +} + +pub fn walk_tydef(vis: &mut impl MutVisitor, ty: &mut TypeDef) { + match ty { + TypeDef::Array(array) => vis.visit_array_type(array), + TypeDef::ArrayReference(array_ref) => vis.visit_array_ref_type(array_ref), + TypeDef::Scalar(scalar) => vis.visit_scalar_type(scalar), + } +} + +pub fn walk_array_type(vis: &mut impl MutVisitor, ty: &mut ArrayType) { + vis.visit_span(&mut ty.span); + vis.visit_array_base_type(&mut ty.base_type); + ty.dimensions.iter_mut().for_each(|d| vis.visit_expr(d)); +} + +pub fn walk_array_base_type(vis: &mut impl MutVisitor, ty: &mut ArrayBaseTypeKind) { + match ty { + ArrayBaseTypeKind::Int(ty) => vis.visit_span(&mut ty.span), + ArrayBaseTypeKind::UInt(ty) => vis.visit_span(&mut ty.span), + ArrayBaseTypeKind::Float(ty) => vis.visit_span(&mut ty.span), + ArrayBaseTypeKind::Complex(ty) => vis.visit_span(&mut ty.span), + ArrayBaseTypeKind::Angle(ty) => vis.visit_span(&mut ty.span), + _ => {} + } +} + +pub fn walk_array_ref_type(vis: &mut impl MutVisitor, ty: &mut ArrayReferenceType) { + vis.visit_span(&mut ty.span); + vis.visit_access_control(&mut ty.mutability); + vis.visit_array_base_type(&mut ty.base_type); + ty.dimensions.iter_mut().for_each(|d| vis.visit_expr(d)); +} + +pub fn walk_scalar_type(vis: &mut impl MutVisitor, ty: &mut ScalarType) { + vis.visit_span(&mut ty.span); + match &mut ty.kind { + super::ast::ScalarTypeKind::Bit(ty) => vis.visit_span(&mut ty.span), + super::ast::ScalarTypeKind::Int(ty) => vis.visit_span(&mut ty.span), + super::ast::ScalarTypeKind::UInt(ty) => vis.visit_span(&mut ty.span), + super::ast::ScalarTypeKind::Float(ty) => vis.visit_span(&mut ty.span), + super::ast::ScalarTypeKind::Complex(ty) => vis.visit_span(&mut ty.span), + super::ast::ScalarTypeKind::Angle(ty) => vis.visit_span(&mut ty.span), + _ => {} + } +} + +pub fn walk_typed_parameter(vis: &mut impl MutVisitor, ty: &mut TypedParameter) { + match ty { + TypedParameter::ArrayReference(array_typed_parameter) => { + vis.visit_array_typed_parameter(array_typed_parameter); + } + TypedParameter::Quantum(quantum_typed_parameter) => { + vis.visit_quantum_typed_parameter(quantum_typed_parameter); + } + TypedParameter::Scalar(scalar_typed_parameter) => { + vis.visit_scalar_typed_parameter(scalar_typed_parameter); + } + } +} + +pub fn walk_array_typed_parameter(vis: &mut impl MutVisitor, ty: &mut ArrayTypedParameter) { + vis.visit_span(&mut ty.span); + vis.visit_ident(&mut ty.ident); + vis.visit_array_ref_type(&mut ty.ty); +} + +pub fn walk_quantum_typed_parameter(vis: &mut impl MutVisitor, ty: &mut QuantumTypedParameter) { + vis.visit_span(&mut ty.span); + vis.visit_ident(&mut ty.ident); + ty.size.iter_mut().for_each(|s| vis.visit_expr(s)); +} + +pub fn walk_scalar_typed_parameter(vis: &mut impl MutVisitor, ty: &mut ScalarTypedParameter) { + vis.visit_span(&mut ty.span); + vis.visit_ident(&mut ty.ident); + vis.visit_scalar_type(&mut ty.ty); +} + +pub fn walk_extern_parameter(vis: &mut impl MutVisitor, param: &mut ExternParameter) { + match param { + ExternParameter::ArrayReference(ty, span) => { + vis.visit_span(span); + vis.visit_array_ref_type(ty); + } + ExternParameter::Scalar(ty, span) => { + vis.visit_span(span); + vis.visit_scalar_type(ty); + } + } +} + +pub fn walk_enumerable_set(vis: &mut impl MutVisitor, set: &mut EnumerableSet) { + match set { + EnumerableSet::DiscreteSet(set) => vis.visit_discrete_set(set), + EnumerableSet::RangeDefinition(range_definition) => { + vis.visit_range_definition(range_definition); + } + EnumerableSet::Expr(expr) => vis.visit_expr(expr), + } +} + +pub fn walk_gate_modifier(vis: &mut impl MutVisitor, modifier: &mut QuantumGateModifier) { + vis.visit_span(&mut modifier.span); + vis.visit_span(&mut modifier.modifier_keyword_span); + match &mut modifier.kind { + GateModifierKind::Inv => {} + GateModifierKind::Pow(expr) => vis.visit_expr(expr), + GateModifierKind::Ctrl(expr) => expr.iter_mut().for_each(|e| vis.visit_expr(e)), + GateModifierKind::NegCtrl(expr) => expr.iter_mut().for_each(|e| vis.visit_expr(e)), + } +} + +pub fn walk_hardware_qubit(vis: &mut impl MutVisitor, operand: &mut HardwareQubit) { + vis.visit_span(&mut operand.span); +} + +pub fn walk_range_definition(vis: &mut impl MutVisitor, range: &mut RangeDefinition) { + vis.visit_span(&mut range.span); + range.start.iter_mut().for_each(|s| vis.visit_expr(s)); + range.step.iter_mut().for_each(|s| vis.visit_expr(s)); + range.end.iter_mut().for_each(|s| vis.visit_expr(s)); +} diff --git a/compiler/qsc_qasm/src/parser/prgm.rs b/compiler/qsc_qasm/src/parser/prgm.rs new file mode 100644 index 0000000000..3cd6a5f905 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/prgm.rs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use super::{ + prim::{many, opt, recovering, recovering_semi, recovering_token, token}, + stmt, Result, +}; +use crate::{ + lex::{Delim, TokenKind}, + parser::{completion::word_kinds::WordKinds, expr}, +}; + +use super::ast::{Program, Stmt, StmtKind, Version}; + +use super::ParserContext; + +/// Grammar: `version? statementOrScope* EOF`. +pub(super) fn parse(s: &mut ParserContext) -> Program { + let lo = s.peek().span.lo; + let version = opt(s, parse_version).unwrap_or_default(); + let stmts = parse_top_level_nodes(s).unwrap_or_default(); + + Program { + span: s.span(lo), + version, + statements: stmts + .into_iter() + .map(Box::new) + .collect::>() + .into_boxed_slice(), + } +} + +/// Grammar: `OPENQASM VersionSpecifier SEMICOLON`. +fn parse_version(s: &mut ParserContext<'_>) -> Result { + s.expect(WordKinds::OpenQASM); + token(s, TokenKind::Keyword(crate::keyword::Keyword::OpenQASM))?; + let next = s.peek(); + if let Some(version) = expr::version(s)? { + recovering_semi(s); + Ok(version) + } else { + Err(crate::parser::error::Error::new( + crate::parser::error::ErrorKind::Lit("version", next.span), + )) + } +} + +pub(super) fn parse_top_level_nodes(s: &mut ParserContext) -> Result> { + const RECOVERY_TOKENS: &[TokenKind] = &[TokenKind::Semicolon, TokenKind::Close(Delim::Brace)]; + let nodes = { + many(s, |s| { + recovering( + s, + |span| Stmt { + span, + annotations: Vec::new().into_boxed_slice(), + kind: Box::new(StmtKind::Err), + }, + RECOVERY_TOKENS, + parse_top_level_node, + ) + }) + }?; + recovering_token(s, TokenKind::Eof); + Ok(nodes) +} + +fn parse_top_level_node(s: &mut ParserContext) -> Result { + if let Some(block) = opt(s, stmt::parse_block)? { + Ok(Stmt { + span: block.span, + annotations: Vec::new().into_boxed_slice(), + kind: Box::new(StmtKind::Block(block)), + }) + } else { + Ok(stmt::parse(s)?) + } +} diff --git a/compiler/qsc_qasm/src/parser/prim.rs b/compiler/qsc_qasm/src/parser/prim.rs new file mode 100644 index 0000000000..f882c47785 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/prim.rs @@ -0,0 +1,238 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#[cfg(test)] +pub(crate) mod tests; + +use super::ast::Ident; +use super::{ + error::{Error, ErrorKind}, + scan::ParserContext, + Parser, Result, +}; +use crate::lex::TokenKind; + +use qsc_data_structures::span::{Span, WithSpan}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(super) enum FinalSep { + Present, + Missing, +} + +pub(super) fn token(s: &mut ParserContext, t: TokenKind) -> Result<()> { + if let TokenKind::Keyword(k) = t { + s.expect(k.into()); + } + + if s.peek().kind == t { + s.advance(); + Ok(()) + } else { + Err(Error::new(ErrorKind::Token( + t, + s.peek().kind, + s.peek().span, + ))) + } +} + +pub(super) fn ident(s: &mut ParserContext) -> Result { + let peek = s.peek(); + if peek.kind == TokenKind::Identifier { + let name = s.read().into(); + s.advance(); + Ok(Ident { + span: peek.span, + name, + }) + } else { + Err(Error::new(ErrorKind::Rule( + "identifier", + peek.kind, + peek.span, + ))) + } +} + +/// Optionally parse with the given parser. +/// Returns Ok(Some(value)) if the parser succeeded, +/// Ok(None) if the parser failed on the first token, +/// Err(error) if the parser failed after consuming some tokens. +pub(super) fn opt(s: &mut ParserContext, mut p: impl Parser) -> Result> { + let offset = s.peek().span.lo; + match p(s) { + Ok(x) => Ok(Some(x)), + Err(error) if advanced(s, offset) => Err(error), + Err(_) => Ok(None), + } +} + +pub(super) fn many(s: &mut ParserContext, mut p: impl Parser) -> Result> { + let mut xs = Vec::new(); + while let Some(x) = opt(s, &mut p)? { + xs.push(x); + } + Ok(xs) +} + +/// Parses a sequence of items separated by commas. +/// Supports recovering on missing items. +pub(super) fn seq(s: &mut ParserContext, mut p: impl Parser) -> Result<(Vec, FinalSep)> +where + T: Default + WithSpan, +{ + let mut xs = Vec::new(); + let mut final_sep = FinalSep::Missing; + while s.peek().kind == TokenKind::Comma { + let mut span = s.peek().span; + span.hi = span.lo; + s.push_error(Error::new(ErrorKind::MissingSeqEntry(span))); + xs.push(T::default().with_span(span)); + s.advance(); + } + while let Some(x) = opt(s, &mut p)? { + xs.push(x); + if token(s, TokenKind::Comma).is_ok() { + while s.peek().kind == TokenKind::Comma { + let mut span = s.peek().span; + span.hi = span.lo; + s.push_error(Error::new(ErrorKind::MissingSeqEntry(span))); + xs.push(T::default().with_span(span)); + s.advance(); + } + final_sep = FinalSep::Present; + } else { + final_sep = FinalSep::Missing; + break; + } + } + Ok((xs, final_sep)) +} + +#[derive(Clone, Copy, Debug)] +pub enum SeqItem { + Item(T), + Missing(Span), +} + +impl std::fmt::Display for SeqItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SeqItem::Item(x) => write!(f, "{x}"), + SeqItem::Missing(span) => write!(f, "Missing {span}"), + } + } +} + +impl SeqItem { + pub fn item(self) -> Option { + match self { + SeqItem::Item(x) => Some(x), + SeqItem::Missing(_) => None, + } + } + + pub fn item_as_ref(&self) -> Option<&T> { + match self { + SeqItem::Item(x) => Some(x), + SeqItem::Missing(_) => None, + } + } + + pub fn is_missing(&self) -> bool { + matches!(self, SeqItem::Missing(_)) + } +} + +/// Parses a sequence of items separated by commas. +/// Supports recovering on missing items. +pub(super) fn seq_item( + s: &mut ParserContext, + mut p: impl Parser, +) -> Result<(Vec>, FinalSep)> { + let mut xs = Vec::new(); + let mut final_sep = FinalSep::Missing; + while s.peek().kind == TokenKind::Comma { + let mut span = s.peek().span; + span.hi = span.lo; + s.push_error(Error::new(ErrorKind::MissingSeqEntry(span))); + xs.push(SeqItem::Missing(span)); + s.advance(); + } + while let Some(x) = opt(s, &mut p)? { + xs.push(SeqItem::Item(x)); + if token(s, TokenKind::Comma).is_ok() { + while s.peek().kind == TokenKind::Comma { + let mut span = s.peek().span; + span.hi = span.lo; + s.push_error(Error::new(ErrorKind::MissingSeqEntry(span))); + xs.push(SeqItem::Missing(span)); + s.advance(); + } + final_sep = FinalSep::Present; + } else { + final_sep = FinalSep::Missing; + break; + } + } + Ok((xs, final_sep)) +} + +/// Try to parse with the given parser. +/// +/// If the parser fails on the first token, propagates the error. +/// +/// If the parser fails after consuming some tokens, performs +/// recovery by advancing until the next token in `tokens` is found. +/// The recovery token is consumed. +pub(super) fn recovering( + s: &mut ParserContext, + default: impl FnOnce(Span) -> T, + tokens: &[TokenKind], + mut p: impl Parser, +) -> Result { + let offset = s.peek().span.lo; + match p(s) { + Ok(value) => Ok(value), + Err(error) if advanced(s, offset) => { + s.push_error(error); + s.recover(tokens); + Ok(default(s.span(offset))) + } + Err(error) => Err(error), + } +} + +pub(super) fn recovering_semi(s: &mut ParserContext) { + if let Err(error) = token(s, TokenKind::Semicolon) { + // no recovery, just move on to the next token + s.push_error(error); + } +} + +pub(super) fn recovering_token(s: &mut ParserContext, t: TokenKind) { + if let Err(error) = token(s, t) { + s.push_error(error); + s.recover(&[t]); + } +} + +pub(super) fn barrier<'a, T>( + s: &mut ParserContext<'a>, + tokens: &'a [TokenKind], + mut p: impl Parser, +) -> Result { + s.push_barrier(tokens); + let result = p(s); + s.pop_barrier().expect("barrier should be popped"); + result +} + +pub(super) fn shorten(from_start: usize, from_end: usize, s: &str) -> &str { + &s[from_start..s.len() - from_end] +} + +fn advanced(s: &ParserContext, from: u32) -> bool { + s.peek().span.lo > from +} diff --git a/compiler/qsc_qasm/src/parser/prim/tests.rs b/compiler/qsc_qasm/src/parser/prim/tests.rs new file mode 100644 index 0000000000..59d35df4f9 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/prim/tests.rs @@ -0,0 +1,340 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use super::{ident, opt, seq, token}; +use crate::{ + keyword::Keyword, + lex::TokenKind, + parser::ast::{IncompletePath, Path, PathKind}, + parser::{ + completion::word_kinds::WordKinds, + error::{Error, ErrorKind}, + scan::ParserContext, + tests::{check, check_opt, check_seq}, + }, +}; +use expect_test::expect; + +use qsc_data_structures::span::Span; + +/// A `path` is a dot-separated list of idents like "Foo.Bar.Baz" +/// this can be a namespace name (in an open statement or namespace declaration), +/// a reference to an item, like `Microsoft.Quantum.Diagnostics.DumpMachine`, +/// or a field access. +/// +/// Path parser. If parsing fails, also returns any valid segments +/// that were parsed up to the final `.` token. +pub(super) fn path( + s: &mut ParserContext, +) -> std::result::Result, (Error, Option>)> { + let lo = s.peek().span.lo; + let i = ident(s).map_err(|e| (e, None))?; + + let mut parts = vec![i]; + while token(s, TokenKind::Dot).is_ok() { + s.expect(WordKinds::PathSegment); + match ident(s) { + Ok(ident) => parts.push(ident), + Err(error) => { + let trivia_span = s.skip_trivia(); + let keyword = trivia_span.hi == trivia_span.lo + && matches!(s.peek().kind, TokenKind::Keyword(_)); + if keyword { + // Consume any keyword that comes immediately after the final + // dot, assuming it was intended to be part of the path. + s.advance(); + } + + return Err(( + error, + Some(Box::new(IncompletePath { + span: s.span(lo), + segments: parts.into(), + keyword, + })), + )); + } + } + } + + let name = parts.pop().expect("path should have at least one part"); + let namespace = if parts.is_empty() { + None + } else { + Some(parts.into()) + }; + + Ok(Box::new(Path { + span: s.span(lo), + segments: namespace, + name: name.into(), + })) +} + +/// Recovering [`Path`] parser. Parsing only fails if no segments +/// were successfully parsed. If any segments were successfully parsed, +/// returns a [`PathKind::Err`] containing the segments that were +/// successfully parsed up to the final `.` token. +fn recovering_path(s: &mut ParserContext) -> Result { + match path(s) { + Ok(path) => Ok(PathKind::Ok(path)), + Err((error, Some(incomplete_path))) => { + s.push_error(error); + Ok(PathKind::Err(Some(incomplete_path))) + } + Err((error, None)) => Err(error), + } +} + +#[test] +fn ident_basic() { + check(ident, "foo", &expect![[r#"Ident [0-3] "foo""#]]); +} + +#[test] +fn ident_num_suffix() { + check(ident, "foo2", &expect![[r#"Ident [0-4] "foo2""#]]); +} + +#[test] +fn ident_underscore_prefix() { + check(ident, "_foo", &expect![[r#"Ident [0-4] "_foo""#]]); +} + +#[test] +fn ident_num_prefix() { + check( + ident, + "2foo", + &expect![[r#" + Error( + Rule( + "identifier", + Literal( + Integer( + Decimal, + ), + ), + Span { + lo: 0, + hi: 1, + }, + ), + ) + "#]], + ); +} + +#[test] +#[ignore = "Need to talk through how to handle this"] +fn ident_keyword() { + for keyword in enum_iterator::all::() { + let mut scanner = ParserContext::new(keyword.as_str()); + let actual = ident(&mut scanner); + let span = Span { + lo: 0, + hi: keyword + .as_str() + .len() + .try_into() + .expect("keyword length should fit into u32"), + }; + + let expected = Error::new(ErrorKind::Rule( + "identifier", + TokenKind::Keyword(keyword), + span, + )); + + assert_eq!(actual, Err(expected), "{keyword}"); + } +} + +#[test] +fn path_single() { + check( + recovering_path, + "Foo", + &expect![[r#" + Path [0-3]: + name: Ident [0-3] "Foo" + segments: "#]], + ); +} + +#[test] +fn path_double() { + check( + recovering_path, + "Foo.Bar", + &expect![[r#" + Path [0-7]: + name: Ident [4-7] "Bar" + segments: + Ident [0-3] "Foo""#]], + ); +} + +#[test] +fn path_triple() { + check( + recovering_path, + "Foo.Bar.Baz", + &expect![[r#" + Path [0-11]: + name: Ident [8-11] "Baz" + segments: + Ident [0-3] "Foo" + Ident [4-7] "Bar""#]], + ); +} + +#[test] +fn path_trailing_dot() { + check( + recovering_path, + "Foo.Bar.", + &expect![[r#" + Err IncompletePath [0-8]: segments: + Ident [0-3] "Foo" + Ident [4-7] "Bar" + + [ + Error( + Rule( + "identifier", + Eof, + Span { + lo: 8, + hi: 8, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn path_followed_by_keyword() { + check( + recovering_path, + "Foo.Bar.in", + &expect![[r#" + Err IncompletePath [0-10]: segments: + Ident [0-3] "Foo" + Ident [4-7] "Bar" + + [ + Error( + Rule( + "identifier", + Keyword( + In, + ), + Span { + lo: 8, + hi: 10, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn opt_succeed() { + check_opt( + |s| opt(s, recovering_path), + "Foo.Bar", + &expect![[r#" + Path [0-7]: + name: Ident [4-7] "Bar" + segments: + Ident [0-3] "Foo""#]], + ); +} + +#[test] +fn opt_fail_no_consume() { + check_opt(|s| opt(s, recovering_path), "123", &expect!["None"]); +} + +#[test] +fn opt_fail_consume() { + check_opt( + |s| opt(s, recovering_path), + "Foo.$", + &expect![[r#" + Err IncompletePath [0-5]: segments: + Ident [0-3] "Foo" + + [ + Error( + Lex( + Unknown( + '$', + Span { + lo: 4, + hi: 5, + }, + ), + ), + ), + Error( + Rule( + "identifier", + Eof, + Span { + lo: 5, + hi: 5, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn seq_empty() { + check_seq(|s| seq(s, ident), "", &expect!["(, Missing)"]); +} + +#[test] +fn seq_single() { + check_seq( + |s| seq(s, ident), + "foo", + &expect![[r#"(Ident [0-3] "foo", Missing)"#]], + ); +} + +#[test] +fn seq_double() { + check_seq( + |s| seq(s, ident), + "foo, bar", + &expect![[r#" + (Ident [0-3] "foo", + Ident [5-8] "bar", Missing)"#]], + ); +} + +#[test] +fn seq_trailing() { + check_seq( + |s| seq(s, ident), + "foo, bar,", + &expect![[r#" + (Ident [0-3] "foo", + Ident [5-8] "bar", Present)"#]], + ); +} + +#[test] +fn seq_fail_no_consume() { + check_seq( + |s| seq(s, ident), + "foo, 2", + &expect![[r#"(Ident [0-3] "foo", Present)"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/scan.rs b/compiler/qsc_qasm/src/parser/scan.rs new file mode 100644 index 0000000000..c390041398 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/scan.rs @@ -0,0 +1,248 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::{ + lex::{Lexer, Token, TokenKind}, + parser::completion::{collector::ValidWordCollector, word_kinds::WordKinds}, +}; +use qsc_data_structures::span::Span; + +use super::error::ErrorKind; +use super::Error; + +#[derive(Debug)] +pub(super) struct NoBarrierError; + +pub(crate) struct ParserContext<'a> { + scanner: Scanner<'a>, + word_collector: Option<&'a mut ValidWordCollector>, +} + +/// Scans over the token stream. Notably enforces LL(1) parser behavior via +/// its lack of a [Clone] implementation and limited peek functionality. +/// This struct should never be clonable, and it should never be able to +/// peek more than one token ahead, to maintain LL(1) enforcement. +pub(super) struct Scanner<'a> { + input: &'a str, + tokens: Lexer<'a>, + barriers: Vec<&'a [TokenKind]>, + errors: Vec, + recovered_eof: bool, + peek: Token, + offset: u32, +} + +impl<'a> ParserContext<'a> { + pub fn new(input: &'a str) -> Self { + Self { + scanner: Scanner::new(input), + word_collector: None, + } + } + + pub fn with_word_collector(input: &'a str, word_collector: &'a mut ValidWordCollector) -> Self { + let mut scanner = Scanner::new(input); + + word_collector.did_advance(&mut scanner.peek, scanner.offset); + + Self { + scanner, + word_collector: Some(word_collector), + } + } + + pub(super) fn peek(&self) -> Token { + self.scanner.peek() + } + + pub(super) fn read(&self) -> &'a str { + self.scanner.read() + } + + pub(super) fn span(&self, from: u32) -> Span { + self.scanner.span(from) + } + + /// Advances the scanner to start of the the next valid token. + pub(super) fn advance(&mut self) { + self.scanner.advance(); + + if let Some(e) = &mut self.word_collector { + e.did_advance(&mut self.scanner.peek, self.scanner.offset); + } + } + + /// Moves the scanner to the start of the current token, + /// returning the span of the skipped trivia. + #[cfg(test)] + pub(super) fn skip_trivia(&mut self) -> Span { + self.scanner.skip_trivia() + } + + /// Pushes a recovery barrier. While the barrier is active, recovery will never advance past any + /// of the barrier tokens, unless it is explicitly listed as a recovery token. + pub(super) fn push_barrier(&mut self, tokens: &'a [TokenKind]) { + self.scanner.push_barrier(tokens); + } + + /// Pops the most recently pushed active barrier. + pub(super) fn pop_barrier(&mut self) -> Result<(), NoBarrierError> { + self.scanner.pop_barrier() + } + + /// Tries to recover from a parse error by advancing tokens until any of the given recovery + /// tokens, or a barrier token, is found. If a recovery token is found, it is consumed. If a + /// barrier token is found first, it is not consumed. + pub(super) fn recover(&mut self, tokens: &[TokenKind]) { + self.scanner.recover(tokens); + } + + pub(super) fn push_error(&mut self, error: Error) { + self.scanner.push_error(error); + + if let Some(e) = &mut self.word_collector { + e.did_error(); + } + } + + pub(super) fn into_errors(self) -> Vec { + self.scanner.into_errors() + } + + pub fn expect(&mut self, expected: WordKinds) { + if let Some(e) = &mut self.word_collector { + e.expect(expected); + } + } +} + +impl<'a> Scanner<'a> { + pub(super) fn new(input: &'a str) -> Self { + let mut tokens = Lexer::new(input); + let (peek, errors) = next_ok(&mut tokens); + Self { + input, + tokens, + barriers: Vec::new(), + peek: peek.unwrap_or_else(|| eof(input.len())), + errors: errors + .into_iter() + .map(|e| Error::new(ErrorKind::Lex(e))) + .collect(), + offset: 0, + recovered_eof: false, + } + } + + pub(super) fn peek(&self) -> Token { + self.peek + } + + pub(super) fn read(&self) -> &'a str { + &self.input[self.peek.span] + } + + pub(super) fn span(&self, from: u32) -> Span { + Span { + lo: from, + hi: self.offset, + } + } + + /// Moves the scanner to the start of the current token, + /// returning the span of the skipped trivia. + #[cfg(test)] + pub(super) fn skip_trivia(&mut self) -> Span { + let lo = self.offset; + self.offset = self.peek.span.lo; + let hi = self.offset; + Span { lo, hi } + } + + pub(super) fn advance(&mut self) { + if self.peek.kind != TokenKind::Eof { + self.offset = self.peek.span.hi; + let (peek, errors) = next_ok(&mut self.tokens); + self.errors + .extend(errors.into_iter().map(|e| Error::new(ErrorKind::Lex(e)))); + self.peek = peek.unwrap_or_else(|| eof(self.input.len())); + } + } + + /// Pushes a recovery barrier. While the barrier is active, recovery will never advance past any + /// of the barrier tokens, unless it is explicitly listed as a recovery token. + pub(super) fn push_barrier(&mut self, tokens: &'a [TokenKind]) { + self.barriers.push(tokens); + } + + /// Pops the most recently pushed active barrier. + pub(super) fn pop_barrier(&mut self) -> Result<(), NoBarrierError> { + match self.barriers.pop() { + Some(_) => Ok(()), + None => Err(NoBarrierError), + } + } + + /// Tries to recover from a parse error by advancing tokens until any of the given recovery + /// tokens, or a barrier token, is found. If a recovery token is found, it is consumed. If a + /// barrier token is found first, it is not consumed. + pub(super) fn recover(&mut self, tokens: &[TokenKind]) { + loop { + let peek = self.peek.kind; + if contains(peek, tokens) { + self.advance(); + break; + } + if peek == TokenKind::Eof || self.barriers.iter().any(|&b| contains(peek, b)) { + break; + } + + self.advance(); + } + } + + pub(super) fn push_error(&mut self, error: Error) { + let is_eof_err = matches!( + error.0, + ErrorKind::Token(_, TokenKind::Eof, _) | ErrorKind::Rule(_, TokenKind::Eof, _) + ); + if !is_eof_err || !self.recovered_eof { + self.errors.push(error); + self.recovered_eof = self.recovered_eof || is_eof_err; + } + } + + pub(super) fn into_errors(self) -> Vec { + self.errors + } +} + +fn eof(offset: usize) -> Token { + let offset = offset.try_into().expect("eof offset should fit into u32"); + Token { + kind: TokenKind::Eof, + span: Span { + lo: offset, + hi: offset, + }, + } +} + +/// Advances the iterator by skipping [`Err`] values until the first [`Ok`] value is found. Returns +/// the found value or [`None`] if the iterator is exhausted. All skipped errors are also +/// accumulated into a vector and returned. +fn next_ok(iter: impl Iterator>) -> (Option, Vec) { + let mut errors = Vec::new(); + for result in iter { + match result { + Ok(v) => return (Some(v), errors), + Err(e) => errors.push(e), + } + } + + (None, errors) +} + +fn contains<'a>(token: TokenKind, tokens: impl IntoIterator) -> bool { + tokens.into_iter().any(|&t| t == token) +} diff --git a/compiler/qsc_qasm/src/parser/stmt.rs b/compiler/qsc_qasm/src/parser/stmt.rs new file mode 100644 index 0000000000..a911f1c669 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt.rs @@ -0,0 +1,1802 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#[cfg(test)] +pub(crate) mod tests; + +use qsc_data_structures::span::Span; +use std::rc::Rc; + +use super::{ + completion::word_kinds::WordKinds, + error::{Error, ErrorKind}, + expr::{self, designator, gate_operand, indexed_identifier}, + prim::{ + self, barrier, many, opt, recovering, recovering_semi, recovering_token, seq, seq_item, + shorten, SeqItem, + }, + Result, +}; +use crate::{ + keyword::Keyword, + lex::{cooked::Type, Delim, TokenKind}, +}; + +use super::ast::{ + list_from_iter, AccessControl, AliasDeclStmt, AngleType, Annotation, ArrayBaseTypeKind, + ArrayReferenceType, ArrayType, ArrayTypedParameter, AssignOpStmt, AssignStmt, BarrierStmt, + BitType, Block, BoxStmt, BreakStmt, CalibrationGrammarStmt, CalibrationStmt, Cast, + ClassicalDeclarationStmt, ComplexType, ConstantDeclStmt, ContinueStmt, DefCalStmt, DefStmt, + DelayStmt, EndStmt, EnumerableSet, Expr, ExprKind, ExprStmt, ExternDecl, ExternParameter, + FloatType, ForStmt, FunctionCall, GPhase, GateCall, GateModifierKind, GateOperand, + IODeclaration, IOKeyword, Ident, Identifier, IfStmt, IncludeStmt, IndexElement, IndexExpr, + IndexSetItem, IndexedIdent, IntType, List, LiteralKind, MeasureArrowStmt, Pragma, + QuantumGateDefinition, QuantumGateModifier, QuantumTypedParameter, QubitDeclaration, + RangeDefinition, ResetStmt, ReturnStmt, ScalarType, ScalarTypeKind, ScalarTypedParameter, Stmt, + StmtKind, SwitchCase, SwitchStmt, TypeDef, TypedParameter, UIntType, WhileLoop, +}; +use super::{prim::token, ParserContext}; + +/// Our implementation differs slightly from the grammar in +/// that we accumulate annotations and append them to the next +/// consecutive statement. +/// +/// Grammar: +/// ```g4 +/// pragma +/// | annotation* ( +/// aliasDeclarationStatement +/// | assignmentStatement +/// | barrierStatement +/// | boxStatement +/// | breakStatement +/// | calStatement +/// | calibrationGrammarStatement +/// | classicalDeclarationStatement +/// | constDeclarationStatement +/// | continueStatement +/// | defStatement +/// | defcalStatement +/// | delayStatement +/// | endStatement +/// | expressionStatement +/// | externStatement +/// | forStatement +/// | gateCallStatement +/// | gateStatement +/// | ifStatement +/// | includeStatement +/// | ioDeclarationStatement +/// | measureArrowAssignmentStatement +/// | oldStyleDeclarationStatement +/// | quantumDeclarationStatement +/// | resetStatement +/// | returnStatement +/// | switchStatement +/// | whileStatement +/// ) +/// ``` +pub(super) fn parse(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + if let Some(pragma) = opt(s, parse_pragma)? { + return Ok(Stmt { + span: s.span(lo), + annotations: [].into(), + kind: Box::new(StmtKind::Pragma(pragma)), + }); + } + let attrs = many(s, parse_annotation)?; + + let kind = if token(s, TokenKind::Semicolon).is_ok() { + if attrs.is_empty() { + let err_item = default(s.span(lo)); + s.push_error(Error::new(ErrorKind::EmptyStatement(err_item.span))); + return Ok(err_item); + } + let lo = attrs.iter().map(|a| a.span.lo).min().unwrap_or_default(); + let hi = attrs.iter().map(|a| a.span.hi).max().unwrap_or_default(); + let err_item = default(Span { lo, hi }); + s.push_error(Error::new(ErrorKind::FloatingAnnotation(err_item.span))); + return Ok(err_item); + } else if let Some(decl) = opt(s, parse_gatedef)? { + decl + } else if let Some(decl) = opt(s, parse_def)? { + decl + } else if let Some(include) = opt(s, parse_include)? { + include + } else if let Some(ty) = opt(s, scalar_or_array_type)? { + disambiguate_type(s, ty)? + } else if let Some(decl) = opt(s, parse_constant_classical_decl)? { + decl + } else if let Some(decl) = opt(s, parse_quantum_decl)? { + decl + } else if let Some(decl) = opt(s, parse_io_decl)? { + decl + } else if let Some(decl) = opt(s, qreg_decl)? { + decl + } else if let Some(decl) = opt(s, creg_decl)? { + decl + } else if let Some(decl) = opt(s, parse_extern)? { + decl + } else if let Some(switch) = opt(s, parse_switch_stmt)? { + StmtKind::Switch(switch) + } else if let Some(stmt) = opt(s, parse_if_stmt)? { + StmtKind::If(stmt) + } else if let Some(stmt) = opt(s, parse_for_stmt)? { + StmtKind::For(stmt) + } else if let Some(stmt) = opt(s, parse_while_loop)? { + StmtKind::WhileLoop(stmt) + } else if let Some(stmt) = opt(s, parse_return)? { + stmt + } else if let Some(stmt) = opt(s, parse_continue_stmt)? { + StmtKind::Continue(stmt) + } else if let Some(stmt) = opt(s, parse_break_stmt)? { + StmtKind::Break(stmt) + } else if let Some(stmt) = opt(s, parse_end_stmt)? { + StmtKind::End(stmt) + } else if let Some(indexed_ident) = opt(s, indexed_identifier)? { + disambiguate_ident(s, indexed_ident)? + } else if let Some(stmt_kind) = opt(s, parse_gate_call_stmt)? { + stmt_kind + } else if let Some(stmt) = opt(s, |s| parse_expression_stmt(s, None))? { + StmtKind::ExprStmt(stmt) + } else if let Some(alias) = opt(s, parse_alias_stmt)? { + StmtKind::Alias(alias) + } else if let Some(stmt) = opt(s, parse_box)? { + StmtKind::Box(stmt) + } else if let Some(stmt) = opt(s, parse_calibration_grammar_stmt)? { + StmtKind::CalibrationGrammar(stmt) + } else if let Some(stmt) = opt(s, parse_defcal_stmt)? { + StmtKind::DefCal(stmt) + } else if let Some(stmt) = opt(s, parse_cal)? { + StmtKind::Cal(stmt) + } else if let Some(stmt) = opt(s, parse_barrier)? { + StmtKind::Barrier(stmt) + } else if let Some(stmt) = opt(s, parse_delay)? { + StmtKind::Delay(stmt) + } else if let Some(stmt) = opt(s, parse_reset)? { + StmtKind::Reset(stmt) + } else if let Some(stmt) = opt(s, parse_measure_stmt)? { + StmtKind::Measure(stmt) + } else { + return if attrs.is_empty() { + Err(Error::new(ErrorKind::Rule( + "statement", + s.peek().kind, + s.peek().span, + ))) + } else { + let span = attrs.last().expect("there is at least one annotation").span; + Err(Error::new(ErrorKind::FloatingAnnotation(span))) + }; + }; + + Ok(Stmt { + span: s.span(lo), + annotations: attrs.into_boxed_slice(), + kind: Box::new(kind), + }) +} + +/// This helper function allows us to disambiguate between +/// non-constant declarations and cast expressions when +/// reading a `TypeDef`. +fn disambiguate_type(s: &mut ParserContext, ty: TypeDef) -> Result { + let lo = ty.span().lo; + if matches!(s.peek().kind, TokenKind::Identifier) { + Ok(parse_non_constant_classical_decl(s, ty, lo)?) + } else { + token(s, TokenKind::Open(Delim::Paren))?; + let arg = expr::expr(s)?; + recovering_token(s, TokenKind::Close(Delim::Paren)); + let cast_expr = Expr { + span: s.span(ty.span().lo), + kind: Box::new(ExprKind::Cast(Cast { + span: s.span(ty.span().lo), + ty, + arg, + })), + }; + Ok(StmtKind::ExprStmt(parse_expression_stmt( + s, + Some(cast_expr), + )?)) + } +} + +/// This helper function allows us to disambiguate between +/// assignments, assignment operations, gate calls, and +/// `expr_stmts` beginning with an ident or a function call +/// when reading an `Ident`. +fn disambiguate_ident(s: &mut ParserContext, indexed_ident: IndexedIdent) -> Result { + let lo = indexed_ident.span.lo; + if s.peek().kind == TokenKind::Eq { + s.advance(); + let expr = expr::expr_or_measurement(s)?; + recovering_semi(s); + Ok(StmtKind::Assign(AssignStmt { + span: s.span(lo), + lhs: indexed_ident, + rhs: expr, + })) + } else if let TokenKind::BinOpEq(op) = s.peek().kind { + s.advance(); + let op = expr::closed_bin_op(op); + let expr = expr::expr_or_measurement(s)?; + recovering_semi(s); + Ok(StmtKind::AssignOp(AssignOpStmt { + span: s.span(lo), + op, + lhs: indexed_ident, + rhs: expr, + })) + } else if s.peek().kind == TokenKind::Open(Delim::Paren) { + if !indexed_ident.indices.is_empty() { + s.push_error(Error::new(ErrorKind::Convert( + "Ident", + "IndexedIdent", + indexed_ident.span, + ))); + } + + let ident = indexed_ident.name; + + s.advance(); + let (args, _) = seq(s, expr::expr)?; + token(s, TokenKind::Close(Delim::Paren))?; + + let funcall = Expr { + span: s.span(lo), + kind: Box::new(ExprKind::FunctionCall(FunctionCall { + span: s.span(lo), + name: ident, + args: args.into_iter().map(Box::new).collect(), + })), + }; + + let expr = expr::expr_with_lhs(s, funcall)?; + + Ok(parse_gate_call_with_expr(s, expr)?) + } else { + let kind = if indexed_ident.indices.is_empty() { + ExprKind::Ident(indexed_ident.name) + } else { + if indexed_ident.indices.len() > 1 { + s.push_error(Error::new(ErrorKind::MultipleIndexOperators( + indexed_ident.span, + ))); + } + + ExprKind::IndexExpr(IndexExpr { + span: indexed_ident.span, + collection: Expr { + span: indexed_ident.name.span, + kind: Box::new(ExprKind::Ident(indexed_ident.name)), + }, + index: *indexed_ident.indices[0].clone(), + }) + }; + + let expr = Expr { + span: indexed_ident.span, + kind: Box::new(kind), + }; + + Ok(parse_gate_call_with_expr(s, expr)?) + } +} + +#[allow(clippy::vec_box)] +pub(super) fn parse_many(s: &mut ParserContext) -> Result> { + many(s, |s| { + recovering(s, default, &[TokenKind::Semicolon], |s| { + parse_block_or_stmt(s) + }) + }) +} + +/// Grammar: `LBRACE statementOrScope* RBRACE`. +pub(super) fn parse_block(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Open(Delim::Brace))?; + let stmts = barrier(s, &[TokenKind::Close(Delim::Brace)], parse_many)?; + recovering_token(s, TokenKind::Close(Delim::Brace)); + Ok(Block { + span: s.span(lo), + stmts: list_from_iter(stmts), + }) +} + +#[allow(clippy::unnecessary_box_returns)] +fn default(span: Span) -> Stmt { + Stmt { + span, + annotations: Vec::new().into_boxed_slice(), + kind: Box::new(StmtKind::Err), + } +} + +/// Grammar: `AnnotationKeyword RemainingLineContent?`. +pub fn parse_annotation(s: &mut ParserContext) -> Result> { + let lo = s.peek().span.lo; + s.expect(WordKinds::Annotation); + + let token = s.peek(); + let pat = &['\t', ' ']; + let parts: Vec<&str> = if token.kind == TokenKind::Annotation { + let lexeme = s.read(); + s.advance(); + // remove @ + // split lexeme at first space/tab collecting each side + shorten(1, 0, lexeme).splitn(2, pat).collect() + } else { + return Err(Error::new(ErrorKind::Rule( + "annotation", + token.kind, + token.span, + ))); + }; + + let identifier = parts.first().map_or_else( + || { + Err(Error::new(ErrorKind::Rule( + "annotation", + token.kind, + token.span, + ))) + }, + |s| Ok(Into::>::into(*s)), + )?; + + if identifier.is_empty() { + s.push_error(Error::new(ErrorKind::Rule( + "annotation missing identifier", + token.kind, + token.span, + ))); + } + + // remove any leading whitespace from the value side + let value = parts + .get(1) + .map(|s| Into::>::into(s.trim_start_matches(pat))); + + Ok(Box::new(Annotation { + span: s.span(lo), + identifier, + value, + })) +} + +/// Grammar: `INCLUDE StringLiteral SEMICOLON`. +fn parse_include(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::Include))?; + let next = s.peek(); + + let lit = expr::lit(s)?; + recovering_semi(s); + + if let Some(lit) = lit { + if let LiteralKind::String(v) = lit.kind { + return Ok(StmtKind::Include(IncludeStmt { + span: s.span(lo), + filename: v.to_string(), + })); + } + } + Err(Error::new(ErrorKind::Rule( + "string literal", + next.kind, + next.span, + ))) +} + +/// Grammar: `PRAGMA RemainingLineContent`. +fn parse_pragma(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + s.expect(WordKinds::Pragma); + + let token = s.peek(); + + let parts: Vec<&str> = if token.kind == TokenKind::Pragma { + let lexeme = s.read(); + s.advance(); + // remove pragma keyword and any leading whitespace + // split lexeme at first space/tab collecting each side + let pat = &['\t', ' ']; + shorten(6, 0, lexeme) + .trim_start_matches(pat) + .splitn(2, pat) + .collect() + } else { + return Err(Error::new(ErrorKind::Rule( + "pragma", token.kind, token.span, + ))); + }; + + let identifier = parts.first().map_or_else( + || { + Err(Error::new(ErrorKind::Rule( + "pragma", token.kind, token.span, + ))) + }, + |s| Ok(Into::>::into(*s)), + )?; + + if identifier.is_empty() { + s.push_error(Error::new(ErrorKind::Rule( + "pragma missing identifier", + token.kind, + token.span, + ))); + } + let value = parts.get(1).map(|s| Into::>::into(*s)); + + Ok(Pragma { + span: s.span(lo), + identifier, + value, + }) +} + +/// Grammar: `EXTERN Identifier LPAREN externArgumentList? RPAREN returnSignature? SEMICOLON`. +fn parse_extern(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(crate::keyword::Keyword::Extern))?; + let ident = Box::new(prim::ident(s)?); + token(s, TokenKind::Open(Delim::Paren))?; + let (params, _) = seq(s, extern_arg_def)?; + token(s, TokenKind::Close(Delim::Paren))?; + let return_type = opt(s, return_sig)?; + recovering_semi(s); + let kind = StmtKind::ExternDecl(ExternDecl { + span: s.span(lo), + ident, + params: list_from_iter(params), + return_type, + }); + Ok(kind) +} + +/// Grammar: `DEF Identifier LPAREN argumentDefinitionList? RPAREN returnSignature? scope`. +fn parse_def(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(crate::keyword::Keyword::Def))?; + let name = Box::new(prim::ident(s)?); + token(s, TokenKind::Open(Delim::Paren))?; + let (exprs, _) = seq(s, arg_def)?; + token(s, TokenKind::Close(Delim::Paren))?; + let return_type = opt(s, return_sig)?.map(Box::new); + let body = parse_block(s)?; + let kind = StmtKind::Def(DefStmt { + span: s.span(lo), + name, + params: list_from_iter(exprs), + body, + return_type, + }); + Ok(kind) +} + +/// Grammar: `scalarType | arrayReferenceType | CREG designator?`. +fn extern_arg_def(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + + let kind = if let Ok(ty) = scalar_type(s) { + ExternParameter::Scalar(ty, s.span(lo)) + } else if let Ok(ty) = array_reference_ty(s) { + ExternParameter::ArrayReference(ty, s.span(lo)) + } else if let Ok(size) = extern_creg_type(s) { + let ty = ScalarType { + span: s.span(lo), + kind: ScalarTypeKind::Bit(BitType { + size, + span: s.span(lo), + }), + }; + ExternParameter::Scalar(ty, s.span(lo)) + } else { + return Err(Error::new(ErrorKind::Rule( + "extern argument definition", + s.peek().kind, + s.peek().span, + ))); + }; + Ok(kind) +} + +/// Grammar: +/// ```g4 +/// scalarType Identifier +/// | qubitType Identifier +/// | (CREG | QREG) Identifier designator? +/// | arrayReferenceType Identifier +/// ``` +fn arg_def(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + + let kind = if let Ok(ty) = scalar_type(s) { + let ident = prim::ident(s)?; + TypedParameter::Scalar(ScalarTypedParameter { + span: s.span(lo), + ty: Box::new(ty), + ident, + }) + } else if let Ok(size) = qubit_type(s) { + let ident = prim::ident(s)?; + TypedParameter::Quantum(QuantumTypedParameter { + span: s.span(lo), + size, + ident, + }) + } else if let Ok((ident, size)) = creg_type(s) { + let ty = ScalarType { + span: s.span(lo), + kind: ScalarTypeKind::Bit(BitType { + size, + span: s.span(lo), + }), + }; + TypedParameter::Scalar(ScalarTypedParameter { + span: s.span(lo), + ty: Box::new(ty), + ident, + }) + } else if let Ok((ident, size)) = qreg_type(s) { + TypedParameter::Quantum(QuantumTypedParameter { + span: s.span(lo), + size, + ident, + }) + } else if let Ok(ty) = array_reference_ty(s) { + let ident = prim::ident(s)?; + TypedParameter::ArrayReference(ArrayTypedParameter { + span: s.span(lo), + ty: Box::new(ty), + ident, + }) + } else { + return Err(Error::new(ErrorKind::Rule( + "argument definition", + s.peek().kind, + s.peek().span, + ))); + }; + Ok(kind) +} + +/// Grammar: +/// `(READONLY | MUTABLE) ARRAY LBRACKET scalarType COMMA (expressionList | DIM EQUALS expression) RBRACKET`. +fn array_reference_ty(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + + let mutability = if token(s, TokenKind::Keyword(crate::keyword::Keyword::ReadOnly)).is_ok() { + AccessControl::ReadOnly + } else if token(s, TokenKind::Keyword(crate::keyword::Keyword::Mutable)).is_ok() { + AccessControl::Mutable + } else { + let token = s.peek(); + return Err(Error::new(ErrorKind::Rule( + "array reference declaration", + token.kind, + token.span, + ))); + }; + token(s, TokenKind::Type(Type::Array))?; + token(s, TokenKind::Open(Delim::Bracket))?; + let base_type = array_base_type(s)?; + token(s, TokenKind::Comma)?; + + let dimensions = if token(s, TokenKind::Keyword(Keyword::Dim)).is_ok() { + token(s, TokenKind::Eq)?; + vec![expr::expr(s)?] + } else { + expr::expr_list(s)? + }; + + token(s, TokenKind::Close(Delim::Bracket))?; + Ok(ArrayReferenceType { + span: s.span(lo), + mutability, + base_type, + dimensions: list_from_iter(dimensions), + }) +} + +/// Grammar: `ARROW scalarType`. +fn return_sig(s: &mut ParserContext) -> Result { + token(s, TokenKind::Arrow)?; + scalar_type(s) +} + +/// Grammar: +/// `GATE Identifier (LPAREN params=identifierList? RPAREN)? qubits=identifierList scope`. +fn parse_gatedef(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(crate::keyword::Keyword::Gate))?; + let ident = Box::new(prim::ident(s)?); + let params = opt(s, gate_params)?.unwrap_or_else(Vec::new); + let (qubits, _) = seq_item(s, prim::ident)?; + let body = Box::new(parse_block(s)?); + Ok(StmtKind::QuantumGateDefinition(QuantumGateDefinition { + span: s.span(lo), + ident, + params: list_from_iter(params), + qubits: list_from_iter(qubits), + body, + })) +} + +fn gate_params(s: &mut ParserContext<'_>) -> Result>> { + token(s, TokenKind::Open(Delim::Paren))?; + let (params, _) = seq_item(s, prim::ident)?; + token(s, TokenKind::Close(Delim::Paren))?; + Ok(params) +} + +/// Grammar: `RETURN (expression | measureExpression)? SEMICOLON`. +fn parse_return(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(crate::keyword::Keyword::Return))?; + let expr = opt(s, expr::expr_or_measurement)?.map(Box::new); + recovering_semi(s); + Ok(StmtKind::Return(ReturnStmt { + span: s.span(lo), + expr, + })) +} + +/// Grammar: `qubitType Identifier SEMICOLON`. +fn parse_quantum_decl(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let size = qubit_type(s)?; + let ty_span = s.span(lo); + let ident = prim::ident(s)?; + + recovering_semi(s); + Ok(StmtKind::QuantumDecl(QubitDeclaration { + span: s.span(lo), + ty_span, + qubit: ident, + size, + })) +} + +/// Grammar: `QUBIT designator?`. +fn qubit_type(s: &mut ParserContext<'_>) -> Result> { + token(s, TokenKind::Keyword(crate::keyword::Keyword::Qubit))?; + let size = opt(s, designator)?; + Ok(size) +} + +/// Grammar: `(INPUT | OUTPUT) (scalarType | arrayType) Identifier SEMICOLON`. +fn parse_io_decl(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + + let kind = if token(s, TokenKind::Keyword(crate::keyword::Keyword::Input)).is_ok() { + IOKeyword::Input + } else if token(s, TokenKind::Keyword(crate::keyword::Keyword::Output)).is_ok() { + IOKeyword::Output + } else { + let token = s.peek(); + return Err(Error::new(ErrorKind::Rule( + "io declaration", + token.kind, + token.span, + ))); + }; + + let ty = scalar_or_array_type(s)?; + + let ident = Box::new(prim::ident(s)?); + recovering_semi(s); + let decl = IODeclaration { + span: s.span(lo), + io_identifier: kind, + ty, + ident, + }; + Ok(StmtKind::IODeclaration(decl)) +} + +/// Grammar `(scalarType | arrayType)`. +pub fn scalar_or_array_type(s: &mut ParserContext) -> Result { + if let Ok(v) = scalar_type(s) { + return Ok(TypeDef::Scalar(v)); + } + if let Ok(v) = array_type(s) { + return Ok(TypeDef::Array(v)); + } + Err(Error::new(ErrorKind::Rule( + "scalar or array type", + s.peek().kind, + s.peek().span, + ))) +} + +/// Grammar: `(scalarType | arrayType) Identifier (EQUALS declarationExpression)? SEMICOLON`. +fn parse_non_constant_classical_decl( + s: &mut ParserContext, + ty: TypeDef, + lo: u32, +) -> Result { + let identifier = prim::ident(s)?; + let init_expr = if s.peek().kind == TokenKind::Eq { + s.advance(); + Some(Box::new(expr::declaration_expr(s)?)) + } else { + None + }; + recovering_semi(s); + let decl = ClassicalDeclarationStmt { + span: s.span(lo), + ty: Box::new(ty), + identifier, + init_expr, + }; + + Ok(StmtKind::ClassicalDecl(decl)) +} + +/// Grammar: `CONST scalarType Identifier EQUALS declarationExpression SEMICOLON`. +fn parse_constant_classical_decl(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::Const))?; + let ty = scalar_or_array_type(s)?; + let identifier = Box::new(prim::ident(s)?); + token(s, TokenKind::Eq)?; + let init_expr = expr::const_declaration_expr(s)?; + recovering_semi(s); + let decl = ConstantDeclStmt { + span: s.span(lo), + ty, + identifier, + init_expr, + }; + + Ok(StmtKind::ConstDecl(decl)) +} + +/// The Spec and the grammar differ in the base type for arrays. We followed the Spec. +/// Grammar: `ARRAY LBRACKET arrayBaseType COMMA expressionList RBRACKET`. +pub(super) fn array_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Type(Type::Array))?; + token(s, TokenKind::Open(Delim::Bracket))?; + let kind = array_base_type(s)?; + token(s, TokenKind::Comma)?; + let expr_list = expr::expr_list(s)?; + token(s, TokenKind::Close(Delim::Bracket))?; + + Ok(ArrayType { + base_type: kind, + span: s.span(lo), + dimensions: list_from_iter(expr_list), + }) +} + +/// The Spec for 3.0, main Spec, and the grammar differ in the base type for arrays. +/// We followed the main Spec. +/// Grammar: +/// | INT designator? +/// | UINT designator? +/// | FLOAT designator? +/// | ANGLE designator? +/// | BOOL +/// | DURATION +/// | COMPLEX (LBRACKET scalarType RBRACKET)? +/// Reference: . +pub(super) fn array_base_type(s: &mut ParserContext) -> Result { + if let Ok(v) = array_angle_type(s) { + return Ok(v); + } + if let Ok(v) = array_bool_type(s) { + return Ok(v); + } + if let Ok(v) = array_int_type(s) { + return Ok(v); + } + if let Ok(v) = array_uint_type(s) { + return Ok(v); + } + if let Ok(v) = array_float_type(s) { + return Ok(v); + } + if let Ok(v) = array_complex_type(s) { + return Ok(v); + } + if let Ok(v) = array_duration_type(s) { + return Ok(v); + } + + Err(Error::new(ErrorKind::Rule( + "array type", + s.peek().kind, + s.peek().span, + ))) +} + +/// Grammar: +/// ```g4 +/// BIT designator? +/// | INT designator? +/// | UINT designator? +/// | FLOAT designator? +/// | ANGLE designator? +/// | BOOL +/// | DURATION +/// | STRETCH +/// | COMPLEX (LBRACKET scalarType RBRACKET)? +/// ``` +pub(super) fn scalar_type(s: &mut ParserContext) -> Result { + if let Ok(v) = scalar_bit_type(s) { + return Ok(v); + } + if let Ok(v) = scalar_angle_type(s) { + return Ok(v); + } + if let Ok(v) = scalar_bool_type(s) { + return Ok(v); + } + if let Ok(v) = scalar_int_type(s) { + return Ok(v); + } + if let Ok(v) = scalar_uint_type(s) { + return Ok(v); + } + if let Ok(v) = scalar_float_type(s) { + return Ok(v); + } + if let Ok(v) = scalar_complex_type(s) { + return Ok(v); + } + if let Ok(v) = scalar_duration_type(s) { + return Ok(v); + } + if let Ok(v) = scalar_stretch_type(s) { + return Ok(v); + } + Err(Error::new(ErrorKind::Rule( + "scalar type", + s.peek().kind, + s.peek().span, + ))) +} + +fn creg_decl(s: &mut ParserContext) -> Result { + let lo: u32 = s.peek().span.lo; + let (identifier, size) = creg_type(s)?; + recovering_semi(s); + Ok(StmtKind::ClassicalDecl(ClassicalDeclarationStmt { + span: s.span(lo), + ty: Box::new(TypeDef::Scalar(ScalarType { + span: s.span(lo), + kind: ScalarTypeKind::Bit(BitType { + size, + span: s.span(lo), + }), + })), + identifier, + init_expr: None, + })) +} + +fn qreg_decl(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let (identifier, size) = qreg_type(s)?; + recovering_semi(s); + Ok(StmtKind::QuantumDecl(QubitDeclaration { + span: s.span(lo), + ty_span: s.span(lo), + qubit: identifier, + size, + })) +} + +fn extern_creg_type(s: &mut ParserContext) -> Result> { + token(s, TokenKind::Keyword(crate::keyword::Keyword::CReg))?; + let size = opt(s, designator)?; + Ok(size) +} + +fn creg_type(s: &mut ParserContext) -> Result<(Ident, Option)> { + token(s, TokenKind::Keyword(crate::keyword::Keyword::CReg))?; + let name = prim::ident(s)?; + let size = opt(s, designator)?; + Ok((name, size)) +} + +fn qreg_type(s: &mut ParserContext) -> Result<(Ident, Option)> { + token(s, TokenKind::Keyword(crate::keyword::Keyword::QReg))?; + let name = prim::ident(s)?; + let size = opt(s, designator)?; + Ok((name, size)) +} + +fn scalar_bit_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Type(Type::Bit))?; + let size = opt(s, designator)?; + Ok(ScalarType { + span: s.span(lo), + kind: ScalarTypeKind::Bit(BitType { + size, + span: s.span(lo), + }), + }) +} + +fn scalar_int_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let ty = int_type(s)?; + Ok(ScalarType { + span: s.span(lo), + kind: ScalarTypeKind::Int(ty), + }) +} + +fn array_int_type(s: &mut ParserContext) -> Result { + let ty = int_type(s)?; + Ok(ArrayBaseTypeKind::Int(ty)) +} + +fn int_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Type(Type::Int))?; + let size = opt(s, designator)?; + Ok(IntType { + size, + span: s.span(lo), + }) +} +fn scalar_uint_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let ty = uint_type(s)?; + Ok(ScalarType { + span: s.span(lo), + kind: ScalarTypeKind::UInt(ty), + }) +} + +fn array_uint_type(s: &mut ParserContext) -> Result { + let ty = uint_type(s)?; + Ok(ArrayBaseTypeKind::UInt(ty)) +} + +fn uint_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Type(Type::UInt))?; + let size = opt(s, designator)?; + Ok(UIntType { + size, + span: s.span(lo), + }) +} + +fn scalar_float_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let ty = float_type(s)?; + Ok(ScalarType { + span: s.span(lo), + kind: ScalarTypeKind::Float(ty), + }) +} + +fn array_float_type(s: &mut ParserContext) -> Result { + let ty = float_type(s)?; + Ok(ArrayBaseTypeKind::Float(ty)) +} + +fn float_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Type(Type::Float))?; + let size = opt(s, designator)?; + Ok(FloatType { + span: s.span(lo), + size, + }) +} + +fn scalar_angle_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let ty = angle_type(s)?; + Ok(ScalarType { + span: s.span(lo), + kind: ScalarTypeKind::Angle(ty), + }) +} + +fn array_angle_type(s: &mut ParserContext) -> Result { + let ty = angle_type(s)?; + Ok(ArrayBaseTypeKind::Angle(ty)) +} + +fn angle_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Type(Type::Angle))?; + let size = opt(s, designator)?; + Ok(AngleType { + size, + span: s.span(lo), + }) +} + +fn scalar_bool_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Type(Type::Bool))?; + Ok(ScalarType { + span: s.span(lo), + kind: ScalarTypeKind::BoolType, + }) +} + +fn array_bool_type(s: &mut ParserContext) -> Result { + token(s, TokenKind::Type(Type::Bool))?; + Ok(ArrayBaseTypeKind::BoolType) +} + +fn scalar_duration_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Type(Type::Duration))?; + Ok(ScalarType { + span: s.span(lo), + kind: ScalarTypeKind::Duration, + }) +} + +fn array_duration_type(s: &mut ParserContext) -> Result { + token(s, TokenKind::Type(Type::Duration))?; + Ok(ArrayBaseTypeKind::Duration) +} + +fn scalar_stretch_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Type(Type::Stretch))?; + Ok(ScalarType { + span: s.span(lo), + kind: ScalarTypeKind::Stretch, + }) +} + +pub(super) fn scalar_complex_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + + let ty = complex_type(s)?; + Ok(ScalarType { + span: s.span(lo), + kind: ScalarTypeKind::Complex(ty), + }) +} + +pub(super) fn array_complex_type(s: &mut ParserContext) -> Result { + let ty = complex_type(s)?; + Ok(ArrayBaseTypeKind::Complex(ty)) +} + +pub(super) fn complex_type(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Type(Type::Complex))?; + + let subty = opt(s, complex_subtype)?; + Ok(ComplexType { + base_size: subty, + span: s.span(lo), + }) +} + +pub(super) fn complex_subtype(s: &mut ParserContext) -> Result { + token(s, TokenKind::Open(Delim::Bracket))?; + let ty = float_type(s)?; + token(s, TokenKind::Close(Delim::Bracket))?; + Ok(ty) +} + +/// The Language Spec and the grammar for switch statements disagree. +/// We followed the Spec when writing the parser. +/// Reference: . +pub fn parse_switch_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::Switch))?; + + // Controlling expression. + token(s, TokenKind::Open(Delim::Paren))?; + let controlling_expr = expr::expr(s)?; + recovering_token(s, TokenKind::Close(Delim::Paren)); + + // Open cases bracket. + token(s, TokenKind::Open(Delim::Brace))?; + + // Cases. + let lo_cases = s.peek().span.lo; + let cases = list_from_iter(many(s, case_stmt)?); + if cases.is_empty() { + s.push_error(Error::new(ErrorKind::MissingSwitchCases(s.span(lo_cases)))); + } + + // Default case. + let default = opt(s, default_case_stmt)?; + + // Close cases bracket. + recovering_token(s, TokenKind::Close(Delim::Brace)); + + Ok(SwitchStmt { + span: s.span(lo), + target: controlling_expr, + cases, + default, + }) +} + +/// Grammar: `CASE expressionList scope`. +fn case_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::Case))?; + + let controlling_label = expr::expr_list(s)?; + if controlling_label.is_empty() { + s.push_error(Error::new(ErrorKind::MissingSwitchCaseLabels(s.span(lo)))); + } + + let block = parse_block(s)?; + + Ok(SwitchCase { + span: s.span(lo), + labels: list_from_iter(controlling_label), + block, + }) +} + +/// Grammar: `DEFAULT scope`. +fn default_case_stmt(s: &mut ParserContext) -> Result { + token(s, TokenKind::Keyword(Keyword::Default))?; + parse_block(s) +} + +/// Grammar: `statement | scope`. +fn parse_block_or_stmt(s: &mut ParserContext) -> Result { + if let Some(block) = opt(s, parse_block)? { + Ok(Stmt { + span: block.span, + annotations: Default::default(), + kind: Box::new(StmtKind::Block(block)), + }) + } else { + parse(s) + } +} + +/// Grammar `IF LPAREN expression RPAREN if_body=statementOrScope (ELSE else_body=statementOrScope)?`. +/// Source: . +pub fn parse_if_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::If))?; + token(s, TokenKind::Open(Delim::Paren))?; + let condition = expr::expr(s)?; + recovering_token(s, TokenKind::Close(Delim::Paren)); + + let if_block = parse_block_or_stmt(s)?; + let else_block = if opt(s, |s| token(s, TokenKind::Keyword(Keyword::Else)))?.is_some() { + Some(parse_block_or_stmt(s)?) + } else { + None + }; + + Ok(IfStmt { + span: s.span(lo), + condition, + if_body: if_block, + else_body: else_block, + }) +} + +/// Ranges in for loops are a bit different. They must have explicit start and end. +/// Grammar `LBRACKET start=expression COLON (step=expression COLON)? stop=expression]`. +/// Reference: . +fn for_loop_range_expr(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Open(Delim::Bracket))?; + let start = Some(expr::expr(s)?); + token(s, TokenKind::Colon)?; + + // QASM ranges have the pattern [start : (step :)? end] + // We assume the second expr is the `end`. + let mut end = Some(expr::expr(s)?); + let mut step = None; + + // If we find a third expr, then the second expr was the `step`. + // and this third expr is the actual `end`. + if token(s, TokenKind::Colon).is_ok() { + step = end; + end = Some(expr::expr(s)?); + } + + recovering_token(s, TokenKind::Close(Delim::Bracket)); + + Ok(RangeDefinition { + span: s.span(lo), + start, + end, + step, + }) +} + +/// Parses the `(setExpression | LBRACKET rangeExpression RBRACKET | expression)` +/// part of a for loop statement. +/// Reference: . +fn for_loop_iterable_expr(s: &mut ParserContext) -> Result { + if let Some(range) = opt(s, for_loop_range_expr)? { + Ok(EnumerableSet::RangeDefinition(range)) + } else if let Some(set) = opt(s, expr::set_expr)? { + Ok(EnumerableSet::DiscreteSet(set)) + } else { + Ok(EnumerableSet::Expr(expr::expr(s)?)) + } +} + +/// Grammar: +/// `FOR scalarType Identifier IN (setExpression | LBRACKET rangeExpression RBRACKET | expression) body=statementOrScope`. +/// Reference: . +pub fn parse_for_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::For))?; + let ty = scalar_type(s)?; + let ident = prim::ident(s)?; + token(s, TokenKind::Keyword(Keyword::In))?; + let set_declaration = Box::new(for_loop_iterable_expr(s)?); + let block = parse_block_or_stmt(s)?; + + Ok(ForStmt { + span: s.span(lo), + ty, + ident, + set_declaration, + body: block, + }) +} + +/// Grammar: `WHILE LPAREN expression RPAREN body=statementOrScope`. +/// Reference: . +pub fn parse_while_loop(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::While))?; + token(s, TokenKind::Open(Delim::Paren))?; + let while_condition = expr::expr(s)?; + recovering_token(s, TokenKind::Close(Delim::Paren)); + let block = parse_block_or_stmt(s)?; + + Ok(WhileLoop { + span: s.span(lo), + while_condition, + body: block, + }) +} + +/// Grammar: `CONTINUE SEMICOLON`. +fn parse_continue_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::Continue))?; + recovering_semi(s); + Ok(ContinueStmt { span: s.span(lo) }) +} + +/// Grammar: `BREAK SEMICOLON`. +fn parse_break_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::Break))?; + recovering_semi(s); + Ok(BreakStmt { span: s.span(lo) }) +} + +/// Grammar: `END SEMICOLON`. +fn parse_end_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::End))?; + recovering_semi(s); + Ok(EndStmt { span: s.span(lo) }) +} + +/// Grammar: `expression SEMICOLON`. +fn parse_expression_stmt(s: &mut ParserContext, lhs: Option) -> Result { + let expr = if let Some(lhs) = lhs { + expr::expr_with_lhs(s, lhs)? + } else { + expr::expr(s)? + }; + recovering_semi(s); + Ok(ExprStmt { + span: s.span(expr.span.lo), + expr, + }) +} + +/// Grammar: `LET Identifier EQUALS aliasExpression SEMICOLON`. +fn parse_alias_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::Let))?; + let ident = Identifier::Ident(Box::new(prim::ident(s)?)); + token(s, TokenKind::Eq)?; + let exprs = expr::alias_expr(s)?; + recovering_semi(s); + + Ok(AliasDeclStmt { + ident, + exprs, + span: s.span(lo), + }) +} + +/// Grammar: `BOX designator? scope`. +fn parse_box(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::Box))?; + let duration = opt(s, designator)?; + let body = parse_box_body(s)?; + + Ok(BoxStmt { + span: s.span(lo), + duration, + body, + }) +} + +fn parse_box_body(s: &mut ParserContext) -> Result> { + token(s, TokenKind::Open(Delim::Brace))?; + let stmts = barrier( + s, + &[TokenKind::Close(Delim::Brace)], + parse_many_boxable_stmt, + )?; + recovering_token(s, TokenKind::Close(Delim::Brace)); + Ok(stmts) +} + +fn parse_many_boxable_stmt(s: &mut ParserContext) -> Result> { + let stmts = many(s, |s| { + recovering( + s, + |span| Stmt { + span, + kind: Box::new(StmtKind::Err), + annotations: Box::new([]), + }, + &[TokenKind::Semicolon], + parse, + ) + }); + + Ok(list_from_iter(stmts?)) +} + +/// In QASM3, it is hard to disambiguate between a quantum-gate-call missing modifiers +/// and expression statements. Consider the following expressions: +/// 1. `Ident(2, 3) a;` +/// 2. `Ident(2, 3) + a * b;` +/// 3. `Ident(2, 3);` +/// 4. `Ident(2, 3)[1];` +/// 5. `Ident;` +/// 6. `Ident[4us] q;` +/// 7. `Ident[4];` +/// 8. `Ident q;` +/// +/// (1) is a quantum-gate-call, (2) is a binary operation, (3) is a function call, and +/// (4) is an identifer. We don't know for sure until we see the what is beyond the gate +/// name and its potential classical parameters. +/// +/// Therefore, we parse the gate name and its potential parameters using the expr parser. +/// If the expr is a function call or an identifier and it is followed by qubit arguments, +/// we reinterpret the expression as a quantum gate. +/// +/// Grammar: +/// `gateModifier* Identifier (LPAREN expressionList? RPAREN)? designator? gateOperandList SEMICOLON +/// | gateModifier* GPHASE (LPAREN expressionList? RPAREN)? designator? gateOperandList? SEMICOLON`. +fn parse_gate_call_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let modifiers = list_from_iter(many(s, gate_modifier)?); + + // If the next token is `gphase`, parse a gphase instead, which has optional operands. + if s.peek().kind == TokenKind::GPhase { + let gphase = parse_gphase(s, lo, modifiers)?; + return Ok(StmtKind::GPhase(gphase)); + } + + // 1. ident = ... + // 2. parameters? = ... Option + // 3. designator? = ... + // 4. qubits = ... -> qubits.is_empty() + + // cases: (no qubits) + // ident + parameters -> function call + // ident + designator -> indexed ident + + // As explained in the docstring, we parse the gate using the expr parser. + let gate_or_expr = expr::expr(s)?; + + let mut duration = opt(s, designator)?; + let qubits = gate_operand_list(s)?; + + // If didn't parse modifiers, a duration, nor qubit args then this is an expr, not a gate call. + if modifiers.is_empty() && duration.is_none() && qubits.is_empty() { + return Ok(StmtKind::ExprStmt(parse_expression_stmt( + s, + Some(gate_or_expr), + )?)); + } + + // We parse the recovering semi after we call parse_expr_stmt. + recovering_semi(s); + + // Reinterpret the function call or ident as a gate call. + let (name, args) = match *gate_or_expr.kind { + ExprKind::FunctionCall(FunctionCall { name, args, .. }) => (name, args), + ExprKind::Ident(ident) => (ident, Default::default()), + ExprKind::IndexExpr(index_expr) => reinterpret_index_expr(index_expr, &mut duration)?, + _ => { + return Err(Error::new(ErrorKind::ExpectedItem( + TokenKind::Identifier, + gate_or_expr.span, + ))) + } + }; + + if qubits.is_empty() { + s.push_error(Error::new(ErrorKind::MissingGateCallOperands(s.span(lo)))); + } + + Ok(StmtKind::GateCall(GateCall { + span: s.span(lo), + modifiers, + name, + args, + qubits, + duration, + })) +} + +/// This parser is used to disambiguate statements starting with an index +/// identifier. It is a simplified version of `parse_gate_call_stmt`. +fn parse_gate_call_with_expr(s: &mut ParserContext, gate_or_expr: Expr) -> Result { + let lo = gate_or_expr.span.lo; + let mut duration = opt(s, designator)?; + let qubits = gate_operand_list(s)?; + + // If didn't parse modifiers, a duration, nor qubit args then this is an expr, not a gate call. + if duration.is_none() && qubits.is_empty() { + return Ok(StmtKind::ExprStmt(parse_expression_stmt( + s, + Some(gate_or_expr), + )?)); + } + + // We parse the recovering semi after we call parse_expr_stmt. + recovering_semi(s); + + // Reinterpret the function call or ident as a gate call. + let (name, args) = match *gate_or_expr.kind { + ExprKind::FunctionCall(FunctionCall { name, args, .. }) => (name, args), + ExprKind::Ident(ident) => (ident, Default::default()), + ExprKind::IndexExpr(index_expr) => reinterpret_index_expr(index_expr, &mut duration)?, + _ => { + return Err(Error::new(ErrorKind::ExpectedItem( + TokenKind::Identifier, + gate_or_expr.span, + ))) + } + }; + + if qubits.is_empty() { + s.push_error(Error::new(ErrorKind::MissingGateCallOperands(s.span(lo)))); + } + + Ok(StmtKind::GateCall(GateCall { + span: s.span(lo), + modifiers: Default::default(), + name, + args, + qubits, + duration, + })) +} + +/// This helper function reinterprets an indexed expression as +/// a gate call. There are two valid cases we are interested in: +/// 1. Ident[4] +/// 2. Ident(2, 3)[4] +/// +/// Case (1) is an indexed identifier, in which case we want to +/// reinterpret it as a gate followed by a designator. +/// Case (2) is an indexed function call, in which case we want to +/// reinterpret it as a parametrized gate followed by a designator. +fn reinterpret_index_expr( + index_expr: IndexExpr, + duration: &mut Option, +) -> Result<(Ident, List)> { + let IndexExpr { + collection, index, .. + } = index_expr; + + if let IndexElement::IndexSet(set) = index { + if set.values.len() == 1 { + let first_elt: IndexSetItem = (*set.values[0]).clone(); + if let IndexSetItem::Expr(expr) = first_elt { + if duration.is_none() { + match *collection.kind { + ExprKind::Ident(name) => { + *duration = Some(expr); + return Ok((name, Default::default())); + } + ExprKind::FunctionCall(FunctionCall { name, args, .. }) => { + *duration = Some(expr); + return Ok((name, args)); + } + _ => (), + } + } + } + } + } + + Err(Error::new(ErrorKind::InvalidGateCallDesignator( + index_expr.span, + ))) +} + +/// Grammar: +/// `gateModifier* GPHASE (LPAREN expressionList? RPAREN)? designator? gateOperandList? SEMICOLON` +fn parse_gphase( + s: &mut ParserContext, + lo: u32, + modifiers: List, +) -> Result { + let gphase_token_lo = s.peek().span.lo; + token(s, TokenKind::GPhase)?; + let gphase_token_span = s.span(gphase_token_lo); + + let args_lo = s.peek().span.lo; + let args = opt(s, |s| { + token(s, TokenKind::Open(Delim::Paren))?; + let exprs = expr::expr_list(s)?; + recovering_token(s, TokenKind::Close(Delim::Paren)); + Ok(list_from_iter(exprs)) + })? + .unwrap_or_default(); + + if args.len() != 1 { + s.push_error(Error::new(ErrorKind::GPhaseInvalidArguments( + s.span(args_lo), + ))); + } + + let duration = opt(s, designator)?; + let qubits = gate_operand_list(s)?; + recovering_semi(s); + + Ok(GPhase { + span: s.span(lo), + gphase_token_span, + modifiers, + args, + qubits, + duration, + }) +} + +/// Grammar: +/// `( +/// INV +/// | POW LPAREN expression RPAREN +/// | (CTRL | NEGCTRL) (LPAREN expression RPAREN)? +/// ) AT`. +fn gate_modifier(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let modifier_keyword_span; + + let kind = if opt(s, |s| token(s, TokenKind::Keyword(Keyword::Inv)))?.is_some() { + modifier_keyword_span = s.span(lo); + GateModifierKind::Inv + } else if opt(s, |s| token(s, TokenKind::Keyword(Keyword::Pow)))?.is_some() { + modifier_keyword_span = s.span(lo); + token(s, TokenKind::Open(Delim::Paren))?; + let expr = expr::expr(s)?; + recovering_token(s, TokenKind::Close(Delim::Paren)); + GateModifierKind::Pow(expr) + } else if opt(s, |s| token(s, TokenKind::Keyword(Keyword::Ctrl)))?.is_some() { + modifier_keyword_span = s.span(lo); + let expr = opt(s, |s| { + token(s, TokenKind::Open(Delim::Paren))?; + let expr = expr::expr(s)?; + recovering_token(s, TokenKind::Close(Delim::Paren)); + Ok(expr) + })?; + GateModifierKind::Ctrl(expr) + } else { + token(s, TokenKind::Keyword(Keyword::NegCtrl))?; + modifier_keyword_span = s.span(lo); + let expr = opt(s, |s| { + token(s, TokenKind::Open(Delim::Paren))?; + let expr = expr::expr(s)?; + recovering_token(s, TokenKind::Close(Delim::Paren)); + Ok(expr) + })?; + GateModifierKind::NegCtrl(expr) + }; + + recovering_token(s, TokenKind::At); + + Ok(QuantumGateModifier { + span: s.span(lo), + modifier_keyword_span, + kind, + }) +} + +/// Grammar: `gateOperand (COMMA gateOperand)* COMMA?`. +fn gate_operand_list(s: &mut ParserContext) -> Result> { + Ok(list_from_iter(seq(s, gate_operand)?.0)) +} + +/// Grammar: `DEFCALGRAMMAR StringLiteral SEMICOLON`. +fn parse_calibration_grammar_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::DefCalGrammar))?; + + let next = s.peek(); + let lit = expr::lit(s)?; + + recovering_semi(s); + if let Some(lit) = lit { + if let LiteralKind::String(name) = lit.kind { + return Ok(CalibrationGrammarStmt { + span: s.span(lo), + name: name.to_string(), + }); + } + } + + Err(Error::new(ErrorKind::Rule( + "string literal", + next.kind, + next.span, + ))) +} + +/// We don't support `defcal` block statements in the compiler. Therefore +/// the parser just goes through the tokens in a defcal block and ignores them. +/// Grammar: `DEFCAL pushmode(eatUntilOpenBrace) pushmode(eatUntilBalancedClosingBrace)`. +fn parse_defcal_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::DefCal))?; + + // Once we have parsed the `defcal` token, we eat all the tokens until we see an open brace. + while !matches!( + s.peek().kind, + TokenKind::Open(Delim::Brace) | TokenKind::Eof + ) { + s.advance(); + } + + token(s, TokenKind::Open(Delim::Brace))?; + let mut level: u32 = 1; + + loop { + match s.peek().kind { + TokenKind::Eof => { + s.advance(); + return Err(Error::new(ErrorKind::Token( + TokenKind::Close(Delim::Brace), + TokenKind::Eof, + s.span(lo), + ))); + } + TokenKind::Open(Delim::Brace) => { + s.advance(); + level += 1; + } + TokenKind::Close(Delim::Brace) => { + s.advance(); + level -= 1; + if level == 0 { + return Ok(DefCalStmt { span: s.span(lo) }); + } + } + _ => s.advance(), + } + } +} + +/// We don't support `cal` block statements in the compiler. Therefore +/// the parser just goes through the tokens in a cal block and ignores them. +/// Grammar: `CAL OPEN_BRACE pushmode(eatUntilBalancedClosingBrace)`. +fn parse_cal(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::Cal))?; + token(s, TokenKind::Open(Delim::Brace))?; + let mut level: u32 = 1; + + loop { + match s.peek().kind { + TokenKind::Eof => { + s.advance(); + return Err(Error::new(ErrorKind::Token( + TokenKind::Close(Delim::Brace), + TokenKind::Eof, + s.span(lo), + ))); + } + TokenKind::Open(Delim::Brace) => { + s.advance(); + level += 1; + } + TokenKind::Close(Delim::Brace) => { + s.advance(); + level -= 1; + if level == 0 { + return Ok(CalibrationStmt { span: s.span(lo) }); + } + } + _ => s.advance(), + } + } +} + +/// Grammar: `BARRIER gateOperandList? SEMICOLON`. +fn parse_barrier(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::Barrier))?; + let qubits = gate_operand_list(s)?; + recovering_semi(s); + + Ok(BarrierStmt { + span: s.span(lo), + qubits, + }) +} + +/// Grammar: `DELAY designator gateOperandList? SEMICOLON`. +fn parse_delay(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::Delay))?; + let duration = designator(s)?; + let qubits = gate_operand_list(s)?; + recovering_semi(s); + + Ok(DelayStmt { + span: s.span(lo), + duration, + qubits, + }) +} + +/// Grammar: `RESET gateOperand SEMICOLON`. +fn parse_reset(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::Reset))?; + let reset_token_span = s.span(lo); + let operand = Box::new(gate_operand(s)?); + recovering_semi(s); + + Ok(ResetStmt { + span: s.span(lo), + reset_token_span, + operand, + }) +} + +/// Grammar: `measureExpression (ARROW indexedIdentifier)? SEMICOLON`. +fn parse_measure_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let measure = expr::measure_expr(s)?; + + let target = opt(s, |s| { + token(s, TokenKind::Arrow)?; + Ok(Box::new(indexed_identifier(s)?)) + })?; + + recovering_semi(s); + + Ok(MeasureArrowStmt { + span: s.span(lo), + measurement: measure, + target, + }) +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests.rs b/compiler/qsc_qasm/src/parser/stmt/tests.rs new file mode 100644 index 0000000000..1db174f83a --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests.rs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +mod alias; +mod annotation; +mod barrier; +mod block; +mod box_stmt; +mod cal; +mod cal_grammar; +mod classical_decl; +mod def; +mod defcal; +mod delay; +mod expr_stmt; +mod extern_decl; +mod for_loops; +mod gate_call; +mod gate_def; +mod gphase; +mod if_stmt; +mod include; +mod invalid_stmts; +mod io_decl; +mod measure; +mod old_style_decl; +mod pragma; +mod quantum_decl; +mod reset; +mod switch_stmt; +mod while_loops; diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/alias.rs b/compiler/qsc_qasm/src/parser/stmt/tests/alias.rs new file mode 100644 index 0000000000..3c6d47ec8a --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/alias.rs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn simple_alias() { + check( + parse, + "let x = a;", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: AliasDeclStmt [0-10]: + ident: Ident [4-5] "x" + exprs: + Expr [8-9]: Ident [8-9] "a""#]], + ); +} + +#[test] +fn concatenation_alias() { + check( + parse, + "let x = a[1:2] ++ b ++ c[1:2:3];", + &expect![[r#" + Stmt [0-32]: + annotations: + kind: AliasDeclStmt [0-32]: + ident: Ident [4-5] "x" + exprs: + Expr [8-14]: IndexExpr [8-14]: + collection: Expr [8-9]: Ident [8-9] "a" + index: IndexSet [10-13]: + values: + RangeDefinition [10-13]: + start: Expr [10-11]: Lit: Int(1) + step: + end: Expr [12-13]: Lit: Int(2) + Expr [18-19]: Ident [18-19] "b" + Expr [23-31]: IndexExpr [23-31]: + collection: Expr [23-24]: Ident [23-24] "c" + index: IndexSet [25-30]: + values: + RangeDefinition [25-30]: + start: Expr [25-26]: Lit: Int(1) + step: Expr [27-28]: Lit: Int(2) + end: Expr [29-30]: Lit: Int(3)"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/annotation.rs b/compiler/qsc_qasm/src/parser/stmt/tests/annotation.rs new file mode 100644 index 0000000000..a37d4e11ff --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/annotation.rs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse_annotation; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn annotation() { + check( + parse_annotation, + "@a.b.d 23", + &expect![[r#" + Annotation [0-9]: + identifier: "a.b.d" + value: "23""#]], + ); +} + +#[test] +fn annotation_ident_only() { + check( + parse_annotation, + "@a.b.d", + &expect![[r#" + Annotation [0-6]: + identifier: "a.b.d" + value: "#]], + ); +} + +#[test] +fn annotation_missing_ident() { + check( + parse_annotation, + "@", + &expect![[r#" + Annotation [0-1]: + identifier: "" + value: + + [ + Error( + Rule( + "annotation missing identifier", + Annotation, + Span { + lo: 0, + hi: 1, + }, + ), + ), + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/barrier.rs b/compiler/qsc_qasm/src/parser/stmt/tests/barrier.rs new file mode 100644 index 0000000000..2a37910565 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/barrier.rs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn barrier() { + check( + parse, + "barrier r, q[0], $2;", + &expect![[r#" + Stmt [0-20]: + annotations: + kind: BarrierStmt [0-20]: + operands: + GateOperand [8-9]: + kind: IndexedIdent [8-9]: + name: Ident [8-9] "r" + index_span: [0-0] + indices: + GateOperand [11-15]: + kind: IndexedIdent [11-15]: + name: Ident [11-12] "q" + index_span: [12-15] + indices: + IndexSet [13-14]: + values: + Expr [13-14]: Lit: Int(0) + GateOperand [17-19]: + kind: HardwareQubit [17-19]: 2"#]], + ); +} + +#[test] +fn barrier_no_args() { + check( + parse, + "barrier;", + &expect![[r#" + Stmt [0-8]: + annotations: + kind: BarrierStmt [0-8]: + operands: "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/block.rs b/compiler/qsc_qasm/src/parser/stmt/tests/block.rs new file mode 100644 index 0000000000..06773eacd5 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/block.rs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse_block; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn nested_blocks() { + check( + parse_block, + " + { + { + int x = 1; + { + x = 2; + } + } + }", + &expect![[r#" + Block [5-106]: + Stmt [15-100]: + annotations: + kind: Block [15-100]: + Stmt [29-39]: + annotations: + kind: ClassicalDeclarationStmt [29-39]: + type: ScalarType [29-32]: IntType [29-32]: + size: + ident: Ident [33-34] "x" + init_expr: Expr [37-38]: Lit: Int(1) + Stmt [52-90]: + annotations: + kind: Block [52-90]: + Stmt [70-76]: + annotations: + kind: AssignStmt [70-76]: + lhs: IndexedIdent [70-71]: + name: Ident [70-71] "x" + index_span: [0-0] + indices: + rhs: Expr [74-75]: Lit: Int(2)"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/box_stmt.rs b/compiler/qsc_qasm/src/parser/stmt/tests/box_stmt.rs new file mode 100644 index 0000000000..5695656f8b --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/box_stmt.rs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn box_stmt() { + check( + parse, + " + box { + H q0; + Rx(2.4) q1; + }", + &expect![[r#" + Stmt [5-50]: + annotations: + kind: BoxStmt [5-50]: + duration: + body: + Stmt [19-24]: + annotations: + kind: GateCall [19-24]: + modifiers: + name: Ident [19-20] "H" + args: + duration: + qubits: + GateOperand [21-23]: + kind: IndexedIdent [21-23]: + name: Ident [21-23] "q0" + index_span: [0-0] + indices: + Stmt [33-44]: + annotations: + kind: GateCall [33-44]: + modifiers: + name: Ident [33-35] "Rx" + args: + Expr [36-39]: Lit: Float(2.4) + duration: + qubits: + GateOperand [41-43]: + kind: IndexedIdent [41-43]: + name: Ident [41-43] "q1" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn box_stmt_with_designator() { + check( + parse, + " + box[4us] { + H q0; + Rx(2.4) q1; + }", + &expect![[r#" + Stmt [5-55]: + annotations: + kind: BoxStmt [5-55]: + duration: Expr [9-12]: Lit: Duration(4.0, Us) + body: + Stmt [24-29]: + annotations: + kind: GateCall [24-29]: + modifiers: + name: Ident [24-25] "H" + args: + duration: + qubits: + GateOperand [26-28]: + kind: IndexedIdent [26-28]: + name: Ident [26-28] "q0" + index_span: [0-0] + indices: + Stmt [38-49]: + annotations: + kind: GateCall [38-49]: + modifiers: + name: Ident [38-40] "Rx" + args: + Expr [41-44]: Lit: Float(2.4) + duration: + qubits: + GateOperand [46-48]: + kind: IndexedIdent [46-48]: + name: Ident [46-48] "q1" + index_span: [0-0] + indices: "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/cal.rs b/compiler/qsc_qasm/src/parser/stmt/tests/cal.rs new file mode 100644 index 0000000000..13633b5175 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/cal.rs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn cal_block_with_unbalanced_braces_errors() { + check( + parse, + "cal { { }", + &expect![[r#" + Error( + Token( + Close( + Brace, + ), + Eof, + Span { + lo: 0, + hi: 9, + }, + ), + ) + "#]], + ); +} + +#[test] +fn cal_block_accept_any_tokens_inside() { + check( + parse, + " + cal { + faoi foaijdf a; + fkfm )( + .314 + }", + &expect![[r#" + Stmt [5-69]: + annotations: + kind: CalibrationStmt [5-69]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/cal_grammar.rs b/compiler/qsc_qasm/src/parser/stmt/tests/cal_grammar.rs new file mode 100644 index 0000000000..da777fc9fa --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/cal_grammar.rs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn defcalgrammar() { + check( + parse, + r#"defcalgrammar "openpulse";"#, + &expect![[r#" + Stmt [0-26]: + annotations: + kind: CalibrationGrammarStmt [0-26]: + name: openpulse"#]], + ); +} + +#[test] +fn defcalgrammar_with_non_string_literal() { + check( + parse, + r#"defcalgrammar 5;"#, + &expect![[r#" + Error( + Rule( + "string literal", + Literal( + Integer( + Decimal, + ), + ), + Span { + lo: 14, + hi: 15, + }, + ), + ) + "#]], + ); +} + +#[test] +fn defcalgrammar_with_no_literal() { + check( + parse, + r#"defcalgrammar;"#, + &expect![[r#" + Error( + Rule( + "string literal", + Semicolon, + Span { + lo: 13, + hi: 14, + }, + ), + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/classical_decl.rs b/compiler/qsc_qasm/src/parser/stmt/tests/classical_decl.rs new file mode 100644 index 0000000000..dcf6e9fc40 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/classical_decl.rs @@ -0,0 +1,1129 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::parser::tests::check; + +use crate::parser::stmt::parse; + +#[test] +fn bit_decl() { + check( + parse, + "bit b;", + &expect![[r#" + Stmt [0-6]: + annotations: + kind: ClassicalDeclarationStmt [0-6]: + type: ScalarType [0-3]: BitType [0-3]: + size: + ident: Ident [4-5] "b" + init_expr: "#]], + ); +} + +#[test] +fn bit_decl_bit_lit() { + check( + parse, + "bit b = 1;", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: ClassicalDeclarationStmt [0-10]: + type: ScalarType [0-3]: BitType [0-3]: + size: + ident: Ident [4-5] "b" + init_expr: Expr [8-9]: Lit: Int(1)"#]], + ); +} + +#[test] +fn const_bit_decl_bit_lit() { + check( + parse, + "const bit b = 1;", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: ConstantDeclStmt [0-16]: + type: ScalarType [6-9]: BitType [6-9]: + size: + ident: Ident [10-11] "b" + init_expr: Expr [14-15]: Lit: Int(1)"#]], + ); +} + +#[test] +fn bit_array_decl() { + check( + parse, + "bit[2] b;", + &expect![[r#" + Stmt [0-9]: + annotations: + kind: ClassicalDeclarationStmt [0-9]: + type: ScalarType [0-6]: BitType [0-6]: + size: Expr [4-5]: Lit: Int(2) + ident: Ident [7-8] "b" + init_expr: "#]], + ); +} + +#[test] +fn bit_array_decl_bit_lit() { + check( + parse, + "bit[2] b = \"11\";", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: ClassicalDeclarationStmt [0-16]: + type: ScalarType [0-6]: BitType [0-6]: + size: Expr [4-5]: Lit: Int(2) + ident: Ident [7-8] "b" + init_expr: Expr [11-15]: Lit: Bitstring("11")"#]], + ); +} + +#[test] +fn const_bit_array_decl_bit_lit() { + check( + parse, + "const bit[2] b = \"11\";", + &expect![[r#" + Stmt [0-22]: + annotations: + kind: ConstantDeclStmt [0-22]: + type: ScalarType [6-12]: BitType [6-12]: + size: Expr [10-11]: Lit: Int(2) + ident: Ident [13-14] "b" + init_expr: Expr [17-21]: Lit: Bitstring("11")"#]], + ); +} + +#[test] +fn bool_decl() { + check( + parse, + "bool b;", + &expect![[r#" + Stmt [0-7]: + annotations: + kind: ClassicalDeclarationStmt [0-7]: + type: ScalarType [0-4]: BoolType + ident: Ident [5-6] "b" + init_expr: "#]], + ); +} + +#[test] +fn bool_decl_int_lit() { + check( + parse, + "bool b = 1;", + &expect![[r#" + Stmt [0-11]: + annotations: + kind: ClassicalDeclarationStmt [0-11]: + type: ScalarType [0-4]: BoolType + ident: Ident [5-6] "b" + init_expr: Expr [9-10]: Lit: Int(1)"#]], + ); +} + +#[test] +fn const_bool_decl_bool_lit() { + check( + parse, + "const bool b = true;", + &expect![[r#" + Stmt [0-20]: + annotations: + kind: ConstantDeclStmt [0-20]: + type: ScalarType [6-10]: BoolType + ident: Ident [11-12] "b" + init_expr: Expr [15-19]: Lit: Bool(true)"#]], + ); +} + +#[test] +fn complex_decl() { + check( + parse, + "complex c;", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: ClassicalDeclarationStmt [0-10]: + type: ScalarType [0-7]: ComplexType [0-7]: + base_size: + ident: Ident [8-9] "c" + init_expr: "#]], + ); +} + +#[test] +fn complex_decl_complex_lit() { + check( + parse, + "complex c = 1im;", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: ClassicalDeclarationStmt [0-16]: + type: ScalarType [0-7]: ComplexType [0-7]: + base_size: + ident: Ident [8-9] "c" + init_expr: Expr [12-15]: Lit: Imaginary(1.0)"#]], + ); +} + +#[test] +fn const_complex_decl_complex_lit() { + check( + parse, + "const complex c = 1im;", + &expect![[r#" + Stmt [0-22]: + annotations: + kind: ConstantDeclStmt [0-22]: + type: ScalarType [6-13]: ComplexType [6-13]: + base_size: + ident: Ident [14-15] "c" + init_expr: Expr [18-21]: Lit: Imaginary(1.0)"#]], + ); +} + +#[test] +fn const_complex_decl_complex_binary_lit() { + check( + parse, + "const complex c = 23.5 + 1.7im;", + &expect![[r#" + Stmt [0-31]: + annotations: + kind: ConstantDeclStmt [0-31]: + type: ScalarType [6-13]: ComplexType [6-13]: + base_size: + ident: Ident [14-15] "c" + init_expr: Expr [18-30]: BinaryOpExpr: + op: Add + lhs: Expr [18-22]: Lit: Float(23.5) + rhs: Expr [25-30]: Lit: Imaginary(1.7)"#]], + ); +} + +#[test] +fn complex_sized_decl() { + check( + parse, + "complex[float[32]] c;", + &expect![[r#" + Stmt [0-21]: + annotations: + kind: ClassicalDeclarationStmt [0-21]: + type: ScalarType [0-18]: ComplexType [0-18]: + base_size: FloatType [8-17]: + size: Expr [14-16]: Lit: Int(32) + ident: Ident [19-20] "c" + init_expr: "#]], + ); +} + +#[test] +fn complex_sized_non_float_subty_decl() { + check( + parse, + "complex[int[32]] c;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Type( + Int, + ), + Span { + lo: 8, + hi: 11, + }, + ), + ) + "#]], + ); +} + +#[test] +fn complex_sized_decl_complex_lit() { + check( + parse, + "complex[float[32]] c = 1im;", + &expect![[r#" + Stmt [0-27]: + annotations: + kind: ClassicalDeclarationStmt [0-27]: + type: ScalarType [0-18]: ComplexType [0-18]: + base_size: FloatType [8-17]: + size: Expr [14-16]: Lit: Int(32) + ident: Ident [19-20] "c" + init_expr: Expr [23-26]: Lit: Imaginary(1.0)"#]], + ); +} + +#[test] +fn const_complex_sized_decl_complex_lit() { + check( + parse, + "const complex[float[32]] c = 1im;", + &expect![[r#" + Stmt [0-33]: + annotations: + kind: ConstantDeclStmt [0-33]: + type: ScalarType [6-24]: ComplexType [6-24]: + base_size: FloatType [14-23]: + size: Expr [20-22]: Lit: Int(32) + ident: Ident [25-26] "c" + init_expr: Expr [29-32]: Lit: Imaginary(1.0)"#]], + ); +} + +#[test] +fn const_complex_implicit_bitness_default() { + check( + parse, + "const complex[float] x;", + &expect![[r#" + Error( + Token( + Eq, + Semicolon, + Span { + lo: 22, + hi: 23, + }, + ), + ) + "#]], + ); +} + +#[test] +fn const_complex_explicit_bitness_default() { + check( + parse, + "const complex[float[42]] x;", + &expect![[r#" + Error( + Token( + Eq, + Semicolon, + Span { + lo: 26, + hi: 27, + }, + ), + ) + "#]], + ); +} + +#[test] +fn int_decl() { + check( + parse, + "int i;", + &expect![[r#" + Stmt [0-6]: + annotations: + kind: ClassicalDeclarationStmt [0-6]: + type: ScalarType [0-3]: IntType [0-3]: + size: + ident: Ident [4-5] "i" + init_expr: "#]], + ); +} + +#[test] +fn int_decl_int_lit() { + check( + parse, + "int i = 1;", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: ClassicalDeclarationStmt [0-10]: + type: ScalarType [0-3]: IntType [0-3]: + size: + ident: Ident [4-5] "i" + init_expr: Expr [8-9]: Lit: Int(1)"#]], + ); +} + +#[test] +fn const_int_explicit_bitness_int_default() { + check( + parse, + "const int[10] x;", + &expect![[r#" + Error( + Token( + Eq, + Semicolon, + Span { + lo: 15, + hi: 16, + }, + ), + ) + "#]], + ); +} + +#[test] +fn const_int_implicit_bitness_int_default() { + check( + parse, + "const int x;", + &expect![[r#" + Error( + Token( + Eq, + Semicolon, + Span { + lo: 11, + hi: 12, + }, + ), + ) + "#]], + ); +} + +#[test] +fn const_int_decl_int_lit() { + check( + parse, + "const int i = 1;", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: ConstantDeclStmt [0-16]: + type: ScalarType [6-9]: IntType [6-9]: + size: + ident: Ident [10-11] "i" + init_expr: Expr [14-15]: Lit: Int(1)"#]], + ); +} + +#[test] +fn int_sized_decl() { + check( + parse, + "int[32] i;", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: ClassicalDeclarationStmt [0-10]: + type: ScalarType [0-7]: IntType [0-7]: + size: Expr [4-6]: Lit: Int(32) + ident: Ident [8-9] "i" + init_expr: "#]], + ); +} + +#[test] +fn int_sized_decl_int_lit() { + check( + parse, + "int[32] i = 1;", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: ClassicalDeclarationStmt [0-14]: + type: ScalarType [0-7]: IntType [0-7]: + size: Expr [4-6]: Lit: Int(32) + ident: Ident [8-9] "i" + init_expr: Expr [12-13]: Lit: Int(1)"#]], + ); +} + +#[test] +fn const_int_sized_decl_int_lit() { + check( + parse, + "const int[32] i = 1;", + &expect![[r#" + Stmt [0-20]: + annotations: + kind: ConstantDeclStmt [0-20]: + type: ScalarType [6-13]: IntType [6-13]: + size: Expr [10-12]: Lit: Int(32) + ident: Ident [14-15] "i" + init_expr: Expr [18-19]: Lit: Int(1)"#]], + ); +} + +#[test] +fn uint_decl() { + check( + parse, + "uint i;", + &expect![[r#" + Stmt [0-7]: + annotations: + kind: ClassicalDeclarationStmt [0-7]: + type: ScalarType [0-4]: UIntType [0-4]: + size: + ident: Ident [5-6] "i" + init_expr: "#]], + ); +} + +#[test] +fn uint_decl_uint_lit() { + check( + parse, + "uint i = 1;", + &expect![[r#" + Stmt [0-11]: + annotations: + kind: ClassicalDeclarationStmt [0-11]: + type: ScalarType [0-4]: UIntType [0-4]: + size: + ident: Ident [5-6] "i" + init_expr: Expr [9-10]: Lit: Int(1)"#]], + ); +} + +#[test] +fn const_uint_explicit_bitness_uint_default() { + check( + parse, + "const uint[10] x;", + &expect![[r#" + Error( + Token( + Eq, + Semicolon, + Span { + lo: 16, + hi: 17, + }, + ), + ) + "#]], + ); +} + +#[test] +fn const_uint_implicit_bitness_uint_default() { + check( + parse, + "const uint x;", + &expect![[r#" + Error( + Token( + Eq, + Semicolon, + Span { + lo: 12, + hi: 13, + }, + ), + ) + "#]], + ); +} + +#[test] +fn const_uint_decl_uint_lit() { + check( + parse, + "const uint i = 1;", + &expect![[r#" + Stmt [0-17]: + annotations: + kind: ConstantDeclStmt [0-17]: + type: ScalarType [6-10]: UIntType [6-10]: + size: + ident: Ident [11-12] "i" + init_expr: Expr [15-16]: Lit: Int(1)"#]], + ); +} + +#[test] +fn uint_sized_decl() { + check( + parse, + "uint[32] i;", + &expect![[r#" + Stmt [0-11]: + annotations: + kind: ClassicalDeclarationStmt [0-11]: + type: ScalarType [0-8]: UIntType [0-8]: + size: Expr [5-7]: Lit: Int(32) + ident: Ident [9-10] "i" + init_expr: "#]], + ); +} + +#[test] +fn uint_sized_decl_uint_lit() { + check( + parse, + "uint[32] i = 1;", + &expect![[r#" + Stmt [0-15]: + annotations: + kind: ClassicalDeclarationStmt [0-15]: + type: ScalarType [0-8]: UIntType [0-8]: + size: Expr [5-7]: Lit: Int(32) + ident: Ident [9-10] "i" + init_expr: Expr [13-14]: Lit: Int(1)"#]], + ); +} + +#[test] +fn const_uint_sized_decl_uint_lit() { + check( + parse, + "const uint[32] i = 1;", + &expect![[r#" + Stmt [0-21]: + annotations: + kind: ConstantDeclStmt [0-21]: + type: ScalarType [6-14]: UIntType [6-14]: + size: Expr [11-13]: Lit: Int(32) + ident: Ident [15-16] "i" + init_expr: Expr [19-20]: Lit: Int(1)"#]], + ); +} + +#[test] +fn float_decl() { + check( + parse, + "float f;", + &expect![[r#" + Stmt [0-8]: + annotations: + kind: ClassicalDeclarationStmt [0-8]: + type: ScalarType [0-5]: FloatType [0-5]: + size: + ident: Ident [6-7] "f" + init_expr: "#]], + ); +} + +#[test] +fn float_decl_float_lit() { + check( + parse, + "float f = 1;", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: ClassicalDeclarationStmt [0-12]: + type: ScalarType [0-5]: FloatType [0-5]: + size: + ident: Ident [6-7] "f" + init_expr: Expr [10-11]: Lit: Int(1)"#]], + ); +} + +#[test] +fn const_float_decl_float_lit() { + check( + parse, + "const float f = 1.0;", + &expect![[r#" + Stmt [0-20]: + annotations: + kind: ConstantDeclStmt [0-20]: + type: ScalarType [6-11]: FloatType [6-11]: + size: + ident: Ident [12-13] "f" + init_expr: Expr [16-19]: Lit: Float(1.0)"#]], + ); +} + +#[test] +fn const_float_default() { + check( + parse, + "const float x;", + &expect![[r#" + Error( + Token( + Eq, + Semicolon, + Span { + lo: 13, + hi: 14, + }, + ), + ) + "#]], + ); +} + +#[test] +fn const_float_sized_default() { + check( + parse, + "const float[64] x;", + &expect![[r#" + Error( + Token( + Eq, + Semicolon, + Span { + lo: 17, + hi: 18, + }, + ), + ) + "#]], + ); +} + +#[test] +fn float_sized_decl() { + check( + parse, + "float[32] f;", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: ClassicalDeclarationStmt [0-12]: + type: ScalarType [0-9]: FloatType [0-9]: + size: Expr [6-8]: Lit: Int(32) + ident: Ident [10-11] "f" + init_expr: "#]], + ); +} + +#[test] +fn float_sized_decl_float_lit() { + check( + parse, + "float[32] f = 1.0;", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: ClassicalDeclarationStmt [0-18]: + type: ScalarType [0-9]: FloatType [0-9]: + size: Expr [6-8]: Lit: Int(32) + ident: Ident [10-11] "f" + init_expr: Expr [14-17]: Lit: Float(1.0)"#]], + ); +} + +#[test] +fn const_float_sized_decl_float_lit() { + check( + parse, + "const float[32] f = 1;", + &expect![[r#" + Stmt [0-22]: + annotations: + kind: ConstantDeclStmt [0-22]: + type: ScalarType [6-15]: FloatType [6-15]: + size: Expr [12-14]: Lit: Int(32) + ident: Ident [16-17] "f" + init_expr: Expr [20-21]: Lit: Int(1)"#]], + ); +} + +#[test] +fn angle_decl() { + check( + parse, + "angle a;", + &expect![[r#" + Stmt [0-8]: + annotations: + kind: ClassicalDeclarationStmt [0-8]: + type: ScalarType [0-5]: AngleType [0-5]: + size: + ident: Ident [6-7] "a" + init_expr: "#]], + ); +} + +#[test] +fn angle_decl_angle_lit() { + check( + parse, + "angle a = 1.0;", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: ClassicalDeclarationStmt [0-14]: + type: ScalarType [0-5]: AngleType [0-5]: + size: + ident: Ident [6-7] "a" + init_expr: Expr [10-13]: Lit: Float(1.0)"#]], + ); +} + +#[test] +fn const_angle_decl_no_init() { + check( + parse, + "const angle a;", + &expect![[r#" + Error( + Token( + Eq, + Semicolon, + Span { + lo: 13, + hi: 14, + }, + ), + ) + "#]], + ); +} + +#[test] +fn const_angle_decl_angle_lit() { + check( + parse, + "const angle a = 1.0;", + &expect![[r#" + Stmt [0-20]: + annotations: + kind: ConstantDeclStmt [0-20]: + type: ScalarType [6-11]: AngleType [6-11]: + size: + ident: Ident [12-13] "a" + init_expr: Expr [16-19]: Lit: Float(1.0)"#]], + ); +} + +#[test] +fn angle_sized_decl() { + check( + parse, + "angle[32] a;", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: ClassicalDeclarationStmt [0-12]: + type: ScalarType [0-9]: AngleType [0-9]: + size: Expr [6-8]: Lit: Int(32) + ident: Ident [10-11] "a" + init_expr: "#]], + ); +} + +#[test] +fn angle_sized_decl_angle_lit() { + check( + parse, + "angle[32] a = 1.0;", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: ClassicalDeclarationStmt [0-18]: + type: ScalarType [0-9]: AngleType [0-9]: + size: Expr [6-8]: Lit: Int(32) + ident: Ident [10-11] "a" + init_expr: Expr [14-17]: Lit: Float(1.0)"#]], + ); +} + +#[test] +fn const_angle_sized_decl_angle_lit() { + check( + parse, + "const angle[32] a = 1.0;", + &expect![[r#" + Stmt [0-24]: + annotations: + kind: ConstantDeclStmt [0-24]: + type: ScalarType [6-15]: AngleType [6-15]: + size: Expr [12-14]: Lit: Int(32) + ident: Ident [16-17] "a" + init_expr: Expr [20-23]: Lit: Float(1.0)"#]], + ); +} + +#[test] +fn duration_decl() { + check( + parse, + "duration d;", + &expect![[r#" + Stmt [0-11]: + annotations: + kind: ClassicalDeclarationStmt [0-11]: + type: ScalarType [0-8]: Duration + ident: Ident [9-10] "d" + init_expr: "#]], + ); +} + +#[test] +fn duration_decl_ns_lit() { + check( + parse, + "duration d = 1000ns;", + &expect![[r#" + Stmt [0-20]: + annotations: + kind: ClassicalDeclarationStmt [0-20]: + type: ScalarType [0-8]: Duration + ident: Ident [9-10] "d" + init_expr: Expr [13-19]: Lit: Duration(1000.0, Ns)"#]], + ); +} + +#[test] +fn duration_decl_us_lit() { + check( + parse, + "duration d = 1000us;", + &expect![[r#" + Stmt [0-20]: + annotations: + kind: ClassicalDeclarationStmt [0-20]: + type: ScalarType [0-8]: Duration + ident: Ident [9-10] "d" + init_expr: Expr [13-19]: Lit: Duration(1000.0, Us)"#]], + ); +} + +#[test] +fn duration_decl_uus_lit() { + // uus is for µ, disabling the lint must be done at the + // crate level, so using uus here in the test name. + check( + parse, + "duration d = 1000µs;", + &expect![[r#" + Stmt [0-21]: + annotations: + kind: ClassicalDeclarationStmt [0-21]: + type: ScalarType [0-8]: Duration + ident: Ident [9-10] "d" + init_expr: Expr [13-20]: Lit: Duration(1000.0, Us)"#]], + ); +} + +#[test] +fn duration_decl_ms_lit() { + check( + parse, + "duration d = 1000ms;", + &expect![[r#" + Stmt [0-20]: + annotations: + kind: ClassicalDeclarationStmt [0-20]: + type: ScalarType [0-8]: Duration + ident: Ident [9-10] "d" + init_expr: Expr [13-19]: Lit: Duration(1000.0, Ms)"#]], + ); +} + +#[test] +fn duration_decl_s_lit() { + check( + parse, + "duration d = 1000s;", + &expect![[r#" + Stmt [0-19]: + annotations: + kind: ClassicalDeclarationStmt [0-19]: + type: ScalarType [0-8]: Duration + ident: Ident [9-10] "d" + init_expr: Expr [13-18]: Lit: Duration(1000.0, S)"#]], + ); +} + +#[test] +fn duration_decl_dt_lit() { + check( + parse, + "duration d = 1000dt;", + &expect![[r#" + Stmt [0-20]: + annotations: + kind: ClassicalDeclarationStmt [0-20]: + type: ScalarType [0-8]: Duration + ident: Ident [9-10] "d" + init_expr: Expr [13-19]: Lit: Duration(1000.0, Dt)"#]], + ); +} + +#[test] +fn const_duration_decl_dt_lit() { + check( + parse, + "const duration d = 10dt;", + &expect![[r#" + Stmt [0-24]: + annotations: + kind: ConstantDeclStmt [0-24]: + type: ScalarType [6-14]: Duration + ident: Ident [15-16] "d" + init_expr: Expr [19-23]: Lit: Duration(10.0, Dt)"#]], + ); +} + +#[test] +fn stretch_decl() { + check( + parse, + "stretch s;", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: ClassicalDeclarationStmt [0-10]: + type: ScalarType [0-7]: Stretch + ident: Ident [8-9] "s" + init_expr: "#]], + ); +} + +#[test] +fn empty_array_decl() { + check( + parse, + "array[int, 0] arr = {};", + &expect![[r#" + Stmt [0-23]: + annotations: + kind: ClassicalDeclarationStmt [0-23]: + type: ArrayType [0-13]: + base_type: ArrayBaseTypeKind IntType [6-9]: + size: + dimensions: + Expr [11-12]: Lit: Int(0) + ident: Ident [14-17] "arr" + init_expr: Expr [20-22]: Lit: Array: "#]], + ); +} + +#[test] +fn simple_array_decl() { + check( + parse, + "array[int[32], 3] arr = {1, 2, 3};", + &expect![[r#" + Stmt [0-34]: + annotations: + kind: ClassicalDeclarationStmt [0-34]: + type: ArrayType [0-17]: + base_type: ArrayBaseTypeKind IntType [6-13]: + size: Expr [10-12]: Lit: Int(32) + dimensions: + Expr [15-16]: Lit: Int(3) + ident: Ident [18-21] "arr" + init_expr: Expr [24-33]: Lit: Array: + Expr [25-26]: Lit: Int(1) + Expr [28-29]: Lit: Int(2) + Expr [31-32]: Lit: Int(3)"#]], + ); +} + +#[test] +fn nested_array_decl() { + check( + parse, + "array[int[32], 3, 2] arr = {{1, 2}, {3, 4}, {5, 6}};", + &expect![[r#" + Stmt [0-52]: + annotations: + kind: ClassicalDeclarationStmt [0-52]: + type: ArrayType [0-20]: + base_type: ArrayBaseTypeKind IntType [6-13]: + size: Expr [10-12]: Lit: Int(32) + dimensions: + Expr [15-16]: Lit: Int(3) + Expr [18-19]: Lit: Int(2) + ident: Ident [21-24] "arr" + init_expr: Expr [27-51]: Lit: Array: + Expr [28-34]: Lit: Array: + Expr [29-30]: Lit: Int(1) + Expr [32-33]: Lit: Int(2) + Expr [36-42]: Lit: Array: + Expr [37-38]: Lit: Int(3) + Expr [40-41]: Lit: Int(4) + Expr [44-50]: Lit: Array: + Expr [45-46]: Lit: Int(5) + Expr [48-49]: Lit: Int(6)"#]], + ); +} + +#[test] +fn measure_hardware_qubit_decl() { + check( + parse, + "bit res = measure $12;", + &expect![[r#" + Stmt [0-22]: + annotations: + kind: ClassicalDeclarationStmt [0-22]: + type: ScalarType [0-3]: BitType [0-3]: + size: + ident: Ident [4-7] "res" + init_expr: MeasureExpr [10-21]: + operand: GateOperand [18-21]: + kind: HardwareQubit [18-21]: 12"#]], + ); +} + +#[test] +fn measure_register_decl() { + check( + parse, + "bit res = measure qubits[2][3];", + &expect![[r#" + Stmt [0-31]: + annotations: + kind: ClassicalDeclarationStmt [0-31]: + type: ScalarType [0-3]: BitType [0-3]: + size: + ident: Ident [4-7] "res" + init_expr: MeasureExpr [10-30]: + operand: GateOperand [18-30]: + kind: IndexedIdent [18-30]: + name: Ident [18-24] "qubits" + index_span: [24-30] + indices: + IndexSet [25-26]: + values: + Expr [25-26]: Lit: Int(2) + IndexSet [28-29]: + values: + Expr [28-29]: Lit: Int(3)"#]], + ); +} + +#[test] +fn const_decl_with_measurement_init_fails() { + check( + parse, + "const bit res = measure q;", + &expect![[r#" + Error( + Token( + Open( + Brace, + ), + Keyword( + Measure, + ), + Span { + lo: 16, + hi: 23, + }, + ), + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/def.rs b/compiler/qsc_qasm/src/parser/stmt/tests/def.rs new file mode 100644 index 0000000000..d257821433 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/def.rs @@ -0,0 +1,278 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::parser::tests::check; + +use crate::parser::stmt::parse; + +#[test] +fn minimal() { + check( + parse, + "def x() { }", + &expect![[r#" + Stmt [0-11]: + annotations: + kind: DefStmt [0-11]: + ident: Ident [4-5] "x" + parameters: + return_type: + body: Block [8-11]: "#]], + ); +} + +#[test] +fn missing_ty_error() { + check( + parse, + "def x() -> { }", + &expect![[r#" + Error( + Rule( + "scalar type", + Open( + Brace, + ), + Span { + lo: 11, + hi: 12, + }, + ), + ) + "#]], + ); +} + +#[test] +fn missing_args_with_delim_error() { + check( + parse, + "def x(,) { }", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: DefStmt [0-12]: + ident: Ident [4-5] "x" + parameters: + ScalarTypedParameter [6-6]: + type: ScalarType [0-0]: Err + ident: Ident [0-0] "" + return_type: + body: Block [9-12]: + + [ + Error( + MissingSeqEntry( + Span { + lo: 6, + hi: 6, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn args_with_extra_delim_err_ty() { + check( + parse, + "def x(int a,,int b) { }", + &expect![[r#" + Stmt [0-23]: + annotations: + kind: DefStmt [0-23]: + ident: Ident [4-5] "x" + parameters: + ScalarTypedParameter [6-11]: + type: ScalarType [6-9]: IntType [6-9]: + size: + ident: Ident [10-11] "a" + ScalarTypedParameter [12-12]: + type: ScalarType [0-0]: Err + ident: Ident [0-0] "" + ScalarTypedParameter [13-18]: + type: ScalarType [13-16]: IntType [13-16]: + size: + ident: Ident [17-18] "b" + return_type: + body: Block [20-23]: + + [ + Error( + MissingSeqEntry( + Span { + lo: 12, + hi: 12, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn classical_subroutine() { + check( + parse, + "def square(int[32] x) -> int { return x ** 2; }", + &expect![[r#" + Stmt [0-47]: + annotations: + kind: DefStmt [0-47]: + ident: Ident [4-10] "square" + parameters: + ScalarTypedParameter [11-20]: + type: ScalarType [11-18]: IntType [11-18]: + size: Expr [15-17]: Lit: Int(32) + ident: Ident [19-20] "x" + return_type: ScalarType [25-28]: IntType [25-28]: + size: + body: Block [29-47]: + Stmt [31-45]: + annotations: + kind: ReturnStmt [31-45]: + expr: Expr [38-44]: BinaryOpExpr: + op: Exp + lhs: Expr [38-39]: Ident [38-39] "x" + rhs: Expr [43-44]: Lit: Int(2)"#]], + ); +} + +#[test] +fn quantum_args() { + check( + parse, + "def x(qubit q, qubit[n] qubits) { }", + &expect![[r#" + Stmt [0-35]: + annotations: + kind: DefStmt [0-35]: + ident: Ident [4-5] "x" + parameters: + QuantumTypedParameter [6-13]: + size: + ident: Ident [12-13] "q" + QuantumTypedParameter [15-30]: + size: Expr [21-22]: Ident [21-22] "n" + ident: Ident [24-30] "qubits" + return_type: + body: Block [32-35]: "#]], + ); +} + +#[test] +fn old_style_args() { + check( + parse, + "def test(creg c, qreg q, creg c2[2], qreg q4[4]) -> int { return x ** 2; }", + &expect![[r#" + Stmt [0-74]: + annotations: + kind: DefStmt [0-74]: + ident: Ident [4-8] "test" + parameters: + ScalarTypedParameter [9-15]: + type: ScalarType [9-15]: BitType [9-15]: + size: + ident: Ident [14-15] "c" + QuantumTypedParameter [17-23]: + size: + ident: Ident [22-23] "q" + ScalarTypedParameter [25-35]: + type: ScalarType [25-35]: BitType [25-35]: + size: Expr [33-34]: Lit: Int(2) + ident: Ident [30-32] "c2" + QuantumTypedParameter [37-47]: + size: Expr [45-46]: Lit: Int(4) + ident: Ident [42-44] "q4" + return_type: ScalarType [52-55]: IntType [52-55]: + size: + body: Block [56-74]: + Stmt [58-72]: + annotations: + kind: ReturnStmt [58-72]: + expr: Expr [65-71]: BinaryOpExpr: + op: Exp + lhs: Expr [65-66]: Ident [65-66] "x" + rhs: Expr [70-71]: Lit: Int(2)"#]], + ); +} + +#[test] +fn readonly_array_arg_with_int_dims() { + check( + parse, + "def specified_sub(readonly array[int[8], 2, 10] arr_arg) {}", + &expect![[r#" + Stmt [0-59]: + annotations: + kind: DefStmt [0-59]: + ident: Ident [4-17] "specified_sub" + parameters: + ArrayTypedParameter [18-55]: + type: ArrayReferenceType [18-47]: + mutability: ReadOnly + base_type: ArrayBaseTypeKind IntType [33-39]: + size: Expr [37-38]: Lit: Int(8) + dimensions: + Expr [41-42]: Lit: Int(2) + Expr [44-46]: Lit: Int(10) + + ident: Ident [48-55] "arr_arg" + return_type: + body: Block [57-59]: "#]], + ); +} + +#[test] +fn readonly_array_arg_with_dim() { + check( + parse, + "def arr_subroutine(readonly array[int[8], #dim = 1] arr_arg) {}", + &expect![[r#" + Stmt [0-63]: + annotations: + kind: DefStmt [0-63]: + ident: Ident [4-18] "arr_subroutine" + parameters: + ArrayTypedParameter [19-59]: + type: ArrayReferenceType [19-51]: + mutability: ReadOnly + base_type: ArrayBaseTypeKind IntType [34-40]: + size: Expr [38-39]: Lit: Int(8) + dimensions: + Expr [49-50]: Lit: Int(1) + + ident: Ident [52-59] "arr_arg" + return_type: + body: Block [61-63]: "#]], + ); +} + +#[test] +fn mutable_array_arg() { + check( + parse, + "def mut_subroutine(mutable array[int[8], #dim = 1] arr_arg) {}", + &expect![[r#" + Stmt [0-62]: + annotations: + kind: DefStmt [0-62]: + ident: Ident [4-18] "mut_subroutine" + parameters: + ArrayTypedParameter [19-58]: + type: ArrayReferenceType [19-50]: + mutability: Mutable + base_type: ArrayBaseTypeKind IntType [33-39]: + size: Expr [37-38]: Lit: Int(8) + dimensions: + Expr [48-49]: Lit: Int(1) + + ident: Ident [51-58] "arr_arg" + return_type: + body: Block [60-62]: "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/defcal.rs b/compiler/qsc_qasm/src/parser/stmt/tests/defcal.rs new file mode 100644 index 0000000000..b086ea2e68 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/defcal.rs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn defcal_block_with_unbalanced_braces_errors() { + check( + parse, + "defcal foo q { { }", + &expect![[r#" + Error( + Token( + Close( + Brace, + ), + Eof, + Span { + lo: 0, + hi: 18, + }, + ), + ) + "#]], + ); +} + +#[test] +fn cal_block_accept_any_tokens_inside() { + check( + parse, + " + defcal foo(a, b) q0 q1 { + faoi foaijdf a; + fkfm )( + .314 + }", + &expect![[r#" + Stmt [5-88]: + annotations: + kind: DefCalStmt [5-88]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/delay.rs b/compiler/qsc_qasm/src/parser/stmt/tests/delay.rs new file mode 100644 index 0000000000..35bf3bb7d1 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/delay.rs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn delay() { + check( + parse, + "delay[a] q[0], q[1];", + &expect![[r#" + Stmt [0-20]: + annotations: + kind: DelayStmt [0-20]: + duration: Expr [6-7]: Ident [6-7] "a" + qubits: + GateOperand [9-13]: + kind: IndexedIdent [9-13]: + name: Ident [9-10] "q" + index_span: [10-13] + indices: + IndexSet [11-12]: + values: + Expr [11-12]: Lit: Int(0) + GateOperand [15-19]: + kind: IndexedIdent [15-19]: + name: Ident [15-16] "q" + index_span: [16-19] + indices: + IndexSet [17-18]: + values: + Expr [17-18]: Lit: Int(1)"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/expr_stmt.rs b/compiler/qsc_qasm/src/parser/stmt/tests/expr_stmt.rs new file mode 100644 index 0000000000..bfad136fb6 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/expr_stmt.rs @@ -0,0 +1,374 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn identifier() { + check( + parse, + "H;", + &expect![[r#" + Stmt [0-2]: + annotations: + kind: ExprStmt [0-2]: + expr: Expr [0-1]: Ident [0-1] "H""#]], + ); +} + +#[test] +fn identifier_plus_number() { + check( + parse, + "H + 2;", + &expect![[r#" + Stmt [0-6]: + annotations: + kind: ExprStmt [0-6]: + expr: Expr [0-5]: BinaryOpExpr: + op: Add + lhs: Expr [0-1]: Ident [0-1] "H" + rhs: Expr [4-5]: Lit: Int(2)"#]], + ); +} + +#[test] +fn assignment() { + check( + parse, + "a = 1;", + &expect![[r#" + Stmt [0-6]: + annotations: + kind: AssignStmt [0-6]: + lhs: IndexedIdent [0-1]: + name: Ident [0-1] "a" + index_span: [0-0] + indices: + rhs: Expr [4-5]: Lit: Int(1)"#]], + ); +} + +#[test] +fn index_assignment() { + check( + parse, + "a[0] = 1;", + &expect![[r#" + Stmt [0-9]: + annotations: + kind: AssignStmt [0-9]: + lhs: IndexedIdent [0-4]: + name: Ident [0-1] "a" + index_span: [1-4] + indices: + IndexSet [2-3]: + values: + Expr [2-3]: Lit: Int(0) + rhs: Expr [7-8]: Lit: Int(1)"#]], + ); +} + +#[test] +fn multi_index_assignment() { + check( + parse, + "a[0][1] = 1;", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: AssignStmt [0-12]: + lhs: IndexedIdent [0-7]: + name: Ident [0-1] "a" + index_span: [1-7] + indices: + IndexSet [2-3]: + values: + Expr [2-3]: Lit: Int(0) + IndexSet [5-6]: + values: + Expr [5-6]: Lit: Int(1) + rhs: Expr [10-11]: Lit: Int(1)"#]], + ); +} + +#[test] +fn assignment_op() { + check( + parse, + "a += 1;", + &expect![[r#" + Stmt [0-7]: + annotations: + kind: AssignOpStmt [0-7]: + op: Add + lhs: IndexedIdent [0-1]: + name: Ident [0-1] "a" + index_span: [0-0] + indices: + rhs: Expr [5-6]: Lit: Int(1)"#]], + ); +} + +#[test] +fn index_assignment_op() { + check( + parse, + "a[0] += 1;", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: AssignOpStmt [0-10]: + op: Add + lhs: IndexedIdent [0-4]: + name: Ident [0-1] "a" + index_span: [1-4] + indices: + IndexSet [2-3]: + values: + Expr [2-3]: Lit: Int(0) + rhs: Expr [8-9]: Lit: Int(1)"#]], + ); +} + +#[test] +fn multi_index_assignment_op() { + check( + parse, + "a[0][1] += 1;", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: AssignOpStmt [0-13]: + op: Add + lhs: IndexedIdent [0-7]: + name: Ident [0-1] "a" + index_span: [1-7] + indices: + IndexSet [2-3]: + values: + Expr [2-3]: Lit: Int(0) + IndexSet [5-6]: + values: + Expr [5-6]: Lit: Int(1) + rhs: Expr [11-12]: Lit: Int(1)"#]], + ); +} + +#[test] +fn assignment_and_unop() { + check( + parse, + "c = a && !b;", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: AssignStmt [0-12]: + lhs: IndexedIdent [0-1]: + name: Ident [0-1] "c" + index_span: [0-0] + indices: + rhs: Expr [4-11]: BinaryOpExpr: + op: AndL + lhs: Expr [4-5]: Ident [4-5] "a" + rhs: Expr [9-11]: UnaryOpExpr: + op: NotL + expr: Expr [10-11]: Ident [10-11] "b""#]], + ); +} + +#[test] +fn assignment_unop_and() { + check( + parse, + "d = !a && b;", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: AssignStmt [0-12]: + lhs: IndexedIdent [0-1]: + name: Ident [0-1] "d" + index_span: [0-0] + indices: + rhs: Expr [4-11]: BinaryOpExpr: + op: AndL + lhs: Expr [4-6]: UnaryOpExpr: + op: NotL + expr: Expr [5-6]: Ident [5-6] "a" + rhs: Expr [10-11]: Ident [10-11] "b""#]], + ); +} + +// These are negative unit tests for gate calls: + +#[test] +fn function_call_plus_ident() { + check( + parse, + "Name(2, 3) + a;", + &expect![[r#" + Stmt [0-15]: + annotations: + kind: ExprStmt [0-15]: + expr: Expr [0-14]: BinaryOpExpr: + op: Add + lhs: Expr [0-10]: FunctionCall [0-10]: + name: Ident [0-4] "Name" + args: + Expr [5-6]: Lit: Int(2) + Expr [8-9]: Lit: Int(3) + rhs: Expr [13-14]: Ident [13-14] "a""#]], + ); +} + +#[test] +fn function_call() { + check( + parse, + "Name(2, 3);", + &expect![[r#" + Stmt [0-11]: + annotations: + kind: ExprStmt [0-11]: + expr: Expr [0-10]: FunctionCall [0-10]: + name: Ident [0-4] "Name" + args: + Expr [5-6]: Lit: Int(2) + Expr [8-9]: Lit: Int(3)"#]], + ); +} + +#[test] +fn indexed_function_call() { + check( + parse, + "Name(2, 3)[1];", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: ExprStmt [0-14]: + expr: Expr [0-13]: IndexExpr [0-13]: + collection: Expr [0-10]: FunctionCall [0-10]: + name: Ident [0-4] "Name" + args: + Expr [5-6]: Lit: Int(2) + Expr [8-9]: Lit: Int(3) + index: IndexSet [11-12]: + values: + Expr [11-12]: Lit: Int(1)"#]], + ); +} + +#[test] +fn multi_indexed_function_call() { + check( + parse, + "Name(2, 3)[1, 0];", + &expect![[r#" + Stmt [0-17]: + annotations: + kind: ExprStmt [0-17]: + expr: Expr [0-16]: IndexExpr [0-16]: + collection: Expr [0-10]: FunctionCall [0-10]: + name: Ident [0-4] "Name" + args: + Expr [5-6]: Lit: Int(2) + Expr [8-9]: Lit: Int(3) + index: IndexSet [11-15]: + values: + Expr [11-12]: Lit: Int(1) + Expr [14-15]: Lit: Int(0)"#]], + ); +} + +#[test] +fn ident() { + check( + parse, + "Name;", + &expect![[r#" + Stmt [0-5]: + annotations: + kind: ExprStmt [0-5]: + expr: Expr [0-4]: Ident [0-4] "Name""#]], + ); +} + +#[test] +fn index_expr() { + check( + parse, + "Name[1];", + &expect![[r#" + Stmt [0-8]: + annotations: + kind: ExprStmt [0-8]: + expr: Expr [0-7]: IndexExpr [0-7]: + collection: Expr [0-4]: Ident [0-4] "Name" + index: IndexSet [5-6]: + values: + Expr [5-6]: Lit: Int(1)"#]], + ); +} + +#[test] +fn index_expr_with_multiple_index_operators_errors() { + check( + parse, + "Name[1][2];", + &expect![[r#" + Stmt [0-11]: + annotations: + kind: ExprStmt [0-11]: + expr: Expr [0-10]: IndexExpr [0-10]: + collection: Expr [0-4]: Ident [0-4] "Name" + index: IndexSet [5-6]: + values: + Expr [5-6]: Lit: Int(1) + + [ + Error( + MultipleIndexOperators( + Span { + lo: 0, + hi: 10, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn cast_expr() { + check( + parse, + "bit(0);", + &expect![[r#" + Stmt [0-7]: + annotations: + kind: ExprStmt [0-7]: + expr: Expr [0-6]: Cast [0-6]: + type: ScalarType [0-3]: BitType [0-3]: + size: + arg: Expr [4-5]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_expr_with_designator() { + check( + parse, + "bit[45](0);", + &expect![[r#" + Stmt [0-11]: + annotations: + kind: ExprStmt [0-11]: + expr: Expr [0-10]: Cast [0-10]: + type: ScalarType [0-7]: BitType [0-7]: + size: Expr [4-6]: Lit: Int(45) + arg: Expr [8-9]: Lit: Int(0)"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/extern_decl.rs b/compiler/qsc_qasm/src/parser/stmt/tests/extern_decl.rs new file mode 100644 index 0000000000..6022cf83f1 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/extern_decl.rs @@ -0,0 +1,297 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::parser::tests::check; + +use crate::parser::stmt::parse; + +#[test] +fn missing_semicolon_err() { + check( + parse, + "extern x()", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: ExternDecl [0-10]: + ident: Ident [7-8] "x" + parameters: + return_type: + + [ + Error( + Token( + Semicolon, + Eof, + Span { + lo: 10, + hi: 10, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn bit_param_bit_ret_decl() { + check( + parse, + "extern x(bit) -> bit;", + &expect![[r#" + Stmt [0-21]: + annotations: + kind: ExternDecl [0-21]: + ident: Ident [7-8] "x" + parameters: + [9-12]: ScalarType [9-12]: BitType [9-12]: + size: + return_type: ScalarType [17-20]: BitType [17-20]: + size: "#]], + ); +} + +#[test] +fn sized_bit_param_bit_ret_decl() { + check( + parse, + "extern x(bit[n]) -> bit;", + &expect![[r#" + Stmt [0-24]: + annotations: + kind: ExternDecl [0-24]: + ident: Ident [7-8] "x" + parameters: + [9-15]: ScalarType [9-15]: BitType [9-15]: + size: Expr [13-14]: Ident [13-14] "n" + return_type: ScalarType [20-23]: BitType [20-23]: + size: "#]], + ); +} + +#[test] +fn sized_creg_param_bit_ret_decl() { + check( + parse, + "extern x(creg[n]) -> bit;", + &expect![[r#" + Stmt [0-25]: + annotations: + kind: ExternDecl [0-25]: + ident: Ident [7-8] "x" + parameters: + [9-16]: ScalarType [9-16]: BitType [9-16]: + size: Expr [14-15]: Ident [14-15] "n" + return_type: ScalarType [21-24]: BitType [21-24]: + size: "#]], + ); +} + +#[test] +fn creg_param_bit_ret_decl() { + check( + parse, + "extern x(creg) -> bit;", + &expect![[r#" + Stmt [0-22]: + annotations: + kind: ExternDecl [0-22]: + ident: Ident [7-8] "x" + parameters: + [9-13]: ScalarType [9-13]: BitType [9-13]: + size: + return_type: ScalarType [18-21]: BitType [18-21]: + size: "#]], + ); +} + +#[test] +fn readonly_array_arg_with_int_dims() { + check( + parse, + "extern x(readonly array[int[8], 2, 10]);", + &expect![[r#" + Stmt [0-40]: + annotations: + kind: ExternDecl [0-40]: + ident: Ident [7-8] "x" + parameters: + [9-38]: ArrayReferenceType [9-38]: + mutability: ReadOnly + base_type: ArrayBaseTypeKind IntType [24-30]: + size: Expr [28-29]: Lit: Int(8) + dimensions: + Expr [32-33]: Lit: Int(2) + Expr [35-37]: Lit: Int(10) + + return_type: "#]], + ); +} + +#[test] +fn readonly_array_arg_with_dim() { + check( + parse, + "extern x(readonly array[int[8], #dim = 1]);", + &expect![[r#" + Stmt [0-43]: + annotations: + kind: ExternDecl [0-43]: + ident: Ident [7-8] "x" + parameters: + [9-41]: ArrayReferenceType [9-41]: + mutability: ReadOnly + base_type: ArrayBaseTypeKind IntType [24-30]: + size: Expr [28-29]: Lit: Int(8) + dimensions: + Expr [39-40]: Lit: Int(1) + + return_type: "#]], + ); +} + +#[test] +fn mutable_array_arg() { + check( + parse, + "extern x(mutable array[int[8], #dim = 1]);", + &expect![[r#" + Stmt [0-42]: + annotations: + kind: ExternDecl [0-42]: + ident: Ident [7-8] "x" + parameters: + [9-40]: ArrayReferenceType [9-40]: + mutability: Mutable + base_type: ArrayBaseTypeKind IntType [23-29]: + size: Expr [27-28]: Lit: Int(8) + dimensions: + Expr [38-39]: Lit: Int(1) + + return_type: "#]], + ); +} + +#[test] +fn unexpected_ident_in_params() { + check( + parse, + "extern x(creg c) -> bit;", + &expect![[r#" + Error( + Token( + Close( + Paren, + ), + Identifier, + Span { + lo: 14, + hi: 15, + }, + ), + ) + "#]], + ); +} + +#[test] +fn annotation() { + check( + parse, + r#"@test.annotation + extern x(creg) -> bit;"#, + &expect![[r#" + Stmt [0-47]: + annotations: + Annotation [0-16]: + identifier: "test.annotation" + value: + kind: ExternDecl [25-47]: + ident: Ident [32-33] "x" + parameters: + [34-38]: ScalarType [34-38]: BitType [34-38]: + size: + return_type: ScalarType [43-46]: BitType [43-46]: + size: "#]], + ); +} + +#[test] +fn missing_ty_error() { + check( + parse, + "extern x() -> ;", + &expect![[r#" + Error( + Rule( + "scalar type", + Semicolon, + Span { + lo: 14, + hi: 15, + }, + ), + ) + "#]], + ); +} + +#[test] +fn missing_args_with_delim_error() { + check( + parse, + "extern x(,);", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: ExternDecl [0-12]: + ident: Ident [7-8] "x" + parameters: + [9-9]: ScalarType [0-0]: Err + return_type: + + [ + Error( + MissingSeqEntry( + Span { + lo: 9, + hi: 9, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn args_with_extra_delim_err_ty() { + check( + parse, + "extern x(int,,int);", + &expect![[r#" + Stmt [0-19]: + annotations: + kind: ExternDecl [0-19]: + ident: Ident [7-8] "x" + parameters: + [9-12]: ScalarType [9-12]: IntType [9-12]: + size: + [13-13]: ScalarType [0-0]: Err + [14-17]: ScalarType [14-17]: IntType [14-17]: + size: + return_type: + + [ + Error( + MissingSeqEntry( + Span { + lo: 13, + hi: 13, + }, + ), + ), + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/for_loops.rs b/compiler/qsc_qasm/src/parser/stmt/tests/for_loops.rs new file mode 100644 index 0000000000..a0debdda1c --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/for_loops.rs @@ -0,0 +1,389 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn simple_for_stmt() { + check( + parse, + " + for int x in {1, 2, 3} { + a = 0; + }", + &expect![[r#" + Stmt [5-50]: + annotations: + kind: ForStmt [5-50]: + variable_type: ScalarType [9-12]: IntType [9-12]: + size: + variable_name: Ident [13-14] "x" + iterable: DiscreteSet [18-27]: + values: + Expr [19-20]: Lit: Int(1) + Expr [22-23]: Lit: Int(2) + Expr [25-26]: Lit: Int(3) + body: Stmt [28-50]: + annotations: + kind: Block [28-50]: + Stmt [38-44]: + annotations: + kind: AssignStmt [38-44]: + lhs: IndexedIdent [38-39]: + name: Ident [38-39] "a" + index_span: [0-0] + indices: + rhs: Expr [42-43]: Lit: Int(0)"#]], + ); +} + +#[test] +fn empty_for_stmt_body() { + check( + parse, + "for int x in {} {}", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: ForStmt [0-18]: + variable_type: ScalarType [4-7]: IntType [4-7]: + size: + variable_name: Ident [8-9] "x" + iterable: DiscreteSet [13-15]: + values: + body: Stmt [16-18]: + annotations: + kind: Block [16-18]: "#]], + ); +} + +#[test] +fn simple_for_stmt_stmt_body() { + check( + parse, + " + for int x in {1, 2, 3} + a = 0; + ", + &expect![[r#" + Stmt [5-42]: + annotations: + kind: ForStmt [5-42]: + variable_type: ScalarType [9-12]: IntType [9-12]: + size: + variable_name: Ident [13-14] "x" + iterable: DiscreteSet [18-27]: + values: + Expr [19-20]: Lit: Int(1) + Expr [22-23]: Lit: Int(2) + Expr [25-26]: Lit: Int(3) + body: Stmt [36-42]: + annotations: + kind: AssignStmt [36-42]: + lhs: IndexedIdent [36-37]: + name: Ident [36-37] "a" + index_span: [0-0] + indices: + rhs: Expr [40-41]: Lit: Int(0)"#]], + ); +} + +#[test] +fn for_stmt_iterating_over_range() { + check( + parse, + " + for int x in [0:2:7] { + a = 0; + }", + &expect![[r#" + Stmt [5-48]: + annotations: + kind: ForStmt [5-48]: + variable_type: ScalarType [9-12]: IntType [9-12]: + size: + variable_name: Ident [13-14] "x" + iterable: RangeDefinition [18-25]: + start: Expr [19-20]: Lit: Int(0) + step: Expr [21-22]: Lit: Int(2) + end: Expr [23-24]: Lit: Int(7) + body: Stmt [26-48]: + annotations: + kind: Block [26-48]: + Stmt [36-42]: + annotations: + kind: AssignStmt [36-42]: + lhs: IndexedIdent [36-37]: + name: Ident [36-37] "a" + index_span: [0-0] + indices: + rhs: Expr [40-41]: Lit: Int(0)"#]], + ); +} + +#[test] +fn for_stmt_iterating_over_range_no_step() { + check( + parse, + " + for int x in [0:7] { + a = 0; + }", + &expect![[r#" + Stmt [5-46]: + annotations: + kind: ForStmt [5-46]: + variable_type: ScalarType [9-12]: IntType [9-12]: + size: + variable_name: Ident [13-14] "x" + iterable: RangeDefinition [18-23]: + start: Expr [19-20]: Lit: Int(0) + step: + end: Expr [21-22]: Lit: Int(7) + body: Stmt [24-46]: + annotations: + kind: Block [24-46]: + Stmt [34-40]: + annotations: + kind: AssignStmt [34-40]: + lhs: IndexedIdent [34-35]: + name: Ident [34-35] "a" + index_span: [0-0] + indices: + rhs: Expr [38-39]: Lit: Int(0)"#]], + ); +} + +#[test] +fn for_stmt_iterating_over_expr() { + check( + parse, + " + for int x in xs { + a = 0; + }", + &expect![[r#" + Stmt [5-43]: + annotations: + kind: ForStmt [5-43]: + variable_type: ScalarType [9-12]: IntType [9-12]: + size: + variable_name: Ident [13-14] "x" + iterable: Expr [18-20]: Ident [18-20] "xs" + body: Stmt [21-43]: + annotations: + kind: Block [21-43]: + Stmt [31-37]: + annotations: + kind: AssignStmt [31-37]: + lhs: IndexedIdent [31-32]: + name: Ident [31-32] "a" + index_span: [0-0] + indices: + rhs: Expr [35-36]: Lit: Int(0)"#]], + ); +} + +#[test] +fn for_stmt_with_continue_stmt() { + check( + parse, + " + for int x in {1, 2, 3} { + a = 0; + continue; + }", + &expect![[r#" + Stmt [5-68]: + annotations: + kind: ForStmt [5-68]: + variable_type: ScalarType [9-12]: IntType [9-12]: + size: + variable_name: Ident [13-14] "x" + iterable: DiscreteSet [18-27]: + values: + Expr [19-20]: Lit: Int(1) + Expr [22-23]: Lit: Int(2) + Expr [25-26]: Lit: Int(3) + body: Stmt [28-68]: + annotations: + kind: Block [28-68]: + Stmt [38-44]: + annotations: + kind: AssignStmt [38-44]: + lhs: IndexedIdent [38-39]: + name: Ident [38-39] "a" + index_span: [0-0] + indices: + rhs: Expr [42-43]: Lit: Int(0) + Stmt [53-62]: + annotations: + kind: ContinueStmt [53-62]"#]], + ); +} + +#[test] +fn for_loop_with_break_stmt() { + check( + parse, + " + for int x in {1, 2, 3} { + a = 0; + break; + }", + &expect![[r#" + Stmt [5-65]: + annotations: + kind: ForStmt [5-65]: + variable_type: ScalarType [9-12]: IntType [9-12]: + size: + variable_name: Ident [13-14] "x" + iterable: DiscreteSet [18-27]: + values: + Expr [19-20]: Lit: Int(1) + Expr [22-23]: Lit: Int(2) + Expr [25-26]: Lit: Int(3) + body: Stmt [28-65]: + annotations: + kind: Block [28-65]: + Stmt [38-44]: + annotations: + kind: AssignStmt [38-44]: + lhs: IndexedIdent [38-39]: + name: Ident [38-39] "a" + index_span: [0-0] + indices: + rhs: Expr [42-43]: Lit: Int(0) + Stmt [53-59]: + annotations: + kind: BreakStmt [53-59]"#]], + ); +} + +#[test] +fn single_stmt_for_stmt() { + check( + parse, + "for int x in {} z q;", + &expect![[r#" + Stmt [0-20]: + annotations: + kind: ForStmt [0-20]: + variable_type: ScalarType [4-7]: IntType [4-7]: + size: + variable_name: Ident [8-9] "x" + iterable: DiscreteSet [13-15]: + values: + body: Stmt [16-20]: + annotations: + kind: GateCall [16-20]: + modifiers: + name: Ident [16-17] "z" + args: + duration: + qubits: + GateOperand [18-19]: + kind: IndexedIdent [18-19]: + name: Ident [18-19] "q" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn annotations_in_single_stmt_for_stmt() { + check( + parse, + " + for int x in {} + @foo + @bar + x = 5;", + &expect![[r#" + Stmt [5-61]: + annotations: + kind: ForStmt [5-61]: + variable_type: ScalarType [9-12]: IntType [9-12]: + size: + variable_name: Ident [13-14] "x" + iterable: DiscreteSet [18-20]: + values: + body: Stmt [29-61]: + annotations: + Annotation [29-33]: + identifier: "foo" + value: + Annotation [42-46]: + identifier: "bar" + value: + kind: AssignStmt [55-61]: + lhs: IndexedIdent [55-56]: + name: Ident [55-56] "x" + index_span: [0-0] + indices: + rhs: Expr [59-60]: Lit: Int(5)"#]], + ); +} + +#[test] +fn nested_single_stmt_for_stmt() { + check( + parse, + "for int x in {} for int y in {} z q;", + &expect![[r#" + Stmt [0-36]: + annotations: + kind: ForStmt [0-36]: + variable_type: ScalarType [4-7]: IntType [4-7]: + size: + variable_name: Ident [8-9] "x" + iterable: DiscreteSet [13-15]: + values: + body: Stmt [16-36]: + annotations: + kind: ForStmt [16-36]: + variable_type: ScalarType [20-23]: IntType [20-23]: + size: + variable_name: Ident [24-25] "y" + iterable: DiscreteSet [29-31]: + values: + body: Stmt [32-36]: + annotations: + kind: GateCall [32-36]: + modifiers: + name: Ident [32-33] "z" + args: + duration: + qubits: + GateOperand [34-35]: + kind: IndexedIdent [34-35]: + name: Ident [34-35] "q" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn for_stmt_with_indented_identifier_errors() { + check( + parse, + "for int x[2] in {} {}", + &expect![[r#" + Error( + Token( + Keyword( + In, + ), + Open( + Bracket, + ), + Span { + lo: 9, + hi: 10, + }, + ), + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/gate_call.rs b/compiler/qsc_qasm/src/parser/stmt/tests/gate_call.rs new file mode 100644 index 0000000000..d905a0095f --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/gate_call.rs @@ -0,0 +1,347 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn gate_call() { + check( + parse, + "H q0;", + &expect![[r#" + Stmt [0-5]: + annotations: + kind: GateCall [0-5]: + modifiers: + name: Ident [0-1] "H" + args: + duration: + qubits: + GateOperand [2-4]: + kind: IndexedIdent [2-4]: + name: Ident [2-4] "q0" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn gate_call_qubit_register() { + check( + parse, + "H q[2];", + &expect![[r#" + Stmt [0-7]: + annotations: + kind: GateCall [0-7]: + modifiers: + name: Ident [0-1] "H" + args: + duration: + qubits: + GateOperand [2-6]: + kind: IndexedIdent [2-6]: + name: Ident [2-3] "q" + index_span: [3-6] + indices: + IndexSet [4-5]: + values: + Expr [4-5]: Lit: Int(2)"#]], + ); +} + +#[test] +fn gate_multiple_qubits() { + check( + parse, + "CNOT q0, q[4];", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: GateCall [0-14]: + modifiers: + name: Ident [0-4] "CNOT" + args: + duration: + qubits: + GateOperand [5-7]: + kind: IndexedIdent [5-7]: + name: Ident [5-7] "q0" + index_span: [0-0] + indices: + GateOperand [9-13]: + kind: IndexedIdent [9-13]: + name: Ident [9-10] "q" + index_span: [10-13] + indices: + IndexSet [11-12]: + values: + Expr [11-12]: Lit: Int(4)"#]], + ); +} + +#[test] +fn gate_with_no_qubits() { + check( + parse, + "inv @ H;", + &expect![[r#" + Stmt [0-8]: + annotations: + kind: GateCall [0-8]: + modifiers: + QuantumGateModifier [0-5]: + modifier_keyword_span: [0-3] + kind: Inv + name: Ident [6-7] "H" + args: + duration: + qubits: + + [ + Error( + MissingGateCallOperands( + Span { + lo: 0, + hi: 8, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn gate_call_with_parameters() { + check( + parse, + "Rx(pi / 2) q0;", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: GateCall [0-14]: + modifiers: + name: Ident [0-2] "Rx" + args: + Expr [3-9]: BinaryOpExpr: + op: Div + lhs: Expr [3-5]: Ident [3-5] "pi" + rhs: Expr [8-9]: Lit: Int(2) + duration: + qubits: + GateOperand [11-13]: + kind: IndexedIdent [11-13]: + name: Ident [11-13] "q0" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn gate_call_inv_modifier() { + check( + parse, + "inv @ H q0;", + &expect![[r#" + Stmt [0-11]: + annotations: + kind: GateCall [0-11]: + modifiers: + QuantumGateModifier [0-5]: + modifier_keyword_span: [0-3] + kind: Inv + name: Ident [6-7] "H" + args: + duration: + qubits: + GateOperand [8-10]: + kind: IndexedIdent [8-10]: + name: Ident [8-10] "q0" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn gate_call_ctrl_inv_modifiers() { + check( + parse, + "ctrl(2) @ inv @ Rx(pi / 2) c1, c2, q0;", + &expect![[r#" + Stmt [0-38]: + annotations: + kind: GateCall [0-38]: + modifiers: + QuantumGateModifier [0-9]: + modifier_keyword_span: [0-4] + kind: Ctrl Some(Expr { span: Span { lo: 5, hi: 6 }, kind: Lit(Lit { span: Span { lo: 5, hi: 6 }, kind: Int(2) }) }) + QuantumGateModifier [10-15]: + modifier_keyword_span: [10-13] + kind: Inv + name: Ident [16-18] "Rx" + args: + Expr [19-25]: BinaryOpExpr: + op: Div + lhs: Expr [19-21]: Ident [19-21] "pi" + rhs: Expr [24-25]: Lit: Int(2) + duration: + qubits: + GateOperand [27-29]: + kind: IndexedIdent [27-29]: + name: Ident [27-29] "c1" + index_span: [0-0] + indices: + GateOperand [31-33]: + kind: IndexedIdent [31-33]: + name: Ident [31-33] "c2" + index_span: [0-0] + indices: + GateOperand [35-37]: + kind: IndexedIdent [35-37]: + name: Ident [35-37] "q0" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn binary_expr_qubit() { + check( + parse, + "Name(2, 3) + a q;", + &expect![[r#" + Error( + ExpectedItem( + Identifier, + Span { + lo: 0, + hi: 14, + }, + ), + ) + "#]], + ); +} + +#[test] +fn parametrized_gate_call() { + check( + parse, + "Name(2, 3) q;", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: GateCall [0-13]: + modifiers: + name: Ident [0-4] "Name" + args: + Expr [5-6]: Lit: Int(2) + Expr [8-9]: Lit: Int(3) + duration: + qubits: + GateOperand [11-12]: + kind: IndexedIdent [11-12]: + name: Ident [11-12] "q" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn parametrized_gate_call_with_designator() { + check( + parse, + "Name(2, 3)[1] q;", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: GateCall [0-16]: + modifiers: + name: Ident [0-4] "Name" + args: + Expr [5-6]: Lit: Int(2) + Expr [8-9]: Lit: Int(3) + duration: Expr [11-12]: Lit: Int(1) + qubits: + GateOperand [14-15]: + kind: IndexedIdent [14-15]: + name: Ident [14-15] "q" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn multi_indexed_gate_call() { + check( + parse, + "Name(2, 3)[1, 0] q;", + &expect![[r#" + Error( + InvalidGateCallDesignator( + Span { + lo: 0, + hi: 16, + }, + ), + ) + "#]], + ); +} + +#[test] +fn gate_call_with_designator() { + check( + parse, + "H[2us] q;", + &expect![[r#" + Stmt [0-9]: + annotations: + kind: GateCall [0-9]: + modifiers: + name: Ident [0-1] "H" + args: + duration: Expr [2-5]: Lit: Duration(2.0, Us) + qubits: + GateOperand [7-8]: + kind: IndexedIdent [7-8]: + name: Ident [7-8] "q" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn gate_call_with_invalid_designator() { + check( + parse, + "H[2us][3] q;", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: GateCall [0-12]: + modifiers: + name: Ident [0-1] "H" + args: + duration: Expr [2-5]: Lit: Duration(2.0, Us) + qubits: + GateOperand [10-11]: + kind: IndexedIdent [10-11]: + name: Ident [10-11] "q" + index_span: [0-0] + indices: + + [ + Error( + MultipleIndexOperators( + Span { + lo: 0, + hi: 9, + }, + ), + ), + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/gate_def.rs b/compiler/qsc_qasm/src/parser/stmt/tests/gate_def.rs new file mode 100644 index 0000000000..d54e31d86a --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/gate_def.rs @@ -0,0 +1,216 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::parser::tests::check; + +use crate::parser::stmt::parse; + +#[test] +fn no_qubits_no_classical() { + check( + parse, + "gate c0q0 {}", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: Gate [0-12]: + ident: Ident [5-9] "c0q0" + parameters: + qubits: + body: Block [10-12]: "#]], + ); +} + +#[test] +fn no_qubits_no_classical_with_parens() { + check( + parse, + "gate c0q0() {}", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: Gate [0-14]: + ident: Ident [5-9] "c0q0" + parameters: + qubits: + body: Block [12-14]: "#]], + ); +} + +#[test] +fn one_qubit_no_classical() { + check( + parse, + "gate c0q1 a {}", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: Gate [0-14]: + ident: Ident [5-9] "c0q1" + parameters: + qubits: + Ident [10-11] "a" + body: Block [12-14]: "#]], + ); +} + +#[test] +fn two_qubits_no_classical() { + check( + parse, + "gate c0q2 a, b {}", + &expect![[r#" + Stmt [0-17]: + annotations: + kind: Gate [0-17]: + ident: Ident [5-9] "c0q2" + parameters: + qubits: + Ident [10-11] "a" + Ident [13-14] "b" + body: Block [15-17]: "#]], + ); +} + +#[test] +fn three_qubits_trailing_comma_no_classical() { + check( + parse, + "gate c0q3 a, b, c, {}", + &expect![[r#" + Stmt [0-21]: + annotations: + kind: Gate [0-21]: + ident: Ident [5-9] "c0q3" + parameters: + qubits: + Ident [10-11] "a" + Ident [13-14] "b" + Ident [16-17] "c" + body: Block [19-21]: "#]], + ); +} + +#[test] +fn no_qubits_one_classical() { + check( + parse, + "gate c1q0(a) {}", + &expect![[r#" + Stmt [0-15]: + annotations: + kind: Gate [0-15]: + ident: Ident [5-9] "c1q0" + parameters: + Ident [10-11] "a" + qubits: + body: Block [13-15]: "#]], + ); +} + +#[test] +fn no_qubits_two_classical() { + check( + parse, + "gate c2q0(a, b) {}", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: Gate [0-18]: + ident: Ident [5-9] "c2q0" + parameters: + Ident [10-11] "a" + Ident [13-14] "b" + qubits: + body: Block [16-18]: "#]], + ); +} + +#[test] +fn no_qubits_three_classical() { + check( + parse, + "gate c3q0(a, b, c) {}", + &expect![[r#" + Stmt [0-21]: + annotations: + kind: Gate [0-21]: + ident: Ident [5-9] "c3q0" + parameters: + Ident [10-11] "a" + Ident [13-14] "b" + Ident [16-17] "c" + qubits: + body: Block [19-21]: "#]], + ); +} + +#[test] +fn one_qubit_one_classical() { + check( + parse, + "gate c1q1(a) b {}", + &expect![[r#" + Stmt [0-17]: + annotations: + kind: Gate [0-17]: + ident: Ident [5-9] "c1q1" + parameters: + Ident [10-11] "a" + qubits: + Ident [13-14] "b" + body: Block [15-17]: "#]], + ); +} + +#[test] +fn two_qubits_two_classical() { + check( + parse, + "gate c2q2(a, b) c, d {}", + &expect![[r#" + Stmt [0-23]: + annotations: + kind: Gate [0-23]: + ident: Ident [5-9] "c2q2" + parameters: + Ident [10-11] "a" + Ident [13-14] "b" + qubits: + Ident [16-17] "c" + Ident [19-20] "d" + body: Block [21-23]: "#]], + ); +} + +#[test] +fn two_qubits_two_classical_with_body() { + check( + parse, + "gate c2q2(a, b) c, d { float[32] x = a - b; }", + &expect![[r#" + Stmt [0-45]: + annotations: + kind: Gate [0-45]: + ident: Ident [5-9] "c2q2" + parameters: + Ident [10-11] "a" + Ident [13-14] "b" + qubits: + Ident [16-17] "c" + Ident [19-20] "d" + body: Block [21-45]: + Stmt [23-43]: + annotations: + kind: ClassicalDeclarationStmt [23-43]: + type: ScalarType [23-32]: FloatType [23-32]: + size: Expr [29-31]: Lit: Int(32) + ident: Ident [33-34] "x" + init_expr: Expr [37-42]: BinaryOpExpr: + op: Sub + lhs: Expr [37-38]: Ident [37-38] "a" + rhs: Expr [41-42]: Ident [41-42] "b""#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/gphase.rs b/compiler/qsc_qasm/src/parser/stmt/tests/gphase.rs new file mode 100644 index 0000000000..4c589d366f --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/gphase.rs @@ -0,0 +1,209 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn gphase() { + check( + parse, + "gphase(pi / 2);", + &expect![[r#" + Stmt [0-15]: + annotations: + kind: GPhase [0-15]: + gphase_token_span: [0-6] + modifiers: + args: + Expr [7-13]: BinaryOpExpr: + op: Div + lhs: Expr [7-9]: Ident [7-9] "pi" + rhs: Expr [12-13]: Lit: Int(2) + duration: + qubits: "#]], + ); +} + +#[test] +fn gphase_qubit_ident() { + check( + parse, + "gphase(a) q0;", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: GPhase [0-13]: + gphase_token_span: [0-6] + modifiers: + args: + Expr [7-8]: Ident [7-8] "a" + duration: + qubits: + GateOperand [10-12]: + kind: IndexedIdent [10-12]: + name: Ident [10-12] "q0" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn gphase_qubit_register() { + check( + parse, + "gphase(a) q[2];", + &expect![[r#" + Stmt [0-15]: + annotations: + kind: GPhase [0-15]: + gphase_token_span: [0-6] + modifiers: + args: + Expr [7-8]: Ident [7-8] "a" + duration: + qubits: + GateOperand [10-14]: + kind: IndexedIdent [10-14]: + name: Ident [10-11] "q" + index_span: [11-14] + indices: + IndexSet [12-13]: + values: + Expr [12-13]: Lit: Int(2)"#]], + ); +} + +#[test] +fn gphase_multiple_qubits() { + check( + parse, + "gphase(a) q0, q[4];", + &expect![[r#" + Stmt [0-19]: + annotations: + kind: GPhase [0-19]: + gphase_token_span: [0-6] + modifiers: + args: + Expr [7-8]: Ident [7-8] "a" + duration: + qubits: + GateOperand [10-12]: + kind: IndexedIdent [10-12]: + name: Ident [10-12] "q0" + index_span: [0-0] + indices: + GateOperand [14-18]: + kind: IndexedIdent [14-18]: + name: Ident [14-15] "q" + index_span: [15-18] + indices: + IndexSet [16-17]: + values: + Expr [16-17]: Lit: Int(4)"#]], + ); +} + +#[test] +fn gphase_no_arguments() { + check( + parse, + "gphase;", + &expect![[r#" + Stmt [0-7]: + annotations: + kind: GPhase [0-7]: + gphase_token_span: [0-6] + modifiers: + args: + duration: + qubits: + + [ + Error( + GPhaseInvalidArguments( + Span { + lo: 6, + hi: 6, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn gphase_with_parameters() { + check( + parse, + "gphase(pi / 2);", + &expect![[r#" + Stmt [0-15]: + annotations: + kind: GPhase [0-15]: + gphase_token_span: [0-6] + modifiers: + args: + Expr [7-13]: BinaryOpExpr: + op: Div + lhs: Expr [7-9]: Ident [7-9] "pi" + rhs: Expr [12-13]: Lit: Int(2) + duration: + qubits: "#]], + ); +} + +#[test] +fn gphase_inv_modifier() { + check( + parse, + "inv @ gphase(a);", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: GPhase [0-16]: + gphase_token_span: [6-12] + modifiers: + QuantumGateModifier [0-5]: + modifier_keyword_span: [0-3] + kind: Inv + args: + Expr [13-14]: Ident [13-14] "a" + duration: + qubits: "#]], + ); +} + +#[test] +fn gphase_ctrl_inv_modifiers() { + check( + parse, + "ctrl @ inv @ gphase(pi / 2) q0;", + &expect![[r#" + Stmt [0-31]: + annotations: + kind: GPhase [0-31]: + gphase_token_span: [13-19] + modifiers: + QuantumGateModifier [0-6]: + modifier_keyword_span: [0-4] + kind: Ctrl None + QuantumGateModifier [7-12]: + modifier_keyword_span: [7-10] + kind: Inv + args: + Expr [20-26]: BinaryOpExpr: + op: Div + lhs: Expr [20-22]: Ident [20-22] "pi" + rhs: Expr [25-26]: Lit: Int(2) + duration: + qubits: + GateOperand [28-30]: + kind: IndexedIdent [28-30]: + name: Ident [28-30] "q0" + index_span: [0-0] + indices: "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/if_stmt.rs b/compiler/qsc_qasm/src/parser/stmt/tests/if_stmt.rs new file mode 100644 index 0000000000..b2d1bd177b --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/if_stmt.rs @@ -0,0 +1,359 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn simple_if_stmt() { + check( + parse, + " + if (x == y) { + a = 0; + } else { + a = 1; + } + ", + &expect![[r#" + Stmt [5-67]: + annotations: + kind: IfStmt [5-67]: + condition: Expr [9-15]: BinaryOpExpr: + op: Eq + lhs: Expr [9-10]: Ident [9-10] "x" + rhs: Expr [14-15]: Ident [14-15] "y" + if_body: Stmt [17-39]: + annotations: + kind: Block [17-39]: + Stmt [27-33]: + annotations: + kind: AssignStmt [27-33]: + lhs: IndexedIdent [27-28]: + name: Ident [27-28] "a" + index_span: [0-0] + indices: + rhs: Expr [31-32]: Lit: Int(0) + else_body: Stmt [45-67]: + annotations: + kind: Block [45-67]: + Stmt [55-61]: + annotations: + kind: AssignStmt [55-61]: + lhs: IndexedIdent [55-56]: + name: Ident [55-56] "a" + index_span: [0-0] + indices: + rhs: Expr [59-60]: Lit: Int(1)"#]], + ); +} + +#[test] +fn if_stmt_missing_else() { + check( + parse, + " + if (x == y) { + a = 0; + } + ", + &expect![[r#" + Stmt [5-39]: + annotations: + kind: IfStmt [5-39]: + condition: Expr [9-15]: BinaryOpExpr: + op: Eq + lhs: Expr [9-10]: Ident [9-10] "x" + rhs: Expr [14-15]: Ident [14-15] "y" + if_body: Stmt [17-39]: + annotations: + kind: Block [17-39]: + Stmt [27-33]: + annotations: + kind: AssignStmt [27-33]: + lhs: IndexedIdent [27-28]: + name: Ident [27-28] "a" + index_span: [0-0] + indices: + rhs: Expr [31-32]: Lit: Int(0) + else_body: "#]], + ); +} + +#[test] +fn nested_if_stmts() { + check( + parse, + " + if (x == y) { + if (x1 == y1) { + a = 0; + } else { + a = 1; + } + } else { + if (x2 == y2) { + a = 2; + } else { + a = 3; + } + } + ", + &expect![[r#" + Stmt [5-215]: + annotations: + kind: IfStmt [5-215]: + condition: Expr [9-15]: BinaryOpExpr: + op: Eq + lhs: Expr [9-10]: Ident [9-10] "x" + rhs: Expr [14-15]: Ident [14-15] "y" + if_body: Stmt [17-113]: + annotations: + kind: Block [17-113]: + Stmt [27-107]: + annotations: + kind: IfStmt [27-107]: + condition: Expr [31-39]: BinaryOpExpr: + op: Eq + lhs: Expr [31-33]: Ident [31-33] "x1" + rhs: Expr [37-39]: Ident [37-39] "y1" + if_body: Stmt [41-71]: + annotations: + kind: Block [41-71]: + Stmt [55-61]: + annotations: + kind: AssignStmt [55-61]: + lhs: IndexedIdent [55-56]: + name: Ident [55-56] "a" + index_span: [0-0] + indices: + rhs: Expr [59-60]: Lit: Int(0) + else_body: Stmt [77-107]: + annotations: + kind: Block [77-107]: + Stmt [91-97]: + annotations: + kind: AssignStmt [91-97]: + lhs: IndexedIdent [91-92]: + name: Ident [91-92] "a" + index_span: [0-0] + indices: + rhs: Expr [95-96]: Lit: Int(1) + else_body: Stmt [119-215]: + annotations: + kind: Block [119-215]: + Stmt [129-209]: + annotations: + kind: IfStmt [129-209]: + condition: Expr [133-141]: BinaryOpExpr: + op: Eq + lhs: Expr [133-135]: Ident [133-135] "x2" + rhs: Expr [139-141]: Ident [139-141] "y2" + if_body: Stmt [143-173]: + annotations: + kind: Block [143-173]: + Stmt [157-163]: + annotations: + kind: AssignStmt [157-163]: + lhs: IndexedIdent [157-158]: + name: Ident [157-158] "a" + index_span: [0-0] + indices: + rhs: Expr [161-162]: Lit: Int(2) + else_body: Stmt [179-209]: + annotations: + kind: Block [179-209]: + Stmt [193-199]: + annotations: + kind: AssignStmt [193-199]: + lhs: IndexedIdent [193-194]: + name: Ident [193-194] "a" + index_span: [0-0] + indices: + rhs: Expr [197-198]: Lit: Int(3)"#]], + ); +} + +#[test] +fn single_stmt_if_stmt() { + check( + parse, + "if (x) z q;", + &expect![[r#" + Stmt [0-11]: + annotations: + kind: IfStmt [0-11]: + condition: Expr [4-5]: Ident [4-5] "x" + if_body: Stmt [7-11]: + annotations: + kind: GateCall [7-11]: + modifiers: + name: Ident [7-8] "z" + args: + duration: + qubits: + GateOperand [9-10]: + kind: IndexedIdent [9-10]: + name: Ident [9-10] "q" + index_span: [0-0] + indices: + else_body: "#]], + ); +} + +#[test] +fn annotations_in_single_stmt_if_stmt() { + check( + parse, + " + if (x) + @foo + @bar + x = 5;", + &expect![[r#" + Stmt [5-52]: + annotations: + kind: IfStmt [5-52]: + condition: Expr [9-10]: Ident [9-10] "x" + if_body: Stmt [20-52]: + annotations: + Annotation [20-24]: + identifier: "foo" + value: + Annotation [33-37]: + identifier: "bar" + value: + kind: AssignStmt [46-52]: + lhs: IndexedIdent [46-47]: + name: Ident [46-47] "x" + index_span: [0-0] + indices: + rhs: Expr [50-51]: Lit: Int(5) + else_body: "#]], + ); +} + +#[test] +fn nested_single_stmt_if_stmt() { + check( + parse, + "if (x) if (y) z q;", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: IfStmt [0-18]: + condition: Expr [4-5]: Ident [4-5] "x" + if_body: Stmt [7-18]: + annotations: + kind: IfStmt [7-18]: + condition: Expr [11-12]: Ident [11-12] "y" + if_body: Stmt [14-18]: + annotations: + kind: GateCall [14-18]: + modifiers: + name: Ident [14-15] "z" + args: + duration: + qubits: + GateOperand [16-17]: + kind: IndexedIdent [16-17]: + name: Ident [16-17] "q" + index_span: [0-0] + indices: + else_body: + else_body: "#]], + ); +} + +#[test] +fn nested_single_stmt_if_else_stmt() { + check( + parse, + "if (x) if (y) z q; else if (a) if (b) h q;", + &expect![[r#" + Stmt [0-42]: + annotations: + kind: IfStmt [0-42]: + condition: Expr [4-5]: Ident [4-5] "x" + if_body: Stmt [7-42]: + annotations: + kind: IfStmt [7-42]: + condition: Expr [11-12]: Ident [11-12] "y" + if_body: Stmt [14-18]: + annotations: + kind: GateCall [14-18]: + modifiers: + name: Ident [14-15] "z" + args: + duration: + qubits: + GateOperand [16-17]: + kind: IndexedIdent [16-17]: + name: Ident [16-17] "q" + index_span: [0-0] + indices: + else_body: Stmt [24-42]: + annotations: + kind: IfStmt [24-42]: + condition: Expr [28-29]: Ident [28-29] "a" + if_body: Stmt [31-42]: + annotations: + kind: IfStmt [31-42]: + condition: Expr [35-36]: Ident [35-36] "b" + if_body: Stmt [38-42]: + annotations: + kind: GateCall [38-42]: + modifiers: + name: Ident [38-39] "h" + args: + duration: + qubits: + GateOperand [40-41]: + kind: IndexedIdent [40-41]: + name: Ident [40-41] "q" + index_span: [0-0] + indices: + else_body: + else_body: + else_body: "#]], + ); +} + +#[test] +fn single_stmt_if_stmt_else_stmt() { + check( + parse, + "if (x) z q; else x q;", + &expect![[r#" + Stmt [0-21]: + annotations: + kind: IfStmt [0-21]: + condition: Expr [4-5]: Ident [4-5] "x" + if_body: Stmt [7-11]: + annotations: + kind: GateCall [7-11]: + modifiers: + name: Ident [7-8] "z" + args: + duration: + qubits: + GateOperand [9-10]: + kind: IndexedIdent [9-10]: + name: Ident [9-10] "q" + index_span: [0-0] + indices: + else_body: Stmt [17-21]: + annotations: + kind: GateCall [17-21]: + modifiers: + name: Ident [17-18] "x" + args: + duration: + qubits: + GateOperand [19-20]: + kind: IndexedIdent [19-20]: + name: Ident [19-20] "q" + index_span: [0-0] + indices: "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/include.rs b/compiler/qsc_qasm/src/parser/stmt/tests/include.rs new file mode 100644 index 0000000000..6acdd8c0b7 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/include.rs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn include_with_no_literal() { + check( + parse, + "include;", + &expect![[r#" + Error( + Rule( + "string literal", + Semicolon, + Span { + lo: 7, + hi: 8, + }, + ), + ) + "#]], + ); +} + +#[test] +fn include_with_non_string_literal() { + check( + parse, + "include 5;", + &expect![[r#" + Error( + Rule( + "string literal", + Literal( + Integer( + Decimal, + ), + ), + Span { + lo: 8, + hi: 9, + }, + ), + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts.rs b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts.rs new file mode 100644 index 0000000000..deff9b0e90 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts.rs @@ -0,0 +1,11 @@ +mod branch; +mod cal; +mod constant; +mod decl; +mod gate_calls; +mod headers; +mod io; +mod loops; +mod measure; +mod switch; +mod tokens; diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/branch.rs b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/branch.rs new file mode 100644 index 0000000000..59d12cb6dc --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/branch.rs @@ -0,0 +1,219 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn if_condition_missing_parens() { + check( + parse, + "if true 3;", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Keyword( + True, + ), + Span { + lo: 3, + hi: 7, + }, + ), + ) + "#]], + ); +} + +#[test] +fn decl_in_if_condition() { + check( + parse, + "if (int[8] myvar = 1) { x $0; }", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Identifier, + Span { + lo: 11, + hi: 16, + }, + ), + ) + "#]], + ); +} + +#[test] +fn assignment_in_if_condition() { + check( + parse, + "if (x = 2) { 3; }", + &expect![[r#" + Stmt [0-17]: + annotations: + kind: IfStmt [0-17]: + condition: Expr [4-5]: Ident [4-5] "x" + if_body: Stmt [11-17]: + annotations: + kind: Block [11-17]: + Stmt [13-15]: + annotations: + kind: ExprStmt [13-15]: + expr: Expr [13-14]: Lit: Int(3) + else_body: + + [ + Error( + Token( + Close( + Paren, + ), + Eq, + Span { + lo: 6, + hi: 7, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn binary_op_assignment_in_if_condition() { + check( + parse, + "if (x += 2) { 3; }", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: IfStmt [0-18]: + condition: Expr [4-5]: Ident [4-5] "x" + if_body: Stmt [12-18]: + annotations: + kind: Block [12-18]: + Stmt [14-16]: + annotations: + kind: ExprStmt [14-16]: + expr: Expr [14-15]: Lit: Int(3) + else_body: + + [ + Error( + Token( + Close( + Paren, + ), + BinOpEq( + Plus, + ), + Span { + lo: 6, + hi: 8, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn empty_if_block_fails() { + check( + parse, + "if (true);", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: IfStmt [0-10]: + condition: Expr [4-8]: Lit: Bool(true) + if_body: Stmt [9-10]: + annotations: + kind: Err + else_body: + + [ + Error( + EmptyStatement( + Span { + lo: 9, + hi: 10, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn empty_if_block_else() { + check( + parse, + "if (true) else x $0;", + &expect![[r#" + Error( + Rule( + "statement", + Keyword( + Else, + ), + Span { + lo: 10, + hi: 14, + }, + ), + ) + "#]], + ); +} + +#[test] +fn empty_if_block_else_with_condition() { + check( + parse, + "if (true) else (false) x $0;", + &expect![[r#" + Error( + Rule( + "statement", + Keyword( + Else, + ), + Span { + lo: 10, + hi: 14, + }, + ), + ) + "#]], + ); +} + +#[test] +fn reset_in_if_condition() { + check( + parse, + "if (reset $0) { x $1; }", + &expect![[r#" + Error( + Rule( + "expression", + Keyword( + Reset, + ), + Span { + lo: 4, + hi: 9, + }, + ), + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/cal.rs b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/cal.rs new file mode 100644 index 0000000000..1d50f9d05d --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/cal.rs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn multiple_defcalgrammar_in_same_stmt() { + check( + parse, + r#"defcalgrammar "openpulse" defcalgrammar "openpulse";"#, + &expect![[r#" + Stmt [0-25]: + annotations: + kind: CalibrationGrammarStmt [0-25]: + name: openpulse + + [ + Error( + Token( + Semicolon, + Keyword( + DefCalGrammar, + ), + Span { + lo: 26, + hi: 39, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn defcalgrammar_with_wrong_literal_kind() { + check( + parse, + "defcalgrammar 3;", + &expect![[r#" + Error( + Rule( + "string literal", + Literal( + Integer( + Decimal, + ), + ), + Span { + lo: 14, + hi: 15, + }, + ), + ) + "#]], + ); +} + +#[test] +fn defcal_bad_signature() { + check( + parse, + "defcal x $0 -> int[8] -> int[8] {}", + &expect![[r#" + Stmt [0-34]: + annotations: + kind: DefCalStmt [0-34]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/constant.rs b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/constant.rs new file mode 100644 index 0000000000..571e8341ba --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/constant.rs @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn const_decl_missing_type_and_init() { + check( + parse, + "const myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Identifier, + Span { + lo: 6, + hi: 11, + }, + ), + ) + "#]], + ); +} + +#[test] +fn const_decl_eq_missing_type_and_init() { + check( + parse, + "const myvar = ;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Identifier, + Span { + lo: 6, + hi: 11, + }, + ), + ) + "#]], + ); +} + +#[test] +fn const_decl_missing_type() { + check( + parse, + "const myvar = 8.0;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Identifier, + Span { + lo: 6, + hi: 11, + }, + ), + ) + "#]], + ); +} + +#[test] +fn invalid_input() { + check( + parse, + "input const myvar = 8;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Keyword( + Const, + ), + Span { + lo: 6, + hi: 11, + }, + ), + ) + "#]], + ); +} + +#[test] +fn invalid_output() { + check( + parse, + "output const myvar = 8;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Keyword( + Const, + ), + Span { + lo: 7, + hi: 12, + }, + ), + ) + "#]], + ); +} + +#[test] +fn invalid_const_input() { + check( + parse, + "const input myvar = 8;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Keyword( + Input, + ), + Span { + lo: 6, + hi: 11, + }, + ), + ) + "#]], + ); +} + +#[test] +fn invalid_const_output() { + check( + parse, + "const output myvar = 8;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Keyword( + Output, + ), + Span { + lo: 6, + hi: 12, + }, + ), + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/decl.rs b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/decl.rs new file mode 100644 index 0000000000..9732110701 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/decl.rs @@ -0,0 +1,996 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn missing_ident() { + check( + parse, + "float;", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Semicolon, + Span { + lo: 5, + hi: 6, + }, + ), + ) + "#]], + ); + check( + parse, + "uint[8];", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Semicolon, + Span { + lo: 7, + hi: 8, + }, + ), + ) + "#]], + ); + check( + parse, + "qreg[4];", + &expect![[r#" + Error( + Rule( + "identifier", + Open( + Bracket, + ), + Span { + lo: 4, + hi: 5, + }, + ), + ) + "#]], + ); + check( + parse, + "creg[4];", + &expect![[r#" + Error( + Rule( + "identifier", + Open( + Bracket, + ), + Span { + lo: 4, + hi: 5, + }, + ), + ) + "#]], + ); + check( + parse, + "complex[float[32]];", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Semicolon, + Span { + lo: 18, + hi: 19, + }, + ), + ) + "#]], + ); +} + +#[test] +#[allow(clippy::too_many_lines)] +fn incorrect_designators() { + check( + parse, + "int[8, 8] myvar;", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: ClassicalDeclarationStmt [0-16]: + type: ScalarType [0-9]: IntType [0-9]: + size: Expr [4-5]: Lit: Int(8) + ident: Ident [10-15] "myvar" + init_expr: + + [ + Error( + Token( + Close( + Bracket, + ), + Comma, + Span { + lo: 5, + hi: 6, + }, + ), + ), + ]"#]], + ); + check( + parse, + "uint[8, 8] myvar;", + &expect![[r#" + Stmt [0-17]: + annotations: + kind: ClassicalDeclarationStmt [0-17]: + type: ScalarType [0-10]: UIntType [0-10]: + size: Expr [5-6]: Lit: Int(8) + ident: Ident [11-16] "myvar" + init_expr: + + [ + Error( + Token( + Close( + Bracket, + ), + Comma, + Span { + lo: 6, + hi: 7, + }, + ), + ), + ]"#]], + ); + check( + parse, + "float[8, 8] myvar;", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: ClassicalDeclarationStmt [0-18]: + type: ScalarType [0-11]: FloatType [0-11]: + size: Expr [6-7]: Lit: Int(8) + ident: Ident [12-17] "myvar" + init_expr: + + [ + Error( + Token( + Close( + Bracket, + ), + Comma, + Span { + lo: 7, + hi: 8, + }, + ), + ), + ]"#]], + ); + check( + parse, + "angle[8, 8] myvar;", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: ClassicalDeclarationStmt [0-18]: + type: ScalarType [0-11]: AngleType [0-11]: + size: Expr [6-7]: Lit: Int(8) + ident: Ident [12-17] "myvar" + init_expr: + + [ + Error( + Token( + Close( + Bracket, + ), + Comma, + Span { + lo: 7, + hi: 8, + }, + ), + ), + ]"#]], + ); + check( + parse, + "bool[4] myvar;", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Open( + Bracket, + ), + Span { + lo: 4, + hi: 5, + }, + ), + ) + "#]], + ); + check( + parse, + "bool[4, 4] myvar;", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Open( + Bracket, + ), + Span { + lo: 4, + hi: 5, + }, + ), + ) + "#]], + ); + check( + parse, + "bit[4, 4] myvar;", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: ClassicalDeclarationStmt [0-16]: + type: ScalarType [0-9]: BitType [0-9]: + size: Expr [4-5]: Lit: Int(4) + ident: Ident [10-15] "myvar" + init_expr: + + [ + Error( + Token( + Close( + Bracket, + ), + Comma, + Span { + lo: 5, + hi: 6, + }, + ), + ), + ]"#]], + ); + check( + parse, + "creg[2] myvar;", + &expect![[r#" + Error( + Rule( + "identifier", + Open( + Bracket, + ), + Span { + lo: 4, + hi: 5, + }, + ), + ) + "#]], + ); + check( + parse, + "creg[2, 2] myvar;", + &expect![[r#" + Error( + Rule( + "identifier", + Open( + Bracket, + ), + Span { + lo: 4, + hi: 5, + }, + ), + ) + "#]], + ); + check( + parse, + "qreg[2] myvar;", + &expect![[r#" + Error( + Rule( + "identifier", + Open( + Bracket, + ), + Span { + lo: 4, + hi: 5, + }, + ), + ) + "#]], + ); + check( + parse, + "qreg[2, 2] myvar;", + &expect![[r#" + Error( + Rule( + "identifier", + Open( + Bracket, + ), + Span { + lo: 4, + hi: 5, + }, + ), + ) + "#]], + ); + check( + parse, + "complex[32] myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Literal( + Integer( + Decimal, + ), + ), + Span { + lo: 8, + hi: 10, + }, + ), + ) + "#]], + ); + check( + parse, + "complex[mytype] myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Identifier, + Span { + lo: 8, + hi: 14, + }, + ), + ) + "#]], + ); + check( + parse, + "complex[float[32], float[32]] myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Comma, + Span { + lo: 17, + hi: 18, + }, + ), + ) + "#]], + ); + check( + parse, + "complex[qreg] myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Keyword( + QReg, + ), + Span { + lo: 8, + hi: 12, + }, + ), + ) + "#]], + ); + check( + parse, + "complex[creg] myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Keyword( + CReg, + ), + Span { + lo: 8, + hi: 12, + }, + ), + ) + "#]], + ); + check( + parse, + "complex[qreg[8]] myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Keyword( + QReg, + ), + Span { + lo: 8, + hi: 12, + }, + ), + ) + "#]], + ); + check( + parse, + "complex[creg[8]] myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Keyword( + CReg, + ), + Span { + lo: 8, + hi: 12, + }, + ), + ) + "#]], + ); +} + +#[test] +fn bad_array_specifiers() { + check( + parse, + "array myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Identifier, + Span { + lo: 6, + hi: 11, + }, + ), + ) + "#]], + ); + check( + parse, + "array[8] myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Literal( + Integer( + Decimal, + ), + ), + Span { + lo: 6, + hi: 7, + }, + ), + ) + "#]], + ); + check( + parse, + "array[not_a_type, 4] myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Identifier, + Span { + lo: 6, + hi: 16, + }, + ), + ) + "#]], + ); + check( + parse, + "array[int[8], int[8], 2] myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Comma, + Span { + lo: 20, + hi: 21, + }, + ), + ) + "#]], + ); +} + +#[test] +fn invalid_identifiers() { + check( + parse, + "int[8] int;", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Type( + Int, + ), + Span { + lo: 7, + hi: 10, + }, + ), + ) + "#]], + ); + check( + parse, + "int[8] def;", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Keyword( + Def, + ), + Span { + lo: 7, + hi: 10, + }, + ), + ) + "#]], + ); + check( + parse, + "int[8] 0;", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Literal( + Integer( + Decimal, + ), + ), + Span { + lo: 7, + hi: 8, + }, + ), + ) + "#]], + ); + check( + parse, + "int[8] input;", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Keyword( + Input, + ), + Span { + lo: 7, + hi: 12, + }, + ), + ) + "#]], + ); +} + +#[test] +fn bad_assignments() { + check( + parse, + "int[8] myvar = end;", + &expect![[r#" + Error( + Token( + Open( + Brace, + ), + Keyword( + End, + ), + Span { + lo: 15, + hi: 18, + }, + ), + ) + "#]], + ); + check( + parse, + "int[8] myvar =;", + &expect![[r#" + Error( + Token( + Open( + Brace, + ), + Semicolon, + Span { + lo: 14, + hi: 15, + }, + ), + ) + "#]], + ); + check( + parse, + "float[32] myvar_f = int[32] myvar_i = 2;", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Identifier, + Span { + lo: 28, + hi: 35, + }, + ), + ) + "#]], + ); +} + +#[test] +fn array_initialiser_uses_braces() { + check( + parse, + "array[uint[8], 4] myvar = [4, 5, 6, 7];", + &expect![[r#" + Error( + Token( + Open( + Brace, + ), + Open( + Bracket, + ), + Span { + lo: 26, + hi: 27, + }, + ), + ) + "#]], + ); +} + +#[test] +fn cant_use_arithmetic_on_the_entire_initialiser() { + check( + parse, + "array[uint[8], 4] myvar = 2 * {1, 2, 3, 4};", + &expect![[r#" + Error( + Rule( + "expression", + Open( + Brace, + ), + Span { + lo: 30, + hi: 31, + }, + ), + ) + "#]], + ); +} + +#[test] +fn backed_arrays_cant_use_dim() { + check( + parse, + "array[uint[8], #dim=2] myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Keyword( + Dim, + ), + Span { + lo: 15, + hi: 19, + }, + ), + ) + "#]], + ); +} + +#[test] +fn cant_have_more_than_one_type_specification() { + check( + parse, + "array[int[8], int[8]] myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Close( + Bracket, + ), + Span { + lo: 20, + hi: 21, + }, + ), + ) + "#]], + ); +} + +#[test] +#[allow(clippy::too_many_lines)] +fn incorrect_orders() { + check( + parse, + "myvar: int[8];", + &expect![[r#" + Stmt [0-5]: + annotations: + kind: ExprStmt [0-5]: + expr: Expr [0-5]: Ident [0-5] "myvar" + + [ + Error( + Token( + Semicolon, + Colon, + Span { + lo: 5, + hi: 6, + }, + ), + ), + ]"#]], + ); + check( + parse, + "myvar int[8];", + &expect![[r#" + Stmt [0-5]: + annotations: + kind: ExprStmt [0-5]: + expr: Expr [0-5]: Ident [0-5] "myvar" + + [ + Error( + Token( + Semicolon, + Type( + Int, + ), + Span { + lo: 6, + hi: 9, + }, + ), + ), + ]"#]], + ); + check( + parse, + "int myvar[8];", + &expect![[r#" + Stmt [0-9]: + annotations: + kind: ClassicalDeclarationStmt [0-9]: + type: ScalarType [0-3]: IntType [0-3]: + size: + ident: Ident [4-9] "myvar" + init_expr: + + [ + Error( + Token( + Semicolon, + Open( + Bracket, + ), + Span { + lo: 9, + hi: 10, + }, + ), + ), + ]"#]], + ); + check( + parse, + "uint myvar[8];", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: ClassicalDeclarationStmt [0-10]: + type: ScalarType [0-4]: UIntType [0-4]: + size: + ident: Ident [5-10] "myvar" + init_expr: + + [ + Error( + Token( + Semicolon, + Open( + Bracket, + ), + Span { + lo: 10, + hi: 11, + }, + ), + ), + ]"#]], + ); + check( + parse, + "float myvar[32];", + &expect![[r#" + Stmt [0-11]: + annotations: + kind: ClassicalDeclarationStmt [0-11]: + type: ScalarType [0-5]: FloatType [0-5]: + size: + ident: Ident [6-11] "myvar" + init_expr: + + [ + Error( + Token( + Semicolon, + Open( + Bracket, + ), + Span { + lo: 11, + hi: 12, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn compound_assigments() { + check( + parse, + "int[8] myvar1, myvar2;", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: ClassicalDeclarationStmt [0-13]: + type: ScalarType [0-6]: IntType [0-6]: + size: Expr [4-5]: Lit: Int(8) + ident: Ident [7-13] "myvar1" + init_expr: + + [ + Error( + Token( + Semicolon, + Comma, + Span { + lo: 13, + hi: 14, + }, + ), + ), + ]"#]], + ); + check( + parse, + "int[8] myvari, float[32] myvarf;", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: ClassicalDeclarationStmt [0-13]: + type: ScalarType [0-6]: IntType [0-6]: + size: Expr [4-5]: Lit: Int(8) + ident: Ident [7-13] "myvari" + init_expr: + + [ + Error( + Token( + Semicolon, + Comma, + Span { + lo: 13, + hi: 14, + }, + ), + ), + ]"#]], + ); + check( + parse, + "int[8] myvari float[32] myvarf;", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: ClassicalDeclarationStmt [0-13]: + type: ScalarType [0-6]: IntType [0-6]: + size: Expr [4-5]: Lit: Int(8) + ident: Ident [7-13] "myvari" + init_expr: + + [ + Error( + Token( + Semicolon, + Type( + Float, + ), + Span { + lo: 14, + hi: 19, + }, + ), + ), + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/gate_calls.rs b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/gate_calls.rs new file mode 100644 index 0000000000..e788385359 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/gate_calls.rs @@ -0,0 +1,230 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn u_gate_with_two_args() { + check( + parse, + "U (1)(2) $0;", + &expect![[r#" + Error( + Convert( + "identifier", + "", + Span { + lo: 0, + hi: 5, + }, + ), + ) + "#]], + ); +} + +#[test] +fn invalid_modifier() { + check( + parse, + "notmodifier @ x $0;", + &expect![[r#" + Stmt [0-11]: + annotations: + kind: ExprStmt [0-11]: + expr: Expr [0-11]: Ident [0-11] "notmodifier" + + [ + Error( + Token( + Semicolon, + At, + Span { + lo: 12, + hi: 13, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn pow_without_arg() { + check( + parse, + "pow @ x $0;", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + At, + Span { + lo: 4, + hi: 5, + }, + ), + ) + "#]], + ); +} + +#[test] +fn pow_with_two_args() { + check( + parse, + "pow(2, 3) @ x $0;", + &expect![[r#" + Stmt [0-17]: + annotations: + kind: GateCall [0-17]: + modifiers: + QuantumGateModifier [0-11]: + modifier_keyword_span: [0-3] + kind: Pow Expr [4-5]: Lit: Int(2) + name: Ident [12-13] "x" + args: + duration: + qubits: + GateOperand [14-16]: + kind: HardwareQubit [14-16]: 0 + + [ + Error( + Token( + Close( + Paren, + ), + Comma, + Span { + lo: 5, + hi: 6, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn ctrl_with_two_args() { + check( + parse, + "ctrl(2, 3) @ x $0, $1;", + &expect![[r#" + Stmt [0-22]: + annotations: + kind: GateCall [0-22]: + modifiers: + QuantumGateModifier [0-12]: + modifier_keyword_span: [0-4] + kind: Ctrl Some(Expr { span: Span { lo: 5, hi: 6 }, kind: Lit(Lit { span: Span { lo: 5, hi: 6 }, kind: Int(2) }) }) + name: Ident [13-14] "x" + args: + duration: + qubits: + GateOperand [15-17]: + kind: HardwareQubit [15-17]: 0 + GateOperand [19-21]: + kind: HardwareQubit [19-21]: 1 + + [ + Error( + Token( + Close( + Paren, + ), + Comma, + Span { + lo: 6, + hi: 7, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn negctrl_with_two_args() { + check( + parse, + "negctrl(2, 3) @ x $0, $1;", + &expect![[r#" + Stmt [0-25]: + annotations: + kind: GateCall [0-25]: + modifiers: + QuantumGateModifier [0-15]: + modifier_keyword_span: [0-7] + kind: NegCtrl Some(Expr { span: Span { lo: 8, hi: 9 }, kind: Lit(Lit { span: Span { lo: 8, hi: 9 }, kind: Int(2) }) }) + name: Ident [16-17] "x" + args: + duration: + qubits: + GateOperand [18-20]: + kind: HardwareQubit [18-20]: 0 + GateOperand [22-24]: + kind: HardwareQubit [22-24]: 1 + + [ + Error( + Token( + Close( + Paren, + ), + Comma, + Span { + lo: 9, + hi: 10, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn inv_with_arg() { + check( + parse, + "inv(1) @ ctrl @ x $0, $1;", + &expect![[r#" + Stmt [0-25]: + annotations: + kind: GateCall [0-25]: + modifiers: + QuantumGateModifier [0-8]: + modifier_keyword_span: [0-3] + kind: Inv + QuantumGateModifier [9-15]: + modifier_keyword_span: [9-13] + kind: Ctrl None + name: Ident [16-17] "x" + args: + duration: + qubits: + GateOperand [18-20]: + kind: HardwareQubit [18-20]: 0 + GateOperand [22-24]: + kind: HardwareQubit [22-24]: 1 + + [ + Error( + Token( + At, + Open( + Paren, + ), + Span { + lo: 3, + hi: 4, + }, + ), + ), + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/headers.rs b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/headers.rs new file mode 100644 index 0000000000..8fa9c878e0 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/headers.rs @@ -0,0 +1,231 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn invalid_version_type() { + check( + parse, + "OPENQASM int;", + &expect![[r#" + Error( + Rule( + "statement", + Keyword( + OpenQASM, + ), + Span { + lo: 0, + hi: 8, + }, + ), + ) + "#]], + ); +} + +#[test] +fn invalid_version_literal() { + check( + parse, + "OPENQASM 'hello, world';", + &expect![[r#" + Error( + Rule( + "statement", + Keyword( + OpenQASM, + ), + Span { + lo: 0, + hi: 8, + }, + ), + ) + "#]], + ); +} + +#[test] +fn invalid_version_missing_dot() { + check( + parse, + "OPENQASM 3 3;", + &expect![[r#" + Error( + Rule( + "statement", + Keyword( + OpenQASM, + ), + Span { + lo: 0, + hi: 8, + }, + ), + ) + "#]], + ); +} + +#[test] +fn invalid_version() { + check( + parse, + "OPENQASM 3.x;", + &expect![[r#" + Error( + Rule( + "statement", + Keyword( + OpenQASM, + ), + Span { + lo: 0, + hi: 8, + }, + ), + ) + "#]], + ); +} + +#[test] +fn include_int() { + check( + parse, + "include 3;", + &expect![[r#" + Error( + Rule( + "string literal", + Literal( + Integer( + Decimal, + ), + ), + Span { + lo: 8, + hi: 9, + }, + ), + ) + "#]], + ); +} + +#[test] +fn include_include() { + check( + parse, + "include include;", + &expect![[r#" + Error( + Rule( + "string literal", + Keyword( + Include, + ), + Span { + lo: 8, + hi: 15, + }, + ), + ) + + [ + Error( + Token( + Semicolon, + Keyword( + Include, + ), + Span { + lo: 8, + hi: 15, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn include_def() { + check( + parse, + "include def;", + &expect![[r#" + Error( + Rule( + "string literal", + Keyword( + Def, + ), + Span { + lo: 8, + hi: 11, + }, + ), + ) + + [ + Error( + Token( + Semicolon, + Keyword( + Def, + ), + Span { + lo: 8, + hi: 11, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn unclosed_string() { + check( + parse, + r#"include "hello;"#, + &expect![[r#" + Error( + Rule( + "string literal", + Eof, + Span { + lo: 15, + hi: 15, + }, + ), + ) + + [ + Error( + Lex( + UnterminatedString( + Span { + lo: 8, + hi: 8, + }, + ), + ), + ), + Error( + Token( + Semicolon, + Eof, + Span { + lo: 15, + hi: 15, + }, + ), + ), + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/io.rs b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/io.rs new file mode 100644 index 0000000000..a6ed43817a --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/io.rs @@ -0,0 +1,187 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn input_missing_ident() { + check( + parse, + "input int[8];", + &expect![[r#" + Error( + Rule( + "identifier", + Semicolon, + Span { + lo: 12, + hi: 13, + }, + ), + ) + "#]], + ); +} + +#[test] +fn output_missing_ident() { + check( + parse, + "output int[8];", + &expect![[r#" + Error( + Rule( + "identifier", + Semicolon, + Span { + lo: 13, + hi: 14, + }, + ), + ) + "#]], + ); +} + +#[test] +fn input_qreg_missing_ident() { + check( + parse, + "input qreg myvar[4];", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Keyword( + QReg, + ), + Span { + lo: 6, + hi: 10, + }, + ), + ) + "#]], + ); +} + +#[test] +fn output_qreg_missing_ident() { + check( + parse, + "output qreg myvar[4];", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Keyword( + QReg, + ), + Span { + lo: 7, + hi: 11, + }, + ), + ) + "#]], + ); +} + +#[test] +fn initialized_input() { + check( + parse, + "input int[8] myvar = 32;", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: IODeclaration [0-18]: + io_keyword: input + type: ScalarType [6-12]: IntType [6-12]: + size: Expr [10-11]: Lit: Int(8) + ident: Ident [13-18] "myvar" + + [ + Error( + Token( + Semicolon, + Eq, + Span { + lo: 19, + hi: 20, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn initialized_output() { + check( + parse, + "output int[8] myvar = 32;", + &expect![[r#" + Stmt [0-19]: + annotations: + kind: IODeclaration [0-19]: + io_keyword: output + type: ScalarType [7-13]: IntType [7-13]: + size: Expr [11-12]: Lit: Int(8) + ident: Ident [14-19] "myvar" + + [ + Error( + Token( + Semicolon, + Eq, + Span { + lo: 20, + hi: 21, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn input_missing_type() { + check( + parse, + "input myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Identifier, + Span { + lo: 6, + hi: 11, + }, + ), + ) + "#]], + ); +} + +#[test] +fn output_missing_type() { + check( + parse, + "output myvar;", + &expect![[r#" + Error( + Rule( + "scalar or array type", + Identifier, + Span { + lo: 7, + hi: 12, + }, + ), + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/loops.rs b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/loops.rs new file mode 100644 index 0000000000..5e775bc19f --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/loops.rs @@ -0,0 +1,298 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn for_missing_var_type() { + check( + parse, + "for myvar in { 1, 2, 3 };", + &expect![[r#" + Error( + Rule( + "scalar type", + Identifier, + Span { + lo: 4, + hi: 9, + }, + ), + ) + "#]], + ); +} + +#[test] +fn for_multiple_vars() { + check( + parse, + "for myvar1, myvar2 in { 1, 2, 3 } { x $0; }", + &expect![[r#" + Error( + Rule( + "scalar type", + Identifier, + Span { + lo: 4, + hi: 10, + }, + ), + ) + "#]], + ); +} + +#[test] +fn for_missing_var_type_and_invalid_collection() { + check( + parse, + "for myvar in { x $0; } { x $0; }", + &expect![[r#" + Error( + Rule( + "scalar type", + Identifier, + Span { + lo: 4, + hi: 9, + }, + ), + ) + "#]], + ); +} + +#[test] +fn for_missing_var_type_and_keyword_in_collection() { + check( + parse, + "for myvar in for { x $0; }", + &expect![[r#" + Error( + Rule( + "scalar type", + Identifier, + Span { + lo: 4, + hi: 9, + }, + ), + ) + "#]], + ); +} + +#[test] +fn for_bad_syntax() { + check( + parse, + "for myvar { x $0; }", + &expect![[r#" + Error( + Rule( + "scalar type", + Identifier, + Span { + lo: 4, + hi: 9, + }, + ), + ) + "#]], + ); +} + +#[test] +fn for_with_while_syntax() { + check( + parse, + "for (true) { x $0; }", + &expect![[r#" + Error( + Rule( + "scalar type", + Open( + Paren, + ), + Span { + lo: 4, + hi: 5, + }, + ), + ) + "#]], + ); +} + +#[test] +fn for_missing_var_and_collection() { + check( + parse, + "for { x $0; }", + &expect![[r#" + Error( + Rule( + "scalar type", + Open( + Brace, + ), + Span { + lo: 4, + hi: 5, + }, + ), + ) + "#]], + ); +} + +#[test] +fn for_invalid_var_name() { + check( + parse, + "for for in { 1, 2, 3 } { x $0; }", + &expect![[r#" + Error( + Rule( + "scalar type", + Keyword( + For, + ), + Span { + lo: 4, + hi: 7, + }, + ), + ) + "#]], + ); +} + +#[test] +fn for_missing_var() { + check( + parse, + "for in { 1, 2, 3 } { x $0; }", + &expect![[r#" + Error( + Rule( + "scalar type", + Keyword( + In, + ), + Span { + lo: 4, + hi: 6, + }, + ), + ) + "#]], + ); +} + +#[test] +fn while_missing_parens() { + check( + parse, + "while true { x $0; }", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Keyword( + True, + ), + Span { + lo: 6, + hi: 10, + }, + ), + ) + "#]], + ); +} + +#[test] +fn while_multi_condition() { + check( + parse, + "while (true) (true) { x $0; }", + &expect![[r#" + Stmt [0-19]: + annotations: + kind: WhileLoop [0-19]: + condition: Expr [7-11]: Lit: Bool(true) + body: Stmt [13-19]: + annotations: + kind: ExprStmt [13-19]: + expr: Expr [13-19]: Paren Expr [14-18]: Lit: Bool(true) + + [ + Error( + Token( + Semicolon, + Open( + Brace, + ), + Span { + lo: 20, + hi: 21, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn while_with_for_syntax() { + check( + parse, + "while x in { 1, 2, 3 } { x $0; }", + &expect![[r#" + Error( + Token( + Open( + Paren, + ), + Identifier, + Span { + lo: 6, + hi: 7, + }, + ), + ) + "#]], + ); +} + +#[test] +fn while_missing_body_fails() { + check( + parse, + "while (true);", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: WhileLoop [0-13]: + condition: Expr [7-11]: Lit: Bool(true) + body: Stmt [12-13]: + annotations: + kind: Err + + [ + Error( + EmptyStatement( + Span { + lo: 12, + hi: 13, + }, + ), + ), + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/measure.rs b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/measure.rs new file mode 100644 index 0000000000..58f77855a1 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/measure.rs @@ -0,0 +1,215 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn measure_multiple_qubits() { + check( + parse, + "measure $0, $1;", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: MeasureArrowStmt [0-10]: + measurement: MeasureExpr [0-10]: + operand: GateOperand [8-10]: + kind: HardwareQubit [8-10]: 0 + target: + + [ + Error( + Token( + Semicolon, + Comma, + Span { + lo: 10, + hi: 11, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn assign_measure_multiple_qubits() { + check( + parse, + "a[0:1] = measure $0, $1;", + &expect![[r#" + Stmt [0-19]: + annotations: + kind: AssignStmt [0-19]: + lhs: IndexedIdent [0-6]: + name: Ident [0-1] "a" + index_span: [1-6] + indices: + IndexSet [2-5]: + values: + RangeDefinition [2-5]: + start: Expr [2-3]: Lit: Int(0) + step: + end: Expr [4-5]: Lit: Int(1) + rhs: MeasureExpr [9-19]: + operand: GateOperand [17-19]: + kind: HardwareQubit [17-19]: 0 + + [ + Error( + Token( + Semicolon, + Comma, + Span { + lo: 19, + hi: 20, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn assign_arrow() { + check( + parse, + "a = measure $0 -> b;", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: AssignStmt [0-14]: + lhs: IndexedIdent [0-1]: + name: Ident [0-1] "a" + index_span: [0-0] + indices: + rhs: MeasureExpr [4-14]: + operand: GateOperand [12-14]: + kind: HardwareQubit [12-14]: 0 + + [ + Error( + Token( + Semicolon, + Arrow, + Span { + lo: 15, + hi: 17, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn initialized_creg() { + check( + parse, + "creg a[1] = measure $0;", + &expect![[r#" + Stmt [0-9]: + annotations: + kind: ClassicalDeclarationStmt [0-9]: + type: ScalarType [0-9]: BitType [0-9]: + size: Expr [7-8]: Lit: Int(1) + ident: Ident [5-6] "a" + init_expr: + + [ + Error( + Token( + Semicolon, + Eq, + Span { + lo: 10, + hi: 11, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn invalid_arrow_target() { + check( + parse, + "measure $0 -> creg a[1];", + &expect![[r#" + Error( + Rule( + "identifier", + Keyword( + CReg, + ), + Span { + lo: 14, + hi: 18, + }, + ), + ) + "#]], + ); + check( + parse, + "measure $0 -> bit[1] a;", + &expect![[r#" + Error( + Rule( + "identifier", + Type( + Bit, + ), + Span { + lo: 14, + hi: 17, + }, + ), + ) + "#]], + ); +} + +#[test] +fn measure_cant_be_used_in_sub_expressions() { + check( + parse, + "a = 2 * measure $0;", + &expect![[r#" + Error( + Rule( + "expression", + Keyword( + Measure, + ), + Span { + lo: 8, + hi: 15, + }, + ), + ) + "#]], + ); + check( + parse, + "a = (measure $0) + (measure $1);", + &expect![[r#" + Error( + Token( + Close( + Paren, + ), + Keyword( + Measure, + ), + Span { + lo: 5, + hi: 12, + }, + ), + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/switch.rs b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/switch.rs new file mode 100644 index 0000000000..17a0a7e9d9 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/switch.rs @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn missing_target() { + check( + parse, + "switch () {}", + &expect![[r#" + Error( + Rule( + "expression", + Close( + Paren, + ), + Span { + lo: 8, + hi: 9, + }, + ), + ) + "#]], + ); +} + +#[test] +fn missing_cases() { + check( + parse, + "switch (i) { x $0 }", + &expect![[r#" + Stmt [0-19]: + annotations: + kind: SwitchStmt [0-19]: + target: Expr [8-9]: Ident [8-9] "i" + cases: + default_case: + + [ + Error( + MissingSwitchCases( + Span { + lo: 13, + hi: 12, + }, + ), + ), + Error( + Token( + Close( + Brace, + ), + Identifier, + Span { + lo: 13, + hi: 14, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn missing_case_labels() { + check( + parse, + "switch (i) { case {} }", + &expect![[r#" + Stmt [0-22]: + annotations: + kind: SwitchStmt [0-22]: + target: Expr [8-9]: Ident [8-9] "i" + cases: + SwitchCase [13-20]: + labels: + block: Block [18-20]: + default_case: + + [ + Error( + MissingSwitchCaseLabels( + Span { + lo: 13, + hi: 17, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn invalid_label_sequence() { + check( + parse, + "switch (i) { case 1,, {} }", + &expect![[r#" + Stmt [0-26]: + annotations: + kind: SwitchStmt [0-26]: + target: Expr [8-9]: Ident [8-9] "i" + cases: + SwitchCase [13-24]: + labels: + Expr [18-19]: Lit: Int(1) + Expr [20-20]: Err + block: Block [22-24]: + default_case: + + [ + Error( + MissingSeqEntry( + Span { + lo: 20, + hi: 20, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn default_case_with_label() { + check( + parse, + "switch (i) { default 0 {} }", + &expect![[r#" + Error( + Token( + Open( + Brace, + ), + Literal( + Integer( + Decimal, + ), + ), + Span { + lo: 21, + hi: 22, + }, + ), + ) + + [ + Error( + MissingSwitchCases( + Span { + lo: 13, + hi: 12, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn bad_case_syntax() { + check( + parse, + "switch (i) { default, default {} }", + &expect![[r#" + Error( + Token( + Open( + Brace, + ), + Comma, + Span { + lo: 20, + hi: 21, + }, + ), + ) + + [ + Error( + MissingSwitchCases( + Span { + lo: 13, + hi: 12, + }, + ), + ), + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/tokens.rs b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/tokens.rs new file mode 100644 index 0000000000..3be5bbc5ba --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/invalid_stmts/tokens.rs @@ -0,0 +1,315 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +#[allow(clippy::too_many_lines)] +fn bad_tokens() { + check( + parse, + "#;", + &expect![[r#" + Stmt [1-2]: + annotations: + kind: Err + + [ + Error( + Lex( + Incomplete( + Ident, + Identifier, + Single( + Semi, + ), + Span { + lo: 1, + hi: 2, + }, + ), + ), + ), + Error( + EmptyStatement( + Span { + lo: 1, + hi: 2, + }, + ), + ), + ]"#]], + ); + + check( + parse, + "3x;", + &expect![[r#" + Error( + ExpectedItem( + Identifier, + Span { + lo: 0, + hi: 1, + }, + ), + ) + "#]], + ); + + check( + parse, + "x@x;", + &expect![[r#" + Stmt [0-1]: + annotations: + kind: ExprStmt [0-1]: + expr: Expr [0-1]: Ident [0-1] "x" + + [ + Error( + Token( + Semicolon, + At, + Span { + lo: 1, + hi: 2, + }, + ), + ), + ]"#]], + ); + + check( + parse, + "3.4.3;", + &expect![[r#" + Stmt [0-3]: + annotations: + kind: ExprStmt [0-3]: + expr: Expr [0-3]: Lit: Float(3.4) + + [ + Error( + Token( + Semicolon, + Literal( + Float, + ), + Span { + lo: 3, + hi: 5, + }, + ), + ), + ]"#]], + ); + + check( + parse, + "3.4e3e3;", + &expect![[r#" + Error( + ExpectedItem( + Identifier, + Span { + lo: 0, + hi: 5, + }, + ), + ) + "#]], + ); +} + +#[test] +#[allow(clippy::too_many_lines)] +fn bad_integer_literals() { + check( + parse, + "3_4_;", + &expect![[r#" + Stmt [4-5]: + annotations: + kind: Err + + [ + Error( + Lex( + Unknown( + '3', + Span { + lo: 0, + hi: 4, + }, + ), + ), + ), + Error( + EmptyStatement( + Span { + lo: 4, + hi: 5, + }, + ), + ), + ]"#]], + ); + + check( + parse, + "0b123;", + &expect![[r#" + Stmt [0-3]: + annotations: + kind: ExprStmt [0-3]: + expr: Expr [0-3]: Lit: Int(1) + + [ + Error( + Token( + Semicolon, + Literal( + Integer( + Decimal, + ), + ), + Span { + lo: 3, + hi: 5, + }, + ), + ), + ]"#]], + ); + + check( + parse, + "0B123;", + &expect![[r#" + Stmt [0-3]: + annotations: + kind: ExprStmt [0-3]: + expr: Expr [0-3]: Lit: Int(1) + + [ + Error( + Token( + Semicolon, + Literal( + Integer( + Decimal, + ), + ), + Span { + lo: 3, + hi: 5, + }, + ), + ), + ]"#]], + ); + + check( + parse, + "0o789;", + &expect![[r#" + Stmt [0-3]: + annotations: + kind: ExprStmt [0-3]: + expr: Expr [0-3]: Lit: Int(7) + + [ + Error( + Token( + Semicolon, + Literal( + Integer( + Decimal, + ), + ), + Span { + lo: 3, + hi: 5, + }, + ), + ), + ]"#]], + ); + + check( + parse, + "0O789;", + &expect![[r#" + Stmt [0-3]: + annotations: + kind: ExprStmt [0-3]: + expr: Expr [0-3]: Lit: Int(7) + + [ + Error( + Token( + Semicolon, + Literal( + Integer( + Decimal, + ), + ), + Span { + lo: 3, + hi: 5, + }, + ), + ), + ]"#]], + ); + + check( + parse, + "0x12g;", + &expect![[r#" + Error( + ExpectedItem( + Identifier, + Span { + lo: 0, + hi: 4, + }, + ), + ) + "#]], + ); + + check( + parse, + "0X12g;", + &expect![[r#" + Error( + ExpectedItem( + Identifier, + Span { + lo: 0, + hi: 4, + }, + ), + ) + "#]], + ); + + check( + parse, + "12af;", + &expect![[r#" + Error( + ExpectedItem( + Identifier, + Span { + lo: 0, + hi: 2, + }, + ), + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/io_decl.rs b/compiler/qsc_qasm/src/parser/stmt/tests/io_decl.rs new file mode 100644 index 0000000000..aa9c48b864 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/io_decl.rs @@ -0,0 +1,484 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::parser::tests::check; + +use crate::parser::stmt::parse; + +#[test] +fn input_bit_decl() { + check( + parse, + "input bit b;", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: IODeclaration [0-12]: + io_keyword: input + type: ScalarType [6-9]: BitType [6-9]: + size: + ident: Ident [10-11] "b""#]], + ); +} + +#[test] +fn output_bit_decl() { + check( + parse, + "output bit b;", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: IODeclaration [0-13]: + io_keyword: output + type: ScalarType [7-10]: BitType [7-10]: + size: + ident: Ident [11-12] "b""#]], + ); +} + +#[test] +fn input_bit_array_decl() { + check( + parse, + "input bit[2] b;", + &expect![[r#" + Stmt [0-15]: + annotations: + kind: IODeclaration [0-15]: + io_keyword: input + type: ScalarType [6-12]: BitType [6-12]: + size: Expr [10-11]: Lit: Int(2) + ident: Ident [13-14] "b""#]], + ); +} + +#[test] +fn output_bit_array_decl() { + check( + parse, + "output bit[2] b;", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: IODeclaration [0-16]: + io_keyword: output + type: ScalarType [7-13]: BitType [7-13]: + size: Expr [11-12]: Lit: Int(2) + ident: Ident [14-15] "b""#]], + ); +} + +#[test] +fn intput_bool_decl() { + check( + parse, + "input bool b;", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: IODeclaration [0-13]: + io_keyword: input + type: ScalarType [6-10]: BoolType + ident: Ident [11-12] "b""#]], + ); +} + +#[test] +fn output_bool_decl() { + check( + parse, + "output bool b;", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: IODeclaration [0-14]: + io_keyword: output + type: ScalarType [7-11]: BoolType + ident: Ident [12-13] "b""#]], + ); +} + +#[test] +fn input_complex_decl() { + check( + parse, + "input complex c;", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: IODeclaration [0-16]: + io_keyword: input + type: ScalarType [6-13]: ComplexType [6-13]: + base_size: + ident: Ident [14-15] "c""#]], + ); +} + +#[test] +fn output_complex_decl() { + check( + parse, + "output complex c;", + &expect![[r#" + Stmt [0-17]: + annotations: + kind: IODeclaration [0-17]: + io_keyword: output + type: ScalarType [7-14]: ComplexType [7-14]: + base_size: + ident: Ident [15-16] "c""#]], + ); +} + +#[test] +fn input_complex_sized_decl() { + check( + parse, + "input complex[float[32]] c;", + &expect![[r#" + Stmt [0-27]: + annotations: + kind: IODeclaration [0-27]: + io_keyword: input + type: ScalarType [6-24]: ComplexType [6-24]: + base_size: FloatType [14-23]: + size: Expr [20-22]: Lit: Int(32) + ident: Ident [25-26] "c""#]], + ); +} + +#[test] +fn output_complex_sized_decl() { + check( + parse, + "output complex[float[32]] c;", + &expect![[r#" + Stmt [0-28]: + annotations: + kind: IODeclaration [0-28]: + io_keyword: output + type: ScalarType [7-25]: ComplexType [7-25]: + base_size: FloatType [15-24]: + size: Expr [21-23]: Lit: Int(32) + ident: Ident [26-27] "c""#]], + ); +} + +#[test] +fn input_int_decl() { + check( + parse, + "input int i;", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: IODeclaration [0-12]: + io_keyword: input + type: ScalarType [6-9]: IntType [6-9]: + size: + ident: Ident [10-11] "i""#]], + ); +} + +#[test] +fn output_int_decl() { + check( + parse, + "output int i;", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: IODeclaration [0-13]: + io_keyword: output + type: ScalarType [7-10]: IntType [7-10]: + size: + ident: Ident [11-12] "i""#]], + ); +} + +#[test] +fn input_int_sized_decl() { + check( + parse, + "input int[32] i;", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: IODeclaration [0-16]: + io_keyword: input + type: ScalarType [6-13]: IntType [6-13]: + size: Expr [10-12]: Lit: Int(32) + ident: Ident [14-15] "i""#]], + ); +} + +#[test] +fn output_int_sized_decl() { + check( + parse, + "output int[32] i;", + &expect![[r#" + Stmt [0-17]: + annotations: + kind: IODeclaration [0-17]: + io_keyword: output + type: ScalarType [7-14]: IntType [7-14]: + size: Expr [11-13]: Lit: Int(32) + ident: Ident [15-16] "i""#]], + ); +} + +#[test] +fn input_uint_decl() { + check( + parse, + "input uint i;", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: IODeclaration [0-13]: + io_keyword: input + type: ScalarType [6-10]: UIntType [6-10]: + size: + ident: Ident [11-12] "i""#]], + ); +} + +#[test] +fn output_uint_decl() { + check( + parse, + "output uint i;", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: IODeclaration [0-14]: + io_keyword: output + type: ScalarType [7-11]: UIntType [7-11]: + size: + ident: Ident [12-13] "i""#]], + ); +} + +#[test] +fn input_uint_sized_decl() { + check( + parse, + "input uint[32] i;", + &expect![[r#" + Stmt [0-17]: + annotations: + kind: IODeclaration [0-17]: + io_keyword: input + type: ScalarType [6-14]: UIntType [6-14]: + size: Expr [11-13]: Lit: Int(32) + ident: Ident [15-16] "i""#]], + ); +} + +#[test] +fn output_uint_sized_decl() { + check( + parse, + "output uint[32] i;", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: IODeclaration [0-18]: + io_keyword: output + type: ScalarType [7-15]: UIntType [7-15]: + size: Expr [12-14]: Lit: Int(32) + ident: Ident [16-17] "i""#]], + ); +} + +#[test] +fn input_float_decl() { + check( + parse, + "input float f;", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: IODeclaration [0-14]: + io_keyword: input + type: ScalarType [6-11]: FloatType [6-11]: + size: + ident: Ident [12-13] "f""#]], + ); +} + +#[test] +fn output_float_decl() { + check( + parse, + "output float f;", + &expect![[r#" + Stmt [0-15]: + annotations: + kind: IODeclaration [0-15]: + io_keyword: output + type: ScalarType [7-12]: FloatType [7-12]: + size: + ident: Ident [13-14] "f""#]], + ); +} + +#[test] +fn input_float_sized_decl() { + check( + parse, + "input float[32] f;", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: IODeclaration [0-18]: + io_keyword: input + type: ScalarType [6-15]: FloatType [6-15]: + size: Expr [12-14]: Lit: Int(32) + ident: Ident [16-17] "f""#]], + ); +} + +#[test] +fn output_float_sized_decl() { + check( + parse, + "output float[32] f;", + &expect![[r#" + Stmt [0-19]: + annotations: + kind: IODeclaration [0-19]: + io_keyword: output + type: ScalarType [7-16]: FloatType [7-16]: + size: Expr [13-15]: Lit: Int(32) + ident: Ident [17-18] "f""#]], + ); +} + +#[test] +fn input_angle_decl() { + check( + parse, + "input angle a;", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: IODeclaration [0-14]: + io_keyword: input + type: ScalarType [6-11]: AngleType [6-11]: + size: + ident: Ident [12-13] "a""#]], + ); +} + +#[test] +fn output_angle_decl() { + check( + parse, + "output angle a;", + &expect![[r#" + Stmt [0-15]: + annotations: + kind: IODeclaration [0-15]: + io_keyword: output + type: ScalarType [7-12]: AngleType [7-12]: + size: + ident: Ident [13-14] "a""#]], + ); +} + +#[test] +fn input_angle_sized_decl() { + check( + parse, + "input angle[32] a;", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: IODeclaration [0-18]: + io_keyword: input + type: ScalarType [6-15]: AngleType [6-15]: + size: Expr [12-14]: Lit: Int(32) + ident: Ident [16-17] "a""#]], + ); +} + +#[test] +fn output_angle_sized_decl() { + check( + parse, + "output angle[32] a;", + &expect![[r#" + Stmt [0-19]: + annotations: + kind: IODeclaration [0-19]: + io_keyword: output + type: ScalarType [7-16]: AngleType [7-16]: + size: Expr [13-15]: Lit: Int(32) + ident: Ident [17-18] "a""#]], + ); +} + +#[test] +fn input_duration_decl() { + check( + parse, + "input duration d;", + &expect![[r#" + Stmt [0-17]: + annotations: + kind: IODeclaration [0-17]: + io_keyword: input + type: ScalarType [6-14]: Duration + ident: Ident [15-16] "d""#]], + ); +} + +#[test] +fn output_duration_decl() { + check( + parse, + "output duration d;", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: IODeclaration [0-18]: + io_keyword: output + type: ScalarType [7-15]: Duration + ident: Ident [16-17] "d""#]], + ); +} + +#[test] +fn input_stretch_decl() { + check( + parse, + "input stretch s;", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: IODeclaration [0-16]: + io_keyword: input + type: ScalarType [6-13]: Stretch + ident: Ident [14-15] "s""#]], + ); +} + +#[test] +fn output_stretch_decl() { + check( + parse, + "output stretch s;", + &expect![[r#" + Stmt [0-17]: + annotations: + kind: IODeclaration [0-17]: + io_keyword: output + type: ScalarType [7-14]: Stretch + ident: Ident [15-16] "s""#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/measure.rs b/compiler/qsc_qasm/src/parser/stmt/tests/measure.rs new file mode 100644 index 0000000000..fd2518ff97 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/measure.rs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn measure_identifier() { + check( + parse, + "measure q;", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: MeasureArrowStmt [0-10]: + measurement: MeasureExpr [0-9]: + operand: GateOperand [8-9]: + kind: IndexedIdent [8-9]: + name: Ident [8-9] "q" + index_span: [0-0] + indices: + target: "#]], + ); +} + +#[test] +fn measure_indented_ident() { + check( + parse, + "measure q[2];", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: MeasureArrowStmt [0-13]: + measurement: MeasureExpr [0-12]: + operand: GateOperand [8-12]: + kind: IndexedIdent [8-12]: + name: Ident [8-9] "q" + index_span: [9-12] + indices: + IndexSet [10-11]: + values: + Expr [10-11]: Lit: Int(2) + target: "#]], + ); +} + +#[test] +fn measure_hardware_qubit() { + check( + parse, + "measure $42;", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: MeasureArrowStmt [0-12]: + measurement: MeasureExpr [0-11]: + operand: GateOperand [8-11]: + kind: HardwareQubit [8-11]: 42 + target: "#]], + ); +} + +#[test] +fn measure_arrow_into_ident() { + check( + parse, + "measure q -> a;", + &expect![[r#" + Stmt [0-15]: + annotations: + kind: MeasureArrowStmt [0-15]: + measurement: MeasureExpr [0-9]: + operand: GateOperand [8-9]: + kind: IndexedIdent [8-9]: + name: Ident [8-9] "q" + index_span: [0-0] + indices: + target: IndexedIdent [13-14]: + name: Ident [13-14] "a" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn measure_arrow_into_indented_ident() { + check( + parse, + "measure q -> a[1];", + &expect![[r#" + Stmt [0-18]: + annotations: + kind: MeasureArrowStmt [0-18]: + measurement: MeasureExpr [0-9]: + operand: GateOperand [8-9]: + kind: IndexedIdent [8-9]: + name: Ident [8-9] "q" + index_span: [0-0] + indices: + target: IndexedIdent [13-17]: + name: Ident [13-14] "a" + index_span: [14-17] + indices: + IndexSet [15-16]: + values: + Expr [15-16]: Lit: Int(1)"#]], + ); +} + +#[test] +fn assign_measure_stmt() { + check( + parse, + "c = measure q;", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: AssignStmt [0-14]: + lhs: IndexedIdent [0-1]: + name: Ident [0-1] "c" + index_span: [0-0] + indices: + rhs: MeasureExpr [4-13]: + operand: GateOperand [12-13]: + kind: IndexedIdent [12-13]: + name: Ident [12-13] "q" + index_span: [0-0] + indices: "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/old_style_decl.rs b/compiler/qsc_qasm/src/parser/stmt/tests/old_style_decl.rs new file mode 100644 index 0000000000..5b9071def6 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/old_style_decl.rs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::parser::tests::check; + +use crate::parser::stmt::parse; + +#[test] +fn creg_decl() { + check( + parse, + "creg c;", + &expect![[r#" + Stmt [0-7]: + annotations: + kind: ClassicalDeclarationStmt [0-7]: + type: ScalarType [0-7]: BitType [0-7]: + size: + ident: Ident [5-6] "c" + init_expr: "#]], + ); +} + +#[test] +fn creg_array_decl() { + check( + parse, + "creg c[n];", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: ClassicalDeclarationStmt [0-10]: + type: ScalarType [0-10]: BitType [0-10]: + size: Expr [7-8]: Ident [7-8] "n" + ident: Ident [5-6] "c" + init_expr: "#]], + ); +} + +#[test] +fn qreg_decl() { + check( + parse, + "qreg q;", + &expect![[r#" + Stmt [0-7]: + annotations: + kind: QubitDeclaration [0-7]: + ty_span: [0-7] + ident: Ident [5-6] "q" + size: "#]], + ); +} + +#[test] +fn qreg_array_decl() { + check( + parse, + "qreg q[n];", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: QubitDeclaration [0-10]: + ty_span: [0-10] + ident: Ident [5-6] "q" + size: Expr [7-8]: Ident [7-8] "n""#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/pragma.rs b/compiler/qsc_qasm/src/parser/stmt/tests/pragma.rs new file mode 100644 index 0000000000..42abef06cb --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/pragma.rs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::parser::tests::check; + +use crate::parser::stmt::parse; + +#[test] +fn pragma_decl() { + check( + parse, + "pragma a.b.d 23", + &expect![[r#" + Stmt [0-15]: + annotations: + kind: Pragma [0-15]: + identifier: "a.b.d" + value: "23""#]], + ); +} + +#[test] +fn pragma_decl_ident_only() { + check( + parse, + "pragma a.b.d", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: Pragma [0-12]: + identifier: "a.b.d" + value: "#]], + ); +} + +#[test] +fn pragma_decl_missing_ident() { + check( + parse, + "pragma ", + &expect![[r#" + Stmt [0-7]: + annotations: + kind: Pragma [0-7]: + identifier: "" + value: + + [ + Error( + Rule( + "pragma missing identifier", + Pragma, + Span { + lo: 0, + hi: 7, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn legacy_pragma_decl() { + check( + parse, + "#pragma a.b.d 23", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: Pragma [0-16]: + identifier: "a" + value: "a.b.d 23""#]], + ); +} + +#[test] +fn legacy_pragma_decl_ident_only() { + check( + parse, + "#pragma a.b.d", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: Pragma [0-13]: + identifier: "a" + value: "a.b.d""#]], + ); +} + +#[test] +fn legacy_pragma_ws_after_hash() { + check( + parse, + "# pragma a.b.d", + &expect![[r#" + Stmt [2-14]: + annotations: + kind: Pragma [2-14]: + identifier: "a.b.d" + value: + + [ + Error( + Lex( + Incomplete( + Ident, + Identifier, + Whitespace, + Span { + lo: 1, + hi: 2, + }, + ), + ), + ), + ]"#]], + ); +} + +#[test] +fn legacy_pragma_decl_missing_ident() { + check( + parse, + "#pragma ", + &expect![[r#" + Stmt [0-8]: + annotations: + kind: Pragma [0-8]: + identifier: "a" + value: """#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/quantum_decl.rs b/compiler/qsc_qasm/src/parser/stmt/tests/quantum_decl.rs new file mode 100644 index 0000000000..67031b9bd2 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/quantum_decl.rs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::parser::tests::check; + +use crate::parser::stmt::parse; + +#[test] +fn quantum_decl() { + check( + parse, + "qubit q;", + &expect![[r#" + Stmt [0-8]: + annotations: + kind: QubitDeclaration [0-8]: + ty_span: [0-5] + ident: Ident [6-7] "q" + size: "#]], + ); +} + +#[test] +fn annotated_quantum_decl() { + check( + parse, + r#" + @a.b.c 123 + qubit q;"#, + &expect![[r#" + Stmt [9-36]: + annotations: + Annotation [9-19]: + identifier: "a.b.c" + value: "123" + kind: QubitDeclaration [28-36]: + ty_span: [28-33] + ident: Ident [34-35] "q" + size: "#]], + ); +} + +#[test] +fn multi_annotated_quantum_decl() { + check( + parse, + r#" + @g.h dolor sit amet, consectetur adipiscing elit + @d.e.f + @a.b.c 123 + qubit q;"#, + &expect![[r#" + Stmt [9-108]: + annotations: + Annotation [9-57]: + identifier: "g.h" + value: "dolor sit amet, consectetur adipiscing elit" + Annotation [66-72]: + identifier: "d.e.f" + value: + Annotation [81-91]: + identifier: "a.b.c" + value: "123" + kind: QubitDeclaration [100-108]: + ty_span: [100-105] + ident: Ident [106-107] "q" + size: "#]], + ); +} + +#[test] +fn quantum_decl_missing_name() { + check( + parse, + "qubit;", + &expect![[r#" + Error( + Rule( + "identifier", + Semicolon, + Span { + lo: 5, + hi: 6, + }, + ), + ) + "#]], + ); +} + +#[test] +fn quantum_decl_with_designator() { + check( + parse, + "qubit[5] qubits;", + &expect![[r#" + Stmt [0-16]: + annotations: + kind: QubitDeclaration [0-16]: + ty_span: [0-8] + ident: Ident [9-15] "qubits" + size: Expr [6-7]: Lit: Int(5)"#]], + ); +} + +#[test] +fn quantum_decl_with_designator_missing_name() { + check( + parse, + "qubit[5]", + &expect![[r#" + Error( + Rule( + "identifier", + Eof, + Span { + lo: 8, + hi: 8, + }, + ), + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/reset.rs b/compiler/qsc_qasm/src/parser/stmt/tests/reset.rs new file mode 100644 index 0000000000..dc8670b04a --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/reset.rs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::stmt::parse; +use crate::parser::tests::check; +use expect_test::expect; + +#[test] +fn reset_ident() { + check( + parse, + "reset a;", + &expect![[r#" + Stmt [0-8]: + annotations: + kind: ResetStmt [0-8]: + reset_token_span: [0-5] + operand: GateOperand [6-7]: + kind: IndexedIdent [6-7]: + name: Ident [6-7] "a" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn reset_indexed_ident() { + check( + parse, + "reset a[1];", + &expect![[r#" + Stmt [0-11]: + annotations: + kind: ResetStmt [0-11]: + reset_token_span: [0-5] + operand: GateOperand [6-10]: + kind: IndexedIdent [6-10]: + name: Ident [6-7] "a" + index_span: [7-10] + indices: + IndexSet [8-9]: + values: + Expr [8-9]: Lit: Int(1)"#]], + ); +} + +#[test] +fn reset_hardware_qubit() { + check( + parse, + "reset $42;", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: ResetStmt [0-10]: + reset_token_span: [0-5] + operand: GateOperand [6-9]: + kind: HardwareQubit [6-9]: 42"#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/switch_stmt.rs b/compiler/qsc_qasm/src/parser/stmt/tests/switch_stmt.rs new file mode 100644 index 0000000000..2ecdea6fd4 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/switch_stmt.rs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse_switch_stmt, tests::check}; +use expect_test::expect; + +#[test] +fn simple_switch() { + check( + parse_switch_stmt, + " + switch (x) { + case 1 {} + default {} + } + ", + &expect![[r#" + SwitchStmt [9-72]: + target: Expr [17-18]: Ident [17-18] "x" + cases: + SwitchCase [32-41]: + labels: + Expr [37-38]: Lit: Int(1) + block: Block [39-41]: + default_case: Block [60-62]: "#]], + ); +} + +#[test] +fn no_cases_no_default() { + check( + parse_switch_stmt, + " + switch (x) {} + ", + &expect![[r#" + SwitchStmt [9-22]: + target: Expr [17-18]: Ident [17-18] "x" + cases: + default_case: + + [ + Error( + MissingSwitchCases( + Span { + lo: 21, + hi: 21, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn no_cases() { + check( + parse_switch_stmt, + " + switch (x) { + default {} + } + ", + &expect![[r#" + SwitchStmt [9-52]: + target: Expr [17-18]: Ident [17-18] "x" + cases: + default_case: Block [40-42]: + + [ + Error( + MissingSwitchCases( + Span { + lo: 32, + hi: 21, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn no_default() { + check( + parse_switch_stmt, + " + switch (x) { + case 0, 1 {} + } + ", + &expect![[r#" + SwitchStmt [9-54]: + target: Expr [17-18]: Ident [17-18] "x" + cases: + SwitchCase [32-44]: + labels: + Expr [37-38]: Lit: Int(0) + Expr [40-41]: Lit: Int(1) + block: Block [42-44]: + default_case: "#]], + ); +} + +#[test] +fn case_with_no_labels() { + check( + parse_switch_stmt, + " + switch (x) { + case {} + } + ", + &expect![[r#" + SwitchStmt [9-49]: + target: Expr [17-18]: Ident [17-18] "x" + cases: + SwitchCase [32-39]: + labels: + block: Block [37-39]: + default_case: + + [ + Error( + MissingSwitchCaseLabels( + Span { + lo: 32, + hi: 36, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn multiple_cases() { + check( + parse_switch_stmt, + " + switch (x) { + case 0 { int x = 0; } + case 1 { int y = 1; } + } + ", + &expect![[r#" + SwitchStmt [9-95]: + target: Expr [17-18]: Ident [17-18] "x" + cases: + SwitchCase [32-53]: + labels: + Expr [37-38]: Lit: Int(0) + block: Block [39-53]: + Stmt [41-51]: + annotations: + kind: ClassicalDeclarationStmt [41-51]: + type: ScalarType [41-44]: IntType [41-44]: + size: + ident: Ident [45-46] "x" + init_expr: Expr [49-50]: Lit: Int(0) + SwitchCase [64-85]: + labels: + Expr [69-70]: Lit: Int(1) + block: Block [71-85]: + Stmt [73-83]: + annotations: + kind: ClassicalDeclarationStmt [73-83]: + type: ScalarType [73-76]: IntType [73-76]: + size: + ident: Ident [77-78] "y" + init_expr: Expr [81-82]: Lit: Int(1) + default_case: "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/stmt/tests/while_loops.rs b/compiler/qsc_qasm/src/parser/stmt/tests/while_loops.rs new file mode 100644 index 0000000000..0e5493d1f5 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/stmt/tests/while_loops.rs @@ -0,0 +1,232 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn simple_while() { + check( + parse, + " + while (x != 2) { + a = 0; + }", + &expect![[r#" + Stmt [5-42]: + annotations: + kind: WhileLoop [5-42]: + condition: Expr [12-18]: BinaryOpExpr: + op: Neq + lhs: Expr [12-13]: Ident [12-13] "x" + rhs: Expr [17-18]: Lit: Int(2) + body: Stmt [20-42]: + annotations: + kind: Block [20-42]: + Stmt [30-36]: + annotations: + kind: AssignStmt [30-36]: + lhs: IndexedIdent [30-31]: + name: Ident [30-31] "a" + index_span: [0-0] + indices: + rhs: Expr [34-35]: Lit: Int(0)"#]], + ); +} + +#[test] +fn empty_while() { + check( + parse, + "while (true) {}", + &expect![[r#" + Stmt [0-15]: + annotations: + kind: WhileLoop [0-15]: + condition: Expr [7-11]: Lit: Bool(true) + body: Stmt [13-15]: + annotations: + kind: Block [13-15]: "#]], + ); +} + +#[test] +fn while_stmt_body() { + check( + parse, + " + while (x != 2) + a = 0;", + &expect![[r#" + Stmt [5-34]: + annotations: + kind: WhileLoop [5-34]: + condition: Expr [12-18]: BinaryOpExpr: + op: Neq + lhs: Expr [12-13]: Ident [12-13] "x" + rhs: Expr [17-18]: Lit: Int(2) + body: Stmt [28-34]: + annotations: + kind: AssignStmt [28-34]: + lhs: IndexedIdent [28-29]: + name: Ident [28-29] "a" + index_span: [0-0] + indices: + rhs: Expr [32-33]: Lit: Int(0)"#]], + ); +} + +#[test] +fn while_loop_with_continue_stmt() { + check( + parse, + " + while (x != 2) { + a = 0; + continue; + }", + &expect![[r#" + Stmt [5-60]: + annotations: + kind: WhileLoop [5-60]: + condition: Expr [12-18]: BinaryOpExpr: + op: Neq + lhs: Expr [12-13]: Ident [12-13] "x" + rhs: Expr [17-18]: Lit: Int(2) + body: Stmt [20-60]: + annotations: + kind: Block [20-60]: + Stmt [30-36]: + annotations: + kind: AssignStmt [30-36]: + lhs: IndexedIdent [30-31]: + name: Ident [30-31] "a" + index_span: [0-0] + indices: + rhs: Expr [34-35]: Lit: Int(0) + Stmt [45-54]: + annotations: + kind: ContinueStmt [45-54]"#]], + ); +} + +#[test] +fn while_loop_with_break_stmt() { + check( + parse, + " + while (x != 2) { + a = 0; + break; + }", + &expect![[r#" + Stmt [5-57]: + annotations: + kind: WhileLoop [5-57]: + condition: Expr [12-18]: BinaryOpExpr: + op: Neq + lhs: Expr [12-13]: Ident [12-13] "x" + rhs: Expr [17-18]: Lit: Int(2) + body: Stmt [20-57]: + annotations: + kind: Block [20-57]: + Stmt [30-36]: + annotations: + kind: AssignStmt [30-36]: + lhs: IndexedIdent [30-31]: + name: Ident [30-31] "a" + index_span: [0-0] + indices: + rhs: Expr [34-35]: Lit: Int(0) + Stmt [45-51]: + annotations: + kind: BreakStmt [45-51]"#]], + ); +} + +#[test] +fn single_stmt_while_stmt() { + check( + parse, + "while (x) z q;", + &expect![[r#" + Stmt [0-14]: + annotations: + kind: WhileLoop [0-14]: + condition: Expr [7-8]: Ident [7-8] "x" + body: Stmt [10-14]: + annotations: + kind: GateCall [10-14]: + modifiers: + name: Ident [10-11] "z" + args: + duration: + qubits: + GateOperand [12-13]: + kind: IndexedIdent [12-13]: + name: Ident [12-13] "q" + index_span: [0-0] + indices: "#]], + ); +} + +#[test] +fn annotations_in_single_stmt_while_stmt() { + check( + parse, + " + while (x) + @foo + @bar + x = 5;", + &expect![[r#" + Stmt [5-55]: + annotations: + kind: WhileLoop [5-55]: + condition: Expr [12-13]: Ident [12-13] "x" + body: Stmt [23-55]: + annotations: + Annotation [23-27]: + identifier: "foo" + value: + Annotation [36-40]: + identifier: "bar" + value: + kind: AssignStmt [49-55]: + lhs: IndexedIdent [49-50]: + name: Ident [49-50] "x" + index_span: [0-0] + indices: + rhs: Expr [53-54]: Lit: Int(5)"#]], + ); +} + +#[test] +fn nested_single_stmt_while_stmt() { + check( + parse, + "while (x) while (y) z q;", + &expect![[r#" + Stmt [0-24]: + annotations: + kind: WhileLoop [0-24]: + condition: Expr [7-8]: Ident [7-8] "x" + body: Stmt [10-24]: + annotations: + kind: WhileLoop [10-24]: + condition: Expr [17-18]: Ident [17-18] "y" + body: Stmt [20-24]: + annotations: + kind: GateCall [20-24]: + modifiers: + name: Ident [20-21] "z" + args: + duration: + qubits: + GateOperand [22-23]: + kind: IndexedIdent [22-23]: + name: Ident [22-23] "q" + index_span: [0-0] + indices: "#]], + ); +} diff --git a/compiler/qsc_qasm/src/parser/tests.rs b/compiler/qsc_qasm/src/parser/tests.rs new file mode 100644 index 0000000000..894e9730a6 --- /dev/null +++ b/compiler/qsc_qasm/src/parser/tests.rs @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::path::Path; +use std::sync::Arc; + +use crate::io::InMemorySourceResolver; +use crate::io::SourceResolver; + +use super::parse_source; +use super::QasmParseResult; +use miette::Report; + +use super::prim::FinalSep; +use super::{scan::ParserContext, Parser}; +use expect_test::Expect; +use std::fmt::Display; + +pub(crate) fn parse_all

( + path: P, + sources: impl IntoIterator, Arc)>, +) -> miette::Result> +where + P: AsRef, +{ + let mut resolver = InMemorySourceResolver::from_iter(sources); + let (path, source) = resolver + .resolve(path.as_ref()) + .map_err(|e| vec![Report::new(e)])?; + let res = crate::parser::parse_source(source, path, &mut resolver); + if res.source.has_errors() { + let errors = res + .errors() + .into_iter() + .map(|e| Report::new(e.clone())) + .collect(); + Err(errors) + } else { + Ok(res) + } +} + +pub(crate) fn parse(source: S) -> miette::Result> +where + S: AsRef, +{ + let mut resolver = InMemorySourceResolver::from_iter([("test".into(), source.as_ref().into())]); + let res = parse_source(source, "test", &mut resolver); + if res.source.has_errors() { + let errors = res + .errors() + .into_iter() + .map(|e| Report::new(e.clone())) + .collect(); + return Err(errors); + } + Ok(res) +} + +pub(super) fn check(parser: impl Parser, input: &str, expect: &Expect) { + check_map(parser, input, expect, ToString::to_string); +} + +pub(super) fn check_opt(parser: impl Parser>, input: &str, expect: &Expect) { + check_map(parser, input, expect, |value| match value { + Some(value) => value.to_string(), + None => "None".to_string(), + }); +} + +pub(super) fn check_seq( + parser: impl Parser<(Vec, FinalSep)>, + input: &str, + expect: &Expect, +) { + check_map(parser, input, expect, |(values, sep)| { + format!( + "({}, {sep:?})", + values + .iter() + .map(ToString::to_string) + .collect::>() + .join(",\n") + ) + }); +} + +fn check_map( + mut parser: impl Parser, + input: &str, + expect: &Expect, + f: impl FnOnce(&T) -> String, +) { + let mut scanner = ParserContext::new(input); + let result = parser(&mut scanner); + let errors = scanner.into_errors(); + match result { + Ok(value) if errors.is_empty() => expect.assert_eq(&f(&value)), + Ok(value) => expect.assert_eq(&format!("{}\n\n{errors:#?}", f(&value))), + Err(error) if errors.is_empty() => expect.assert_debug_eq(&error), + Err(error) => expect.assert_eq(&format!("{error:#?}\n\n{errors:#?}")), + } +} + +#[test] +fn int_version_can_be_parsed() -> miette::Result<(), Vec> { + let source = r#"OPENQASM 3;"#; + let res = parse(source)?; + assert_eq!( + Some(format!("{}", res.source.program.version.expect("version"))), + Some("3".to_string()) + ); + Ok(()) +} + +#[test] +fn dotted_version_can_be_parsed() -> miette::Result<(), Vec> { + let source = r#"OPENQASM 3.0;"#; + let res = parse(source)?; + assert_eq!( + Some(format!("{}", res.source.program.version.expect("version"))), + Some("3.0".to_string()) + ); + Ok(()) +} + +#[test] +fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { + let source0 = r#"OPENQASM 3.0; + include "stdgates.inc"; + include "source1.qasm";"#; + let source1 = ""; + let all_sources = [ + ("source0.qasm".into(), source0.into()), + ("source1.qasm".into(), source1.into()), + ]; + + let res = parse_all("source0.qasm", all_sources)?; + assert!(res.source.includes().len() == 1); + Ok(()) +} + +#[test] +fn programs_with_includes_with_includes_can_be_parsed() -> miette::Result<(), Vec> { + let source0 = r#"OPENQASM 3.0; + include "stdgates.inc"; + include "source1.qasm"; + "#; + let source1 = r#"include "source2.qasm"; + "#; + let source2 = ""; + let all_sources = [ + ("source0.qasm".into(), source0.into()), + ("source1.qasm".into(), source1.into()), + ("source2.qasm".into(), source2.into()), + ]; + + let res = parse_all("source0.qasm", all_sources)?; + assert!(res.source.includes().len() == 1); + assert!(res.source.includes()[0].includes().len() == 1); + Ok(()) +} diff --git a/compiler/qsc_qasm/src/semantic.rs b/compiler/qsc_qasm/src/semantic.rs new file mode 100644 index 0000000000..720982bfa5 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic.rs @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::io::InMemorySourceResolver; +use crate::io::SourceResolver; +use crate::parser::QasmSource; + +use lowerer::Lowerer; +use qsc_frontend::compile::SourceMap; +use qsc_frontend::error::WithSource; + +use std::path::Path; + +pub(crate) mod ast; +pub(crate) mod const_eval; +pub mod error; +mod lowerer; +pub use error::Error; +pub use error::SemanticErrorKind; +pub mod symbols; +pub mod types; + +#[cfg(test)] +pub(crate) mod tests; + +pub struct QasmSemanticParseResult { + pub source: QasmSource, + pub source_map: SourceMap, + pub symbols: self::symbols::SymbolTable, + pub program: self::ast::Program, + pub errors: Vec>, +} + +impl QasmSemanticParseResult { + #[must_use] + pub fn has_errors(&self) -> bool { + self.has_syntax_errors() || self.has_semantic_errors() + } + + #[must_use] + pub fn has_syntax_errors(&self) -> bool { + self.source.has_errors() + } + + #[must_use] + pub fn has_semantic_errors(&self) -> bool { + !self.errors.is_empty() + } + + pub fn sytax_errors(&self) -> Vec> { + let mut self_errors = self + .source + .errors() + .iter() + .map(|e| self.map_parse_error(e.clone())) + .collect::>(); + let include_errors = self + .source + .includes() + .iter() + .flat_map(QasmSource::all_errors) + .map(|e| self.map_parse_error(e)) + .collect::>(); + + self_errors.extend(include_errors); + self_errors + } + + #[must_use] + pub fn semantic_errors(&self) -> Vec> { + self.errors().clone() + } + + #[must_use] + pub fn all_errors(&self) -> Vec> { + let mut parse_errors = self.sytax_errors(); + let sem_errors = self.semantic_errors(); + parse_errors.extend(sem_errors); + parse_errors + } + + #[must_use] + pub fn errors(&self) -> Vec> { + self.errors.clone() + } + + fn map_parse_error(&self, error: crate::parser::Error) -> WithSource { + WithSource::from_map( + &self.source_map, + crate::Error(crate::ErrorKind::Parser(error)), + ) + } +} + +pub(crate) fn parse(source: S, path: P) -> QasmSemanticParseResult +where + S: AsRef, + P: AsRef, +{ + let mut resolver = InMemorySourceResolver::from_iter([( + path.as_ref().display().to_string().into(), + source.as_ref().into(), + )]); + parse_source(source, path, &mut resolver) +} + +/// Parse a QASM file and return the parse result. +/// This function will resolve includes using the provided resolver. +/// If an include file cannot be resolved, an error will be returned. +/// If a file is included recursively, a stack overflow occurs. +pub fn parse_source(source: S, path: P, resolver: &mut R) -> QasmSemanticParseResult +where + S: AsRef, + P: AsRef, + R: SourceResolver, +{ + let res = crate::parser::parse_source(source, path, resolver); + let analyzer = Lowerer::new(res.source, res.source_map); + let sem_res = analyzer.lower(); + let errors = sem_res.all_errors(); + QasmSemanticParseResult { + source: sem_res.source, + source_map: sem_res.source_map, + symbols: sem_res.symbols, + program: sem_res.program, + errors, + } +} diff --git a/compiler/qsc_qasm/src/semantic/ast.rs b/compiler/qsc_qasm/src/semantic/ast.rs new file mode 100644 index 0000000000..3e46a9dd3d --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/ast.rs @@ -0,0 +1,1403 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use num_bigint::BigInt; +use qsc_data_structures::span::Span; +use std::{ + fmt::{self, Display, Formatter}, + hash::Hash, + rc::Rc, +}; + +use crate::{ + display_utils::{ + write_field, write_header, write_indented_list, write_list_field, write_opt_field, + write_opt_list_field, writeln_field, writeln_header, writeln_list_field, writeln_opt_field, + }, + parser::ast::List, + semantic::symbols::SymbolId, + stdlib::angle::Angle, +}; + +use crate::parser::ast as syntax; + +#[derive(Clone, Debug)] +pub struct Program { + pub statements: List, + pub version: Option, +} + +impl Display for Program { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "Program:")?; + writeln_opt_field(f, "version", self.version.as_ref())?; + write_list_field(f, "statements", &self.statements) + } +} + +#[derive(Clone, Debug)] +pub struct Stmt { + pub span: Span, + pub annotations: List, + pub kind: Box, +} + +impl Display for Stmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "Stmt", self.span)?; + writeln_list_field(f, "annotations", &self.annotations)?; + write_field(f, "kind", &self.kind) + } +} + +#[derive(Clone, Debug)] +pub struct Annotation { + pub span: Span, + pub identifier: Rc, + pub value: Option>, +} + +impl Display for Annotation { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let identifier = format!("\"{}\"", self.identifier); + let value = self.value.as_ref().map(|val| format!("\"{val}\"")); + writeln_header(f, "Annotation", self.span)?; + writeln_field(f, "identifier", &identifier)?; + write_opt_field(f, "value", value.as_ref()) + } +} + +/// A path that was successfully parsed up to a certain `.`, +/// but is missing its final identifier. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct IncompletePath { + /// The whole span of the incomplete path, + /// including the final `.` and any whitespace or keyword + /// that follows it. + pub span: Span, + /// Any segments that were successfully parsed before the final `.`. + pub segments: Box<[Ident]>, + /// Whether a keyword exists after the final `.`. + /// This keyword can be presumed to be a partially typed identifier. + pub keyword: bool, +} + +/// A path to a declaration or a field access expression, +/// to be disambiguated during name resolution. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct Path { + /// The span. + pub span: Span, + /// The segments that make up the front of the path before the final `.`. + pub segments: Option>, + /// The declaration or field name. + pub name: Box, +} + +impl Display for Path { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln_header(f, "Path", self.span)?; + writeln_field(f, "name", &self.name)?; + write_opt_list_field(f, "segments", self.segments.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct MeasureExpr { + pub span: Span, + pub measure_token_span: Span, + pub operand: GateOperand, +} + +impl Display for MeasureExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "MeasureExpr", self.span)?; + writeln_field(f, "measure_token_span", &self.measure_token_span)?; + write_field(f, "operand", &self.operand) + } +} + +/// A binary operator. +#[derive(Clone, Copy, Debug)] +pub enum BinOp { + /// Addition: `+`. + Add, + /// Bitwise AND: `&`. + AndB, + /// Logical AND: `&&`. + AndL, + /// Division: `/`. + Div, + /// Equality: `==`. + Eq, + /// Exponentiation: `**`. + Exp, + /// Greater than: `>`. + Gt, + /// Greater than or equal: `>=`. + Gte, + /// Less than: `<`. + Lt, + /// Less than or equal: `<=`. + Lte, + /// Modulus: `%`. + Mod, + /// Multiplication: `*`. + Mul, + /// Inequality: `!=`. + Neq, + /// Bitwise OR: `|`. + OrB, + /// Logical OR: `||`. + OrL, + /// Shift left: `<<`. + Shl, + /// Shift right: `>>`. + Shr, + /// Subtraction: `-`. + Sub, + /// Bitwise XOR: `^`. + XorB, +} + +impl Display for BinOp { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + BinOp::Add => write!(f, "Add"), + BinOp::AndB => write!(f, "AndB"), + BinOp::AndL => write!(f, "AndL"), + BinOp::Div => write!(f, "Div"), + BinOp::Eq => write!(f, "Eq"), + BinOp::Exp => write!(f, "Exp"), + BinOp::Gt => write!(f, "Gt"), + BinOp::Gte => write!(f, "Gte"), + BinOp::Lt => write!(f, "Lt"), + BinOp::Lte => write!(f, "Lte"), + BinOp::Mod => write!(f, "Mod"), + BinOp::Mul => write!(f, "Mul"), + BinOp::Neq => write!(f, "Neq"), + BinOp::OrB => write!(f, "OrB"), + BinOp::OrL => write!(f, "OrL"), + BinOp::Shl => write!(f, "Shl"), + BinOp::Shr => write!(f, "Shr"), + BinOp::Sub => write!(f, "Sub"), + BinOp::XorB => write!(f, "XorB"), + } + } +} + +impl From for BinOp { + fn from(value: syntax::BinOp) -> Self { + match value { + syntax::BinOp::Add => BinOp::Add, + syntax::BinOp::AndB => BinOp::AndB, + syntax::BinOp::AndL => BinOp::AndL, + syntax::BinOp::Div => BinOp::Div, + syntax::BinOp::Eq => BinOp::Eq, + syntax::BinOp::Exp => BinOp::Exp, + syntax::BinOp::Gt => BinOp::Gt, + syntax::BinOp::Gte => BinOp::Gte, + syntax::BinOp::Lt => BinOp::Lt, + syntax::BinOp::Lte => BinOp::Lte, + syntax::BinOp::Mod => BinOp::Mod, + syntax::BinOp::Mul => BinOp::Mul, + syntax::BinOp::Neq => BinOp::Neq, + syntax::BinOp::OrB => BinOp::OrB, + syntax::BinOp::OrL => BinOp::OrL, + syntax::BinOp::Shl => BinOp::Shl, + syntax::BinOp::Shr => BinOp::Shr, + syntax::BinOp::Sub => BinOp::Sub, + syntax::BinOp::XorB => BinOp::XorB, + } + } +} + +/// A unary operator. +#[derive(Clone, Copy, Debug)] +pub enum UnaryOp { + /// Negation: `-`. + Neg, + /// Bitwise NOT: `~`. + NotB, + /// Logical NOT: `!`. + NotL, +} + +impl Display for UnaryOp { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + UnaryOp::Neg => write!(f, "Neg"), + UnaryOp::NotB => write!(f, "NotB"), + UnaryOp::NotL => write!(f, "NotL"), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct GateOperand { + pub span: Span, + pub kind: GateOperandKind, +} + +impl Display for GateOperand { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "GateOperand", self.span)?; + write_field(f, "kind", &self.kind) + } +} + +#[derive(Clone, Debug, Default)] +pub enum GateOperandKind { + /// `IndexedIdent` and `Ident` get lowered to an `Expr`. + Expr(Box), + HardwareQubit(HardwareQubit), + #[default] + Err, +} + +impl Display for GateOperandKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Expr(expr) => write!(f, "{expr}"), + Self::HardwareQubit(qubit) => write!(f, "{qubit}"), + Self::Err => write!(f, "Err"), + } + } +} + +#[derive(Clone, Debug)] +pub struct HardwareQubit { + pub span: Span, + pub name: Rc, +} + +impl Display for HardwareQubit { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "HardwareQubit {}: {}", self.span, self.name) + } +} + +#[derive(Clone, Debug)] +pub struct AliasDeclStmt { + pub symbol_id: SymbolId, + pub exprs: List, + pub span: Span, +} + +impl Display for AliasDeclStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "AliasDeclStmt", self.span)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + write_list_field(f, "exprs", &self.exprs) + } +} + +/// A statement kind. +#[derive(Clone, Debug, Default)] +pub enum StmtKind { + Alias(AliasDeclStmt), + Assign(AssignStmt), + IndexedAssign(IndexedAssignStmt), + AssignOp(AssignOpStmt), + Barrier(BarrierStmt), + Box(BoxStmt), + Block(Box), + Break(BreakStmt), + CalibrationGrammar(CalibrationGrammarStmt), + ClassicalDecl(ClassicalDeclarationStmt), + Continue(ContinueStmt), + Def(DefStmt), + DefCal(DefCalStmt), + Delay(DelayStmt), + End(EndStmt), + ExprStmt(ExprStmt), + ExternDecl(ExternDecl), + For(ForStmt), + If(IfStmt), + GateCall(GateCall), + Include(IncludeStmt), + InputDeclaration(InputDeclaration), + OutputDeclaration(OutputDeclaration), + MeasureArrow(MeasureArrowStmt), + Pragma(Pragma), + QuantumGateDefinition(QuantumGateDefinition), + QubitDecl(QubitDeclaration), + QubitArrayDecl(QubitArrayDeclaration), + Reset(ResetStmt), + Return(ReturnStmt), + Switch(SwitchStmt), + WhileLoop(WhileLoop), + /// An invalid statement. + #[default] + Err, +} + +impl Display for StmtKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + StmtKind::Alias(alias) => write!(f, "{alias}"), + StmtKind::Assign(stmt) => write!(f, "{stmt}"), + StmtKind::AssignOp(stmt) => write!(f, "{stmt}"), + StmtKind::Barrier(barrier) => write!(f, "{barrier}"), + StmtKind::Box(box_stmt) => write!(f, "{box_stmt}"), + StmtKind::Block(block) => write!(f, "{block}"), + StmtKind::Break(stmt) => write!(f, "{stmt}"), + StmtKind::CalibrationGrammar(grammar) => write!(f, "{grammar}"), + StmtKind::ClassicalDecl(decl) => write!(f, "{decl}"), + StmtKind::Continue(stmt) => write!(f, "{stmt}"), + StmtKind::Def(def) => write!(f, "{def}"), + StmtKind::DefCal(defcal) => write!(f, "{defcal}"), + StmtKind::Delay(delay) => write!(f, "{delay}"), + StmtKind::End(end_stmt) => write!(f, "{end_stmt}"), + StmtKind::ExprStmt(expr) => write!(f, "{expr}"), + StmtKind::ExternDecl(decl) => write!(f, "{decl}"), + StmtKind::For(for_stmt) => write!(f, "{for_stmt}"), + StmtKind::GateCall(gate_call) => write!(f, "{gate_call}"), + StmtKind::If(if_stmt) => write!(f, "{if_stmt}"), + StmtKind::Include(include) => write!(f, "{include}"), + StmtKind::IndexedAssign(assign) => write!(f, "{assign}"), + StmtKind::InputDeclaration(io) => write!(f, "{io}"), + StmtKind::OutputDeclaration(io) => write!(f, "{io}"), + StmtKind::MeasureArrow(measure) => write!(f, "{measure}"), + StmtKind::Pragma(pragma) => write!(f, "{pragma}"), + StmtKind::QuantumGateDefinition(gate) => write!(f, "{gate}"), + StmtKind::QubitDecl(decl) => write!(f, "{decl}"), + StmtKind::QubitArrayDecl(decl) => write!(f, "{decl}"), + StmtKind::Reset(reset_stmt) => write!(f, "{reset_stmt}"), + StmtKind::Return(return_stmt) => write!(f, "{return_stmt}"), + StmtKind::Switch(switch_stmt) => write!(f, "{switch_stmt}"), + StmtKind::WhileLoop(while_loop) => write!(f, "{while_loop}"), + StmtKind::Err => write!(f, "Err"), + } + } +} + +#[derive(Clone, Debug)] +pub struct CalibrationGrammarStmt { + pub span: Span, + pub name: String, +} + +impl Display for CalibrationGrammarStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "CalibrationGrammarStmt", self.span)?; + write_field(f, "name", &self.name) + } +} + +#[derive(Clone, Debug)] +pub struct DefCalStmt { + pub span: Span, +} + +impl Display for DefCalStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "DefCalStmt {}", self.span) + } +} + +#[derive(Clone, Debug)] +pub struct IfStmt { + pub span: Span, + pub condition: Expr, + pub if_body: Stmt, + pub else_body: Option, +} + +impl Display for IfStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "IfStmt", self.span)?; + writeln_field(f, "condition", &self.condition)?; + writeln_field(f, "if_body", &self.if_body)?; + write_opt_field(f, "else_body", self.else_body.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct BarrierStmt { + pub span: Span, + pub qubits: List, +} + +impl Display for BarrierStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "BarrierStmt", self.span)?; + write_list_field(f, "operands", &self.qubits) + } +} + +#[derive(Clone, Debug)] +pub struct ResetStmt { + pub span: Span, + pub reset_token_span: Span, + pub operand: Box, +} + +impl Display for ResetStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ResetStmt", self.span)?; + writeln_field(f, "reset_token_span", &self.reset_token_span)?; + write_field(f, "operand", &self.operand) + } +} + +/// A sequenced block of statements. +#[derive(Clone, Debug, Default)] +pub struct Block { + /// The span. + pub span: Span, + /// The statements in the block. + pub stmts: List, +} + +impl Display for Block { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write_header(f, "Block", self.span)?; + write_indented_list(f, &self.stmts) + } +} + +#[derive(Clone, Debug, Default)] +pub struct BreakStmt { + pub span: Span, +} + +impl Display for BreakStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write_header(f, "BreakStmt", self.span) + } +} + +#[derive(Clone, Debug, Default)] +pub struct ContinueStmt { + pub span: Span, +} + +impl Display for ContinueStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write_header(f, "ContinueStmt", self.span) + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct Ident { + pub span: Span, + pub name: Rc, +} + +impl Default for Ident { + fn default() -> Self { + Ident { + span: Span::default(), + name: "".into(), + } + } +} + +impl Display for Ident { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Ident {} \"{}\"", self.span, self.name) + } +} + +#[derive(Clone, Debug)] +pub struct IndexedIdent { + pub span: Span, + pub name_span: Span, + pub index_span: Span, + pub symbol_id: SymbolId, + pub indices: List, +} + +impl Display for IndexedIdent { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "IndexedIdent", self.span)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + writeln_field(f, "name_span", &self.name_span)?; + writeln_field(f, "index_span", &self.index_span)?; + write_list_field(f, "indices", &self.indices) + } +} + +#[derive(Clone, Debug)] +pub struct ExprStmt { + pub span: Span, + pub expr: Expr, +} + +impl Display for ExprStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ExprStmt", self.span)?; + write_field(f, "expr", &self.expr) + } +} + +#[derive(Clone, Debug, Default)] +pub struct Expr { + pub span: Span, + pub kind: Box, + pub ty: super::types::Type, +} + +impl Display for Expr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "Expr", self.span)?; + writeln_field(f, "ty", &self.ty)?; + write_field(f, "kind", &self.kind) + } +} + +#[derive(Clone, Debug)] +pub struct DiscreteSet { + pub span: Span, + pub values: List, +} + +impl Display for DiscreteSet { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "DiscreteSet", self.span)?; + write_list_field(f, "values", &self.values) + } +} + +#[derive(Clone, Debug)] +pub struct IndexSet { + pub span: Span, + pub values: List, +} + +impl Display for IndexSet { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "IndexSet", self.span)?; + write_list_field(f, "values", &self.values) + } +} + +#[derive(Clone, Debug)] +pub struct RangeDefinition { + pub span: Span, + pub start: Option, + pub end: Option, + pub step: Option, +} + +impl Display for RangeDefinition { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "RangeDefinition", self.span)?; + writeln_opt_field(f, "start", self.start.as_ref())?; + writeln_opt_field(f, "step", self.step.as_ref())?; + write_opt_field(f, "end", self.end.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct QuantumGateModifier { + pub span: Span, + pub modifier_keyword_span: Span, + pub kind: GateModifierKind, +} + +impl Display for QuantumGateModifier { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "QuantumGateModifier", self.span)?; + writeln_field(f, "modifier_keyword_span", &self.modifier_keyword_span)?; + write_field(f, "kind", &self.kind) + } +} + +#[derive(Clone, Debug)] +pub enum GateModifierKind { + Inv, + Pow(Expr), + Ctrl(u32), + NegCtrl(u32), +} + +impl Display for GateModifierKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + GateModifierKind::Inv => write!(f, "Inv"), + GateModifierKind::Pow(expr) => write!(f, "Pow {expr}"), + GateModifierKind::Ctrl(ctrls) => write!(f, "Ctrl {ctrls:?}"), + GateModifierKind::NegCtrl(ctrls) => write!(f, "NegCtrl {ctrls:?}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct IntType { + pub span: Span, + pub size: Option, +} + +impl Display for IntType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "IntType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct UIntType { + pub span: Span, + pub size: Option, +} + +impl Display for UIntType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "UIntType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct FloatType { + pub span: Span, + pub size: Option, +} + +impl Display for FloatType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "FloatType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct ComplexType { + pub span: Span, + pub base_size: Option, +} + +impl Display for ComplexType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ComplexType", self.span)?; + write_opt_field(f, "base_size", self.base_size.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct AngleType { + pub span: Span, + pub size: Option, +} + +impl Display for AngleType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "AngleType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct BitType { + pub span: Span, + pub size: Option, +} + +impl Display for BitType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "BitType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct QuantumArgument { + pub span: Span, + pub expr: Option, +} + +impl Display for QuantumArgument { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "QuantumArgument", self.span)?; + write_opt_field(f, "expr", self.expr.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct Pragma { + pub span: Span, + pub identifier: Rc, + pub value: Option>, +} + +impl Display for Pragma { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let identifier = format!("\"{}\"", self.identifier); + let value = self.value.as_ref().map(|val| format!("\"{val}\"")); + writeln_header(f, "Pragma", self.span)?; + writeln_field(f, "identifier", &identifier)?; + write_opt_field(f, "value", value.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct IncludeStmt { + pub span: Span, + pub filename: String, +} + +impl Display for IncludeStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "IncludeStmt", self.span)?; + write_field(f, "filename", &self.filename) + } +} + +#[derive(Clone, Debug)] +pub struct QubitDeclaration { + pub span: Span, + pub symbol_id: SymbolId, +} + +impl Display for QubitDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "QubitDeclaration", self.span)?; + write_field(f, "symbol_id", &self.symbol_id) + } +} + +#[derive(Clone, Debug)] +pub struct QubitArrayDeclaration { + pub span: Span, + pub symbol_id: SymbolId, + pub size: u32, + pub size_span: Span, +} + +impl Display for QubitArrayDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "QubitArrayDeclaration", self.span)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + writeln_field(f, "size", &self.size)?; + write_field(f, "size_span", &self.size_span) + } +} + +#[derive(Clone, Debug)] +pub struct QuantumGateDefinition { + pub span: Span, + pub name_span: Span, + pub symbol_id: SymbolId, + pub params: Box<[SymbolId]>, + pub qubits: Box<[SymbolId]>, + pub body: Block, +} + +impl Display for QuantumGateDefinition { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "Gate", self.span)?; + writeln_field(f, "name_span", &self.name_span)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + writeln_list_field(f, "parameters", &self.params)?; + writeln_list_field(f, "qubits", &self.qubits)?; + write_field(f, "body", &self.body) + } +} + +#[derive(Clone, Debug)] +pub struct ExternDecl { + pub span: Span, + pub symbol_id: SymbolId, + pub params: Box<[crate::types::Type]>, + pub return_type: crate::types::Type, +} + +impl Display for ExternDecl { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ExternDecl", self.span)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + writeln_list_field(f, "parameters", &self.params)?; + write_field(f, "return_type", &self.return_type) + } +} + +#[derive(Clone, Debug)] +pub struct GateCall { + pub span: Span, + pub modifiers: List, + pub symbol_id: SymbolId, + pub gate_name_span: Span, + pub args: List, + pub qubits: List, + pub duration: Option, + pub classical_arity: u32, + pub quantum_arity: u32, +} + +impl Display for GateCall { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "GateCall", self.span)?; + writeln_list_field(f, "modifiers", &self.modifiers)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + writeln_field(f, "gate_name_span", &self.gate_name_span)?; + writeln_list_field(f, "args", &self.args)?; + writeln_list_field(f, "qubits", &self.qubits)?; + writeln_opt_field(f, "duration", self.duration.as_ref())?; + writeln_field(f, "classical_arity", &self.classical_arity)?; + write_field(f, "quantum_arity", &self.quantum_arity) + } +} + +#[derive(Clone, Debug)] +pub struct DelayStmt { + pub span: Span, + pub duration: Expr, + pub qubits: List, +} + +impl Display for DelayStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "DelayStmt", self.span)?; + writeln_field(f, "duration", &self.duration)?; + write_list_field(f, "qubits", &self.qubits) + } +} + +#[derive(Clone, Debug)] +pub struct BoxStmt { + pub span: Span, + pub duration: Option, + pub body: List, +} + +impl Display for BoxStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "BoxStmt", self.span)?; + writeln_opt_field(f, "duration", self.duration.as_ref())?; + write_list_field(f, "body", &self.body) + } +} + +#[derive(Clone, Debug)] +pub struct MeasureArrowStmt { + pub span: Span, + pub measurement: MeasureExpr, + pub target: Option>, +} + +impl Display for MeasureArrowStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "MeasureArrowStmt", self.span)?; + writeln_field(f, "measurement", &self.measurement)?; + write_opt_field(f, "target", self.target.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct ClassicalDeclarationStmt { + pub span: Span, + pub ty_span: Span, + pub symbol_id: SymbolId, + pub init_expr: Box, +} + +impl Display for ClassicalDeclarationStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ClassicalDeclarationStmt", self.span)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + writeln_field(f, "ty_span", &self.ty_span)?; + write_field(f, "init_expr", self.init_expr.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct InputDeclaration { + pub span: Span, + pub symbol_id: SymbolId, +} + +impl Display for InputDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "InputDeclaration", self.span)?; + write_field(f, "symbol_id", &self.symbol_id) + } +} + +#[derive(Clone, Debug)] +pub struct OutputDeclaration { + pub span: Span, + pub ty_span: Span, + pub symbol_id: SymbolId, + pub init_expr: Box, +} + +impl Display for OutputDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "OutputDeclaration", self.span)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + writeln_field(f, "ty_span", &self.ty_span)?; + write_field(f, "init_expr", &self.init_expr) + } +} + +#[derive(Clone, Debug)] +pub struct DefStmt { + pub span: Span, + pub symbol_id: SymbolId, + pub has_qubit_params: bool, + pub params: Box<[SymbolId]>, + pub body: Block, + pub return_type: crate::types::Type, +} + +impl Display for DefStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "DefStmt", self.span)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + writeln_field(f, "has_qubit_params", &self.has_qubit_params)?; + writeln_list_field(f, "parameters", &self.params)?; + writeln_field(f, "return_type", &self.return_type)?; + write_field(f, "body", &self.body) + } +} + +#[derive(Clone, Debug)] +pub struct ReturnStmt { + pub span: Span, + pub expr: Option>, +} + +impl Display for ReturnStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ReturnStmt", self.span)?; + write_opt_field(f, "expr", self.expr.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct WhileLoop { + pub span: Span, + pub condition: Expr, + pub body: Stmt, +} + +impl Display for WhileLoop { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "WhileLoop", self.span)?; + writeln_field(f, "condition", &self.condition)?; + write_field(f, "body", &self.body) + } +} + +#[derive(Clone, Debug)] +pub struct ForStmt { + pub span: Span, + pub loop_variable: SymbolId, + pub set_declaration: Box, + pub body: Stmt, +} + +impl Display for ForStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ForStmt", self.span)?; + writeln_field(f, "loop_variable", &self.loop_variable)?; + writeln_field(f, "iterable", &self.set_declaration)?; + write_field(f, "body", &self.body) + } +} + +#[derive(Clone, Debug)] +pub enum EnumerableSet { + DiscreteSet(DiscreteSet), + RangeDefinition(RangeDefinition), + Expr(Expr), +} + +impl Display for EnumerableSet { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + EnumerableSet::DiscreteSet(set) => write!(f, "{set}"), + EnumerableSet::RangeDefinition(range) => write!(f, "{range}"), + EnumerableSet::Expr(expr) => write!(f, "{expr}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct SwitchStmt { + pub span: Span, + pub target: Expr, + pub cases: List, + /// Note that `None` is quite different to `[]` in this case; the latter is + /// an explicitly empty body, whereas the absence of a default might mean + /// that the switch is inexhaustive, and a linter might want to complain. + pub default: Option, +} + +impl Display for SwitchStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "SwitchStmt", self.span)?; + writeln_field(f, "target", &self.target)?; + writeln_list_field(f, "cases", &self.cases)?; + write_opt_field(f, "default_case", self.default.as_ref()) + } +} + +#[derive(Clone, Debug)] +pub struct SwitchCase { + pub span: Span, + pub labels: List, + pub block: Block, +} + +impl Display for SwitchCase { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "SwitchCase", self.span)?; + writeln_list_field(f, "labels", &self.labels)?; + write_field(f, "block", &self.block) + } +} + +#[derive(Clone, Debug, Default)] +pub enum ExprKind { + /// An expression with invalid syntax that can't be parsed. + #[default] + Err, + Ident(SymbolId), + IndexedIdentifier(IndexedIdent), + UnaryOp(UnaryOpExpr), + BinaryOp(BinaryOpExpr), + Lit(LiteralKind), + FunctionCall(FunctionCall), + Cast(Cast), + IndexExpr(IndexExpr), + Paren(Expr), + Measure(MeasureExpr), +} + +impl Display for ExprKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + ExprKind::Err => write!(f, "Err"), + ExprKind::Ident(id) => write!(f, "SymbolId({id})"), + ExprKind::IndexedIdentifier(id) => write!(f, "{id}"), + ExprKind::UnaryOp(expr) => write!(f, "{expr}"), + ExprKind::BinaryOp(expr) => write!(f, "{expr}"), + ExprKind::Lit(lit) => write!(f, "Lit: {lit}"), + ExprKind::FunctionCall(call) => write!(f, "{call}"), + ExprKind::Cast(expr) => write!(f, "{expr}"), + ExprKind::IndexExpr(expr) => write!(f, "{expr}"), + ExprKind::Paren(expr) => write!(f, "Paren {expr}"), + ExprKind::Measure(expr) => write!(f, "{expr}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct AssignStmt { + pub span: Span, + pub symbol_id: SymbolId, + pub lhs_span: Span, + pub rhs: Expr, +} + +impl Display for AssignStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "AssignStmt", self.span)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + writeln_field(f, "lhs_span", &self.lhs_span)?; + write_field(f, "rhs", &self.rhs) + } +} + +#[derive(Clone, Debug)] +pub struct IndexedAssignStmt { + pub span: Span, + pub symbol_id: SymbolId, + pub name_span: Span, + pub indices: List, + pub rhs: Expr, +} + +impl Display for IndexedAssignStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "AssignStmt", self.span)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + writeln_field(f, "name_span", &self.name_span)?; + writeln_list_field(f, "indices", &self.indices)?; + write_field(f, "rhs", &self.rhs) + } +} + +#[derive(Clone, Debug)] +pub struct AssignOpStmt { + pub span: Span, + pub symbol_id: SymbolId, + pub indices: List, + pub op: BinOp, + pub lhs: Expr, + pub rhs: Expr, +} + +impl Display for AssignOpStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "AssignOpStmt", self.span)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + writeln_list_field(f, "indices", &self.indices)?; + writeln_field(f, "op", &self.op)?; + writeln_field(f, "lhs", &self.rhs)?; + write_field(f, "rhs", &self.rhs) + } +} + +#[derive(Clone, Debug)] +pub struct UnaryOpExpr { + pub span: Span, + pub op: UnaryOp, + pub expr: Expr, +} + +impl Display for UnaryOpExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "UnaryOpExpr", self.span)?; + writeln_field(f, "op", &self.op)?; + write_field(f, "expr", &self.expr) + } +} + +#[derive(Clone, Debug)] +pub struct BinaryOpExpr { + pub op: BinOp, + pub lhs: Expr, + pub rhs: Expr, +} + +impl BinaryOpExpr { + pub fn span(&self) -> Span { + Span { + lo: self.lhs.span.lo, + hi: self.rhs.span.hi, + } + } +} + +impl Display for BinaryOpExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "BinaryOpExpr:")?; + writeln_field(f, "op", &self.op)?; + writeln_field(f, "lhs", &self.lhs)?; + write_field(f, "rhs", &self.rhs) + } +} + +#[derive(Clone, Debug)] +pub struct FunctionCall { + pub span: Span, + pub fn_name_span: Span, + pub symbol_id: SymbolId, + pub args: List, +} + +impl Display for FunctionCall { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "FunctionCall", self.span)?; + writeln_field(f, "fn_name_span", &self.fn_name_span)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + write_list_field(f, "args", &self.args) + } +} + +#[derive(Clone, Debug)] +pub struct Cast { + pub span: Span, + pub ty: crate::semantic::types::Type, + pub expr: Expr, +} + +impl Display for Cast { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "Cast", self.span)?; + writeln_field(f, "ty", &self.ty)?; + write_field(f, "expr", &self.expr) + } +} + +#[derive(Clone, Debug)] +pub struct IndexExpr { + pub span: Span, + pub collection: Expr, + pub index: IndexElement, +} + +impl Display for IndexExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "IndexExpr", self.span)?; + writeln_field(f, "collection", &self.collection)?; + write_field(f, "index", &self.index) + } +} + +#[derive(Clone, Debug)] +pub enum LiteralKind { + Angle(Angle), + Array(List), + Bitstring(BigInt, u32), + Bool(bool), + Duration(f64, TimeUnit), + Float(f64), + Complex(f64, f64), + Int(i64), + BigInt(BigInt), + String(Rc), + Bit(bool), +} + +impl Display for LiteralKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + LiteralKind::Array(exprs) => write_list_field(f, "Array", exprs), + LiteralKind::Bitstring(value, width) => { + let width = *width as usize; + write!(f, "Bitstring(\"{:0>width$}\")", value.to_str_radix(2)) + } + LiteralKind::Angle(a) => write!(f, "Angle({a})"), + LiteralKind::Bit(b) => write!(f, "Bit({:?})", u8::from(*b)), + LiteralKind::Bool(b) => write!(f, "Bool({b:?})"), + LiteralKind::Complex(real, imag) => write!(f, "Complex({real:?}, {imag:?})"), + LiteralKind::Duration(value, unit) => { + write!(f, "Duration({value:?}, {unit:?})") + } + LiteralKind::Float(value) => write!(f, "Float({value:?})"), + LiteralKind::Int(i) => write!(f, "Int({i:?})"), + LiteralKind::BigInt(i) => write!(f, "BigInt({i:?})"), + LiteralKind::String(s) => write!(f, "String({s:?})"), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Version { + pub major: u32, + pub minor: Option, + pub span: Span, +} + +impl PartialEq for Version { + fn eq(&self, other: &Self) -> bool { + // If the minor versions are missing + // we assume them to be 0. + let self_minor = self.minor.unwrap_or_default(); + let other_minor = other.minor.unwrap_or_default(); + + // Then we check if the major and minor version are equal. + self.major == other.major && self_minor == other_minor + } +} + +impl PartialOrd for Version { + fn partial_cmp(&self, other: &Self) -> Option { + // If the minor versions are missing + // we assume them to be 0. + let self_minor = self.minor.unwrap_or_default(); + let other_minor = other.minor.unwrap_or_default(); + + // We compare the major versions. + match self.major.partial_cmp(&other.major) { + // If they are equal, we disambiguate + // using the minor versions. + Some(core::cmp::Ordering::Equal) => self_minor.partial_cmp(&other_minor), + // Else, we return their ordering. + ord => ord, + } + } +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.minor { + Some(minor) => write!(f, "{}.{}", self.major, minor), + None => write!(f, "{}", self.major), + } + } +} + +#[derive(Clone, Debug)] +pub enum IndexElement { + DiscreteSet(DiscreteSet), + IndexSet(IndexSet), +} + +impl IndexElement { + pub fn span(&self) -> Span { + match self { + IndexElement::DiscreteSet(discrete_set) => discrete_set.span, + IndexElement::IndexSet(index_set) => index_set.span, + } + } +} + +impl Display for IndexElement { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + IndexElement::DiscreteSet(set) => write!(f, "{set}"), + IndexElement::IndexSet(set) => write!(f, "{set}"), + } + } +} + +#[derive(Clone, Debug)] +pub enum IndexSetItem { + RangeDefinition(RangeDefinition), + Expr(Expr), + Err, +} + +impl Display for IndexSetItem { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + IndexSetItem::RangeDefinition(range) => write!(f, "{range}"), + IndexSetItem::Expr(expr) => write!(f, "{expr}"), + IndexSetItem::Err => write!(f, "Err"), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum TimeUnit { + Dt, + /// Nanoseconds. + Ns, + /// Microseconds. + Us, + /// Milliseconds. + Ms, + /// Seconds. + S, +} + +impl Display for TimeUnit { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + TimeUnit::Dt => write!(f, "dt"), + TimeUnit::Ns => write!(f, "ns"), + TimeUnit::Us => write!(f, "us"), + TimeUnit::Ms => write!(f, "ms"), + TimeUnit::S => write!(f, "s"), + } + } +} + +impl From for TimeUnit { + fn from(value: crate::parser::ast::TimeUnit) -> Self { + match value { + syntax::TimeUnit::Dt => Self::Dt, + syntax::TimeUnit::Ns => Self::Ns, + syntax::TimeUnit::Us => Self::Us, + syntax::TimeUnit::Ms => Self::Ms, + syntax::TimeUnit::S => Self::S, + } + } +} + +#[derive(Clone, Debug)] +pub struct EndStmt { + pub span: Span, +} + +impl Display for EndStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "End {}", self.span) + } +} diff --git a/compiler/qsc_qasm/src/semantic/const_eval.rs b/compiler/qsc_qasm/src/semantic/const_eval.rs new file mode 100644 index 0000000000..d43931ddd5 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/const_eval.rs @@ -0,0 +1,776 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! This module allows us to perform const evaluation at lowering time. +//! The purpose of this is to be able to compute the widths of types +//! and sizes of arrays. Therefore, those are the only const evaluation +//! paths that are implemented. + +use super::ast::{ + BinOp, BinaryOpExpr, Cast, Expr, ExprKind, FunctionCall, IndexExpr, IndexedIdent, LiteralKind, + UnaryOp, UnaryOpExpr, +}; +use super::symbols::SymbolId; +use crate::semantic::Lowerer; +use crate::stdlib::angle; +use crate::{ + convert::safe_i64_to_f64, + semantic::types::{ArrayDimensions, Type}, +}; +use miette::Diagnostic; +use num_bigint::BigInt; +use qsc_data_structures::span::Span; +use thiserror::Error; + +#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] +pub enum ConstEvalError { + #[error("expression must be const")] + #[diagnostic(code("Qasm.Compiler.ExprMustBeConst"))] + ExprMustBeConst(#[label] Span), + #[error("uint expression must evaluate to a non-negative value, but it evaluated to {0}")] + #[diagnostic(code("Qasm.Compiler.NegativeUIntValue"))] + NegativeUIntValue(i64, #[label] Span), + #[error("{0} doesn't fit in {1}")] + #[diagnostic(code("Qasm.Compiler.ValueOverflow"))] + ValueOverflow(String, String, #[label] Span), +} + +impl ConstEvalError {} + +impl Expr { + /// Tries to evaluate the expression. It takes the current `Lowerer` as + /// the evaluation context to resolve symbols and push errors in case + /// of failure. + pub(crate) fn const_eval(&self, ctx: &mut Lowerer) -> Option { + let ty = &self.ty; + if !ty.is_const() { + ctx.push_const_eval_error(ConstEvalError::ExprMustBeConst(self.span)); + return None; + } + + match &*self.kind { + ExprKind::Ident(symbol_id) => symbol_id.const_eval(ctx), + ExprKind::IndexedIdentifier(indexed_ident) => indexed_ident.const_eval(ctx), + ExprKind::UnaryOp(unary_op_expr) => unary_op_expr.const_eval(ctx), + ExprKind::BinaryOp(binary_op_expr) => binary_op_expr.const_eval(ctx), + ExprKind::Lit(literal_kind) => Some(literal_kind.clone()), + ExprKind::FunctionCall(function_call) => function_call.const_eval(ctx, ty), + ExprKind::Cast(cast) => cast.const_eval(ctx), + ExprKind::IndexExpr(index_expr) => index_expr.const_eval(ctx, ty), + ExprKind::Paren(expr) => expr.const_eval(ctx), + // Measurements are non-const, so we don't need to implement them. + ExprKind::Measure(_) | ExprKind::Err => None, + } + } +} + +impl SymbolId { + fn const_eval(self, ctx: &mut Lowerer) -> Option { + let symbol = ctx.symbols[self].clone(); + symbol + .get_const_expr() // get the value of the symbol (an Expr) + .const_eval(ctx) // const eval that Expr + } +} + +impl IndexedIdent { + #[allow(clippy::unused_self)] + fn const_eval(&self, _ctx: &mut Lowerer) -> Option { + None + } +} + +/// A helper macro for evaluating unary and binary operations of values +/// wrapped in the `semantic::LiteralKind` enum. Unwraps the value in the +/// `LiteralKind` and rewraps it in another `LiteralKind` variant while +/// applying some operation to it. +macro_rules! rewrap_lit { + // This pattern is used for unary expressions. + ($lit:expr, $pat:pat, $out:expr) => { + if let $pat = $lit { + Some($out) + } else { + unreachable!("if we hit this there is a bug in the type system") + } + }; +} + +impl UnaryOpExpr { + fn const_eval(&self, ctx: &mut Lowerer) -> Option { + use LiteralKind::{Angle, Bit, Bitstring, Bool, Float, Int}; + let operand_ty = &self.expr.ty; + let lit = self.expr.const_eval(ctx)?; + + match &self.op { + UnaryOp::Neg => match operand_ty { + Type::Int(..) => rewrap_lit!(lit, Int(val), Int(-val)), + Type::Float(..) => rewrap_lit!(lit, Float(val), Float(-val)), + Type::Angle(..) => rewrap_lit!(lit, Angle(val), Angle(-val)), + _ => None, + }, + UnaryOp::NotB => match operand_ty { + Type::Int(size, _) | Type::UInt(size, _) => rewrap_lit!(lit, Int(val), { + let mask = (1 << (*size)?) - 1; + Int(!val & mask) + }), + Type::Angle(..) => rewrap_lit!(lit, Angle(val), Angle(!val)), + Type::Bit(..) => rewrap_lit!(lit, Bit(val), Bit(!val)), + Type::BitArray(..) => { + rewrap_lit!(lit, Bitstring(val, size), { + let mask = BigInt::from((1 << size) - 1); + Bitstring(!val & mask, size) + }) + } + // Angle is treated like a unit in the QASM3 Spec, but we are currently + // treating it as a float, so we can't apply bitwise negation to it. + _ => None, + }, + UnaryOp::NotL => match operand_ty { + Type::Bool(..) => rewrap_lit!(lit, Bool(val), Bool(!val)), + _ => None, + }, + } + } +} + +/// By this point it is guaranteed that the lhs and rhs are of the same type. +/// Any conversions have been made explicit by inserting casts during lowering. +/// Note: the type of the binary expression doesn't need to be the same as the +/// operands, for example, comparison operators can have integer operands +/// but their type is boolean. +/// We can write a simpler implementation under that assumption. +/// +/// There are some exceptions: +/// 1. The rhs in Shl and Shr must be of type `UInt`. +/// 2. Angle can be multiplied and divided by `UInt`. +fn assert_binary_op_ty_invariant(op: BinOp, lhs_ty: &Type, rhs_ty: &Type) { + // Exceptions: + if matches!( + (op, lhs_ty, rhs_ty), + (BinOp::Shl | BinOp::Shr, _, _) + | (BinOp::Mul | BinOp::Div, Type::Angle(..), Type::UInt(..)) + | (BinOp::Mul, Type::UInt(..), Type::Angle(..)) + ) { + return; + } + + assert_eq!(lhs_ty, rhs_ty); +} + +impl BinaryOpExpr { + #[allow(clippy::too_many_lines)] + fn const_eval(&self, ctx: &mut Lowerer) -> Option { + use LiteralKind::{Angle, Bit, Bitstring, Bool, Float, Int}; + + let lhs = self.lhs.const_eval(ctx); + let rhs = self.rhs.const_eval(ctx); + let (lhs, rhs) = (lhs?, rhs?); + let lhs_ty = &self.lhs.ty; + + assert_binary_op_ty_invariant(self.op, &self.lhs.ty, &self.rhs.ty); + + match &self.op { + // Bit Shifts + BinOp::Shl => { + assert!( + matches!(self.rhs.ty, Type::UInt(..)), + "shift left rhs should have been casted to uint during lowering" + ); + let LiteralKind::Int(rhs) = rhs else { + unreachable!("if we hit this there is a bug in the type system"); + }; + if rhs < 0 { + ctx.push_const_eval_error(ConstEvalError::NegativeUIntValue( + rhs, + self.rhs.span, + )); + return None; + } + + match lhs_ty { + Type::UInt(Some(size), _) => rewrap_lit!(lhs, Int(lhs), { + let mask = (1 << size) - 1; + Int((lhs << rhs) & mask) + }), + Type::UInt(..) => rewrap_lit!(lhs, Int(lhs), Int(lhs << rhs)), + Type::Angle(..) => { + rewrap_lit!(lhs, Angle(lhs), Angle(lhs << rhs)) + } + Type::Bit(..) => rewrap_lit!(lhs, Bit(lhs), { + // The Spec says "The shift operators shift bits off the end." + // Therefore if the rhs is > 0 the value becomes zero. + Bit(rhs == 0 && lhs) + }), + Type::BitArray(..) => { + rewrap_lit!(lhs, Bitstring(lhs, size), { + let mask = BigInt::from((1 << size) - 1); + Bitstring((lhs << rhs) & mask, size) + }) + } + _ => None, + } + } + BinOp::Shr => { + assert!( + matches!(self.rhs.ty, Type::UInt(..)), + "shift right rhs should have been casted to uint during lowering" + ); + let LiteralKind::Int(rhs) = rhs else { + unreachable!("if we hit this there is a bug in the type system"); + }; + if rhs < 0 { + ctx.push_const_eval_error(ConstEvalError::NegativeUIntValue( + rhs, + self.rhs.span, + )); + return None; + } + + match lhs_ty { + Type::UInt(..) => rewrap_lit!(lhs, Int(lhs), Int(lhs >> rhs)), + Type::Angle(..) => { + rewrap_lit!(lhs, Angle(lhs), Angle(lhs >> rhs)) + } + Type::Bit(..) => rewrap_lit!(lhs, Bit(lhs), { + // The Spec says "The shift operators shift bits off the end." + // Therefore if the rhs is > 0 the value becomes zero. + Bit(rhs == 0 && lhs) + }), + Type::BitArray(..) => { + rewrap_lit!(lhs, Bitstring(lhs, size), Bitstring(lhs >> rhs, size)) + } + _ => None, + } + } + + // Bitwise + BinOp::AndB => match lhs_ty { + Type::UInt(..) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs & rhs)), + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Angle(rhs)), Angle(lhs & rhs)) + } + Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Bit(rhs)), Bit(lhs & rhs)), + Type::BitArray(..) => rewrap_lit!( + (lhs, rhs), + (Bitstring(lhs, lsize), Bitstring(rhs, rsize)), + Bitstring(lhs & rhs, lsize.min(rsize)) + ), + _ => None, + }, + BinOp::OrB => match lhs_ty { + Type::UInt(..) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs | rhs)), + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Angle(rhs)), Angle(lhs | rhs)) + } + Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Bit(rhs)), Bit(lhs | rhs)), + Type::BitArray(..) => rewrap_lit!( + (lhs, rhs), + (Bitstring(lhs, lsize), Bitstring(rhs, rsize)), + Bitstring(lhs | rhs, lsize.max(rsize)) + ), + _ => None, + }, + BinOp::XorB => match lhs_ty { + Type::UInt(..) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs ^ rhs)), + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Angle(rhs)), Angle(lhs ^ rhs)) + } + Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Bit(rhs)), Bit(lhs ^ rhs)), + Type::BitArray(..) => rewrap_lit!( + (lhs, rhs), + (Bitstring(lhs, lsize), Bitstring(rhs, rsize)), + Bitstring(lhs ^ rhs, lsize.max(rsize)) + ), + _ => None, + }, + + // Logical + BinOp::AndL => match lhs_ty { + Type::Bool(..) => rewrap_lit!((lhs, rhs), (Bool(lhs), Bool(rhs)), Bool(lhs && rhs)), + _ => None, + }, + BinOp::OrL => match lhs_ty { + Type::Bool(..) => rewrap_lit!((lhs, rhs), (Bool(lhs), Bool(rhs)), Bool(lhs || rhs)), + _ => None, + }, + + // Comparison + BinOp::Eq => match lhs_ty { + Type::Int(..) | Type::UInt(..) => { + rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Bool(lhs == rhs)) + } + Type::Float(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), { + #[allow(clippy::float_cmp)] + Bool(lhs == rhs) + }) + } + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Angle(rhs)), Bool(lhs == rhs)) + } + Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Bit(rhs)), Bool(lhs == rhs)), + Type::BitArray(..) => rewrap_lit!( + (lhs, rhs), + (Bitstring(lhs, _), Bitstring(rhs, _)), + Bool(lhs == rhs) + ), + _ => None, + }, + BinOp::Neq => match lhs_ty { + Type::Int(..) | Type::UInt(..) => { + rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Bool(lhs != rhs)) + } + Type::Float(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), { + #[allow(clippy::float_cmp)] + Bool(lhs != rhs) + }) + } + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Angle(rhs)), Bool(lhs != rhs)) + } + Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Bit(rhs)), Bool(lhs != rhs)), + Type::BitArray(..) => rewrap_lit!( + (lhs, rhs), + (Bitstring(lhs, _), Bitstring(rhs, _)), + Bool(lhs != rhs) + ), + _ => None, + }, + BinOp::Gt => match lhs_ty { + Type::Int(..) | Type::UInt(..) => { + rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Bool(lhs > rhs)) + } + Type::Float(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Bool(lhs > rhs)) + } + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Angle(rhs)), Bool(lhs > rhs)) + } + // This was originally `lhs > rhs` but clippy suggested this expression. + Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Bit(rhs)), Bool(lhs && !rhs)), + Type::BitArray(..) => rewrap_lit!( + (lhs, rhs), + (Bitstring(lhs, _), Bitstring(rhs, _)), + Bool(lhs > rhs) + ), + _ => None, + }, + BinOp::Gte => match lhs_ty { + Type::Int(..) | Type::UInt(..) => { + rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Bool(lhs >= rhs)) + } + Type::Float(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Bool(lhs >= rhs)) + } + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Angle(rhs)), Bool(lhs >= rhs)) + } + Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Bit(rhs)), Bool(lhs >= rhs)), + Type::BitArray(..) => rewrap_lit!( + (lhs, rhs), + (Bitstring(lhs, _), Bitstring(rhs, _)), + Bool(lhs >= rhs) + ), + _ => None, + }, + BinOp::Lt => match lhs_ty { + Type::Int(..) | Type::UInt(..) => { + rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Bool(lhs < rhs)) + } + Type::Float(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Bool(lhs < rhs)) + } + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Angle(rhs)), Bool(lhs < rhs)) + } + // This was originally `lhs < rhs` but clippy suggested this expression. + Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Bit(rhs)), Bool(!lhs & rhs)), + Type::BitArray(..) => rewrap_lit!( + (lhs, rhs), + (Bitstring(lhs, _), Bitstring(rhs, _)), + Bool(lhs < rhs) + ), + _ => None, + }, + BinOp::Lte => match lhs_ty { + Type::Int(..) | Type::UInt(..) => { + rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Bool(lhs <= rhs)) + } + Type::Float(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Bool(lhs <= rhs)) + } + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Angle(rhs)), Bool(lhs <= rhs)) + } + Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Bit(rhs)), Bool(lhs <= rhs)), + Type::BitArray(..) => rewrap_lit!( + (lhs, rhs), + (Bitstring(lhs, _), Bitstring(rhs, _)), + Bool(lhs <= rhs) + ), + _ => None, + }, + + // Arithmetic + BinOp::Add => match lhs_ty { + Type::Int(..) | Type::UInt(..) => { + rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs + rhs)) + } + Type::Float(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Float(lhs + rhs)) + } + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Angle(rhs)), Angle(lhs + rhs)) + } + _ => None, + }, + BinOp::Sub => match lhs_ty { + Type::Int(..) | Type::UInt(..) => { + rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs - rhs)) + } + Type::Float(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Float(lhs - rhs)) + } + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Angle(rhs)), Angle(lhs - rhs)) + } + _ => None, + }, + BinOp::Mul => match lhs_ty { + Type::Int(..) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs * rhs)), + Type::UInt(..) => match &self.rhs.ty { + Type::UInt(..) => { + rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs * rhs)) + } + Type::Angle(..) => rewrap_lit!((lhs, rhs), (Int(lhs), Angle(rhs)), { + if lhs < 0 { + ctx.push_const_eval_error(ConstEvalError::NegativeUIntValue( + lhs, + self.lhs.span, + )); + return None; + } + #[allow(clippy::cast_sign_loss)] + Angle(rhs * u64::try_from(lhs).ok()?) + }), + + _ => None, + }, + Type::Float(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Float(lhs * rhs)) + } + Type::Angle(..) => { + rewrap_lit!( + (lhs, rhs), + (Angle(lhs), Int(rhs)), + Angle(lhs * u64::try_from(rhs).ok()?) + ) + } + _ => None, + }, + BinOp::Div => match lhs_ty { + Type::Int(..) | Type::UInt(..) => { + rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs / rhs)) + } + Type::Float(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Float(lhs / rhs)) + } + Type::Angle(..) => match &self.rhs.ty { + Type::UInt(..) => { + rewrap_lit!( + (lhs, rhs), + (Angle(lhs), Int(rhs)), + Angle(lhs / u64::try_from(rhs).ok()?) + ) + } + Type::Angle(..) => { + rewrap_lit!( + (lhs, rhs), + (Angle(lhs), Angle(rhs)), + Int((lhs / rhs).try_into().ok()?) + ) + } + _ => None, + }, + _ => None, + }, + BinOp::Mod => match lhs_ty { + Type::Int(..) | Type::UInt(..) => { + rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs % rhs)) + } + _ => None, + }, + BinOp::Exp => match lhs_ty { + Type::Int(..) | Type::UInt(..) => { + rewrap_lit!( + (lhs, rhs), + (Int(lhs), Int(rhs)), + Int(lhs.wrapping_pow(u32::try_from(rhs).ok()?)) + ) + } + Type::Float(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Float(lhs.powf(rhs))) + } + _ => None, + }, + } + } +} + +impl FunctionCall { + #[allow(clippy::unused_self)] + fn const_eval(&self, _ctx: &mut Lowerer, _ty: &Type) -> Option { + None + } +} + +impl IndexExpr { + #[allow(clippy::unused_self)] + fn const_eval(&self, _ctx: &mut Lowerer, _ty: &Type) -> Option { + None + } +} + +impl Cast { + fn const_eval(&self, ctx: &mut Lowerer) -> Option { + match &self.ty { + Type::Bool(..) => cast_to_bool(self, ctx), + Type::Int(..) => cast_to_int(self, ctx), + Type::UInt(..) => cast_to_uint(self, ctx), + Type::Float(..) => cast_to_float(self, ctx), + Type::Angle(..) => cast_to_angle(self, ctx), + Type::Bit(..) => cast_to_bit(self, ctx), + Type::BitArray(..) => cast_to_bitarray(self, ctx), + _ => None, + } + } +} + +/// +---------------+-----------------------------------------+ +/// | Allowed casts | Casting from | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | Casting to | bool | int | uint | float | angle | bit | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | bool | - | Yes | Yes | Yes | Yes | Yes | +/// +---------------+------+-----+------+-------+-------+-----+ +fn cast_to_bool(cast: &Cast, ctx: &mut Lowerer) -> Option { + use LiteralKind::{Angle, Bit, Bitstring, Bool, Float, Int}; + let lit = cast.expr.const_eval(ctx)?; + + match &cast.expr.ty { + Type::Bool(..) => Some(lit), + Type::Bit(..) => rewrap_lit!(lit, Bit(val), Bool(val)), + Type::BitArray(..) => rewrap_lit!(lit, Bitstring(val, _), Bool(val != BigInt::ZERO)), + Type::Int(..) | Type::UInt(..) => rewrap_lit!(lit, Int(val), Bool(val != 0)), + Type::Float(..) => rewrap_lit!(lit, Float(val), Bool(val != 0.0)), + Type::Angle(..) => rewrap_lit!(lit, Angle(val), Bool(val.into())), + _ => None, + } +} + +/// +---------------+-----------------------------------------+ +/// | Allowed casts | Casting from | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | Casting to | bool | int | uint | float | angle | bit | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | int | Yes | - | Yes | Yes | No | Yes | +/// +---------------+------+-----+------+-------+-------+-----+ +fn cast_to_int(cast: &Cast, ctx: &mut Lowerer) -> Option { + use LiteralKind::{Bit, Bitstring, Bool, Float, Int}; + let lit = cast.expr.const_eval(ctx)?; + + match &cast.expr.ty { + Type::Bool(..) => rewrap_lit!(lit, Bool(val), Int(i64::from(val))), + Type::Bit(..) => rewrap_lit!(lit, Bit(val), Int(i64::from(val))), + Type::BitArray(..) => { + rewrap_lit!(lit, Bitstring(val, _), Int(i64::try_from(val).ok()?)) + } + // UInt Overflowing behavior. + // This is tricky because the inner representation of UInt + // is already an i64. Therefore, there is nothing to do. + Type::Int(..) | Type::UInt(..) => Some(lit), + Type::Float(..) => rewrap_lit!(lit, Float(val), { + #[allow(clippy::cast_possible_truncation)] + Int(val as i64) + }), + _ => None, + } +} + +/// +---------------+-----------------------------------------+ +/// | Allowed casts | Casting from | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | Casting to | bool | int | uint | float | angle | bit | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | uint | Yes | Yes | - | Yes | No | Yes | +/// +---------------+------+-----+------+-------+-------+-----+ +fn cast_to_uint(cast: &Cast, ctx: &mut Lowerer) -> Option { + use LiteralKind::{Bit, Bitstring, Bool, Float, Int}; + let lit = cast.expr.const_eval(ctx)?; + + match &cast.expr.ty { + Type::Bool(..) => rewrap_lit!(lit, Bool(val), Int(i64::from(val))), + Type::Bit(..) => rewrap_lit!(lit, Bit(val), Int(i64::from(val))), + Type::BitArray(..) => { + rewrap_lit!(lit, Bitstring(val, _), Int(i64::try_from(val).ok()?)) + } + // UInt Overflowing behavior. + // This is tricky because the inner representation of UInt + // is already an i64. Therefore, there is nothing to do. + Type::Int(..) | Type::UInt(..) => Some(lit), + Type::Float(..) => rewrap_lit!(lit, Float(val), { + #[allow(clippy::cast_possible_truncation)] + Int(val as i64) + }), + _ => None, + } +} + +/// +---------------+-----------------------------------------+ +/// | Allowed casts | Casting from | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | Casting to | bool | int | uint | float | angle | bit | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | float | Yes | Yes | Yes | - | No | No | +/// +---------------+------+-----+------+-------+-------+-----+ +fn cast_to_float(cast: &Cast, ctx: &mut Lowerer) -> Option { + use LiteralKind::{Bool, Float, Int}; + let lit = cast.expr.const_eval(ctx)?; + + match &cast.expr.ty { + Type::Bool(..) => rewrap_lit!(lit, Bool(val), Float(if val { 1.0 } else { 0.0 })), + Type::Int(..) | Type::UInt(..) => rewrap_lit!(lit, Int(val), { + #[allow(clippy::cast_precision_loss)] + Float(safe_i64_to_f64(val)?) + }), + Type::Float(..) => Some(lit), + _ => None, + } +} + +/// +---------------+-----------------------------------------+ +/// | Allowed casts | Casting from | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | Casting to | bool | int | uint | float | angle | bit | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | angle | No | No | No | Yes | - | Yes | +/// +---------------+------+-----+------+-------+-------+-----+ +fn cast_to_angle(cast: &Cast, ctx: &mut Lowerer) -> Option { + use LiteralKind::{Angle, Bit, Bitstring, Float}; + let lit = cast.expr.const_eval(ctx)?; + + match &cast.expr.ty { + Type::Float(size, _) => rewrap_lit!( + lit, + Float(val), + Angle(angle::Angle::from_f64_maybe_sized(val, *size)) + ), + Type::Angle(..) => rewrap_lit!( + lit, + Angle(val), + Angle(val.cast_to_maybe_sized(cast.ty.width())) + ), + Type::Bit(..) => rewrap_lit!( + lit, + Bit(val), + Angle(angle::Angle { + value: val.into(), + size: 1 + }) + ), + Type::BitArray(..) => rewrap_lit!( + lit, + Bitstring(val, size), + Angle(angle::Angle { + value: val.try_into().ok()?, + size + }) + ), + _ => None, + } +} + +/// +---------------+-----------------------------------------+ +/// | Allowed casts | Casting from | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | Casting to | bool | int | uint | float | angle | bit | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | bit | Yes | Yes | Yes | No | Yes | - | +/// +---------------+------+-----+------+-------+-------+-----+ +fn cast_to_bit(cast: &Cast, ctx: &mut Lowerer) -> Option { + use LiteralKind::{Angle, Bit, Bool, Int}; + let lit = cast.expr.const_eval(ctx)?; + + match &cast.expr.ty { + Type::Bool(..) => rewrap_lit!(lit, Bool(val), Bit(val)), + Type::Bit(..) => Some(lit), + Type::Int(..) | Type::UInt(..) => rewrap_lit!(lit, Int(val), Bit(val != 0)), + Type::Angle(..) => rewrap_lit!(lit, Angle(val), Bit(val.value != 0)), + _ => None, + } +} + +/// +---------------+-----------------------------------------+ +/// | Allowed casts | Casting from | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | Casting to | bool | int | uint | float | angle | bit | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | bitarray | Yes | Yes | Yes | No | Yes | - | +/// +---------------+------+-----+------+-------+-------+-----+ +fn cast_to_bitarray(cast: &Cast, ctx: &mut Lowerer) -> Option { + use LiteralKind::{Angle, Bit, Bitstring, Bool, Int}; + let lit = cast.expr.const_eval(ctx)?; + + let Type::BitArray(dims, _) = &cast.ty else { + unreachable!("we got here after matching Type::BitArray in Cast::const_eval"); + }; + + let ArrayDimensions::One(size) = dims else { + ctx.push_unsupported_error_message("multidimensional arrays", cast.span); + return None; + }; + let size = *size; + + match &cast.expr.ty { + Type::Bool(..) => rewrap_lit!(lit, Bool(val), Bitstring(BigInt::from(val), size)), + Type::Angle(..) => rewrap_lit!(lit, Angle(val), { + let new_val = val.cast_to_maybe_sized(Some(size)); + Bitstring(new_val.value.into(), size) + }), + Type::Bit(..) => rewrap_lit!(lit, Bit(val), Bitstring(BigInt::from(val), size)), + Type::BitArray(..) => rewrap_lit!(lit, Bitstring(val, rhs_size), { + if rhs_size > size { + ctx.push_const_eval_error(ConstEvalError::ValueOverflow( + cast.expr.ty.to_string(), + cast.ty.to_string(), + cast.span, + )); + return None; + } + Bitstring(val, size) + }), + Type::Int(..) | Type::UInt(..) => rewrap_lit!(lit, Int(val), { + let actual_bits = number_of_bits(val); + if actual_bits > size { + ctx.push_const_eval_error(ConstEvalError::ValueOverflow( + cast.expr.ty.to_string(), + cast.ty.to_string(), + cast.span, + )); + return None; + } + Bitstring(BigInt::from(val), size) + }), + _ => None, + } +} + +fn number_of_bits(mut val: i64) -> u32 { + let mut bits = 0; + while val != 0 { + val >>= 1; + bits += 1; + } + bits +} diff --git a/compiler/qsc_qasm/src/semantic/error.rs b/compiler/qsc_qasm/src/semantic/error.rs new file mode 100644 index 0000000000..c1dd88acec --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/error.rs @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use miette::Diagnostic; +use qsc_data_structures::span::Span; +use thiserror::Error; + +#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] +#[error(transparent)] +#[diagnostic(transparent)] +pub struct Error(pub SemanticErrorKind); + +/// Represents the kind of semantic error that occurred during lowering of a QASM file(s). +/// For the most part, these errors are fatal and prevent compilation and are +/// safety checks to ensure that the QASM code is valid. +/// +/// We can't use the semantics library for this: +/// - it is unsafe to use (heavy use of panic and unwrap) +/// - it is missing many language features +#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] +pub enum SemanticErrorKind { + #[error("annotation missing target statement")] + #[diagnostic(code("Qasm.Lowerer.AnnotationWithoutStatement"))] + AnnotationWithoutStatement(#[label] Span), + #[error("array literals are only allowed in classical declarations")] + #[diagnostic(code("Qasm.Lowerer.ArrayLiteralInNonClassicalDecl"))] + ArrayLiteralInNonClassicalDecl(#[label] Span), + #[error("array size must be a non-negative integer const expression")] + #[diagnostic(code("Qasm.Lowerer.ArraySizeMustBeNonNegativeConstExpr"))] + ArraySizeMustBeNonNegativeConstExpr(#[label] Span), + #[error("calibration statements are not supported: {0}")] + #[diagnostic(code("Qasm.Lowerer.CalibrationsNotSupported"))] + CalibrationsNotSupported(String, #[label] Span), + #[error("cannot alias type {0}. Only qubit and qubit[] can be aliased")] + #[diagnostic(code("Qasm.Lowerer.CannotAliasType"))] + CannotAliasType(String, #[label] Span), + #[error("cannot apply operator {0} to types {1} and {2}")] + #[diagnostic(code("Qasm.Lowerer.CannotApplyOperatorToTypes"))] + CannotApplyOperatorToTypes(String, String, String, #[label] Span), + #[error("cannot assign a value of {0} type to a classical variable of {1} type")] + #[diagnostic(code("Qasm.Lowerer.CannotAssignToType"))] + CannotAssignToType(String, String, #[label] Span), + #[error("cannot call an expression that is not a function")] + #[diagnostic(code("Qasm.Lowerer.CannotCallNonFunction"))] + CannotCallNonFunction(#[label] Span), + #[error("cannot call a gate that is not a gate")] + #[diagnostic(code("Qasm.Lowerer.CannotCallNonGate"))] + CannotCallNonGate(#[label] Span), + #[error("cannot cast expression of type {0} to type {1}")] + #[diagnostic(code("Qasm.Lowerer.CannotCast"))] + CannotCast(String, String, #[label] Span), + #[error("cannot cast literal expression of type {0} to type {1}")] + #[diagnostic(code("Qasm.Lowerer.CannotCastLiteral"))] + CannotCastLiteral(String, String, #[label] Span), + #[error("cannot index variables of type {0}")] + #[diagnostic(code("Qasm.Lowerer.CannotIndexType"))] + CannotIndexType(String, #[label] Span), + #[error("cannot update const variable {0}")] + #[diagnostic(help("mutable variables must be declared without the keyword `const`"))] + #[diagnostic(code("Qasm.Lowerer.CannotUpdateConstVariable"))] + CannotUpdateConstVariable(String, #[label] Span), + #[error("cannot cast expression of type {0} to type {1} as it would cause truncation")] + #[diagnostic(code("Qasm.Lowerer.CastWouldCauseTruncation"))] + CastWouldCauseTruncation(String, String, #[label] Span), + #[error("invalid classical statement in box")] + #[diagnostic(code("Qasm.Lowerer.ClassicalStmtInBox"))] + ClassicalStmtInBox(#[label] Span), + #[error("complex numbers in assignment binary expressions are not yet supported")] + #[diagnostic(code("Qasm.Lowerer.ComplexBinaryAssignment"))] + ComplexBinaryAssignment(#[label] Span), + #[error("designator must be a positive literal integer")] + #[diagnostic(code("Qasm.Lowerer.DesignatorMustBePositiveIntLiteral"))] + DesignatorMustBePositiveIntLiteral(#[label] Span), + #[error("def declarations must be done in global scope")] + #[diagnostic(code("Qasm.Lowerer.DefDeclarationInNonGlobalScope"))] + DefDeclarationInNonGlobalScope(#[label] Span), + #[error("designator is too large")] + #[diagnostic(code("Qasm.Lowerer.DesignatorTooLarge"))] + DesignatorTooLarge(#[label] Span), + #[error("{0} must be a const expression")] + #[diagnostic(code("Qasm.Lowerer.ExprMustBeConst"))] + ExprMustBeConst(String, #[label] Span), + #[error("{0} must fit in a u32")] + #[diagnostic(code("Qasm.Lowerer.ExprMustFitInU32"))] + ExprMustFitInU32(String, #[label] Span), + #[error("extern declarations must be done in global scope")] + #[diagnostic(code("Qasm.Lowerer.DefDeclarationInNonGlobalScope"))] + ExternDeclarationInNonGlobalScope(#[label] Span), + #[error("failed to compile all expressions in expression list")] + #[diagnostic(code("Qasm.Lowerer.FailedToCompileExpressionList"))] + FailedToCompileExpressionList(#[label] Span), + #[error("for iterable must have a set expression, range expression, or iterable expression")] + #[diagnostic(code("Qasm.Lowerer.ForIterableInvalidExpression"))] + ForIterableInvalidExpression(#[label] Span), + #[error("for statements must have a body or statement")] + #[diagnostic(code("Qasm.Lowerer.ForStatementsMustHaveABodyOrStatement"))] + ForStatementsMustHaveABodyOrStatement(#[label] Span), + #[error("if statement missing {0} expression")] + #[diagnostic(code("Qasm.Lowerer.IfStmtMissingExpression"))] + IfStmtMissingExpression(String, #[label] Span), + #[error("include {0} could not be found")] + #[diagnostic(code("Qasm.Lowerer.IncludeNotFound"))] + IncludeNotFound(String, #[label] Span), + #[error("include {0} must be declared in global scope")] + #[diagnostic(code("Qasm.Lowerer.IncludeNotInGlobalScope"))] + IncludeNotInGlobalScope(String, #[label] Span), + #[error("include {0} must be declared in global scope")] + #[diagnostic(code("Qasm.Lowerer.IncludeStatementMissingPath"))] + IncludeStatementMissingPath(#[label] Span), + #[error("inconsistent types in alias expression: {0}")] + #[diagnostic(code("Qasm.Lowerer.InconsistentTypesInAlias"))] + InconsistentTypesInAlias(String, #[label] Span), + #[error("indexed must be a single expression")] + #[diagnostic(code("Qasm.Lowerer.IndexMustBeSingleExpr"))] + IndexMustBeSingleExpr(#[label] Span), + #[error("assigning {0} values to {1} must be in a range that be converted to {1}")] + #[diagnostic(code("Qasm.Lowerer.InvalidCastValueRange"))] + InvalidCastValueRange(String, String, #[label] Span), + #[error("gate operands other than qubits or qubit arrays are not supported")] + #[diagnostic(code("Qasm.Lowerer.InvalidGateOperand"))] + InvalidGateOperand(#[label] Span), + #[error("control counts must be integer literals")] + #[diagnostic(code("Qasm.Lowerer.InvalidControlCount"))] + InvalidControlCount(#[label] Span), + #[error("gate operands other than qubit arrays are not supported")] + #[diagnostic(code("Qasm.Lowerer.InvalidIndexedGateOperand"))] + InvalidIndexedGateOperand(#[label] Span), + #[error("gate expects {0} classical arguments, but {1} were provided")] + #[diagnostic(code("Qasm.Lowerer.InvalidNumberOfClassicalArgs"))] + InvalidNumberOfClassicalArgs(usize, usize, #[label] Span), + #[error("gate expects {0} qubit arguments, but {1} were provided")] + #[diagnostic(code("Qasm.Lowerer.InvalidNumberOfQubitArgs"))] + InvalidNumberOfQubitArgs(usize, usize, #[label] Span), + #[error("{0} can only appear in {1} scopes")] + #[diagnostic(code("Qasm.Lowerer.InvalidScope"))] + InvalidScope(String, String, #[label] Span), + #[error("measure statements must have a name")] + #[diagnostic(code("Qasm.Lowerer.MeasureExpressionsMustHaveName"))] + MeasureExpressionsMustHaveName(#[label] Span), + #[error("measure statements must have a gate operand name")] + #[diagnostic(code("Qasm.Lowerer.MeasureExpressionsMustHaveGateOperand"))] + MeasureExpressionsMustHaveGateOperand(#[label] Span), + #[error("return statements on a non-void subroutine should have a target expression")] + #[diagnostic(code("Qasm.Lowerer.MissingTargetExpressionInReturnStmt"))] + MissingTargetExpressionInReturnStmt(#[label] Span), + #[error("control counts must be postitive integers")] + #[diagnostic(code("Qasm.Lowerer.NegativeControlCount"))] + NegativeControlCount(#[label] Span), + #[error("{0} are not supported")] + #[diagnostic(code("Qasm.Lowerer.NotSupported"))] + NotSupported(String, #[label] Span), + #[error("{0} were introduced in version {1}")] + #[diagnostic(code("Qasm.Lowerer.NotSupportedInThisVersion"))] + NotSupportedInThisVersion(String, String, #[label] Span), + #[error("the operator {0} is not valid with lhs {1} and rhs {2}")] + #[diagnostic(code("Qasm.Lowerer.OperatorNotSupportedForTypes"))] + OperatorNotSupportedForTypes(String, String, String, #[label] Span), + #[error("pow gate modifiers must have an exponent")] + #[diagnostic(code("Qasm.Lowerer.PowModifierMustHaveExponent"))] + PowModifierMustHaveExponent(#[label] Span), + #[error("quantum declarations must be done in global scope")] + #[diagnostic(code("Qasm.Lowerer.QuantumDeclarationInNonGlobalScope"))] + QuantumDeclarationInNonGlobalScope(#[label] Span), + #[error("quantum typed values cannot be used in binary expressions")] + #[diagnostic(code("Qasm.Lowerer.QuantumTypesInBinaryExpression"))] + QuantumTypesInBinaryExpression(#[label] Span), + #[error("range expressions must have a start")] + #[diagnostic(code("Qasm.Lowerer.RangeExpressionsMustHaveStart"))] + RangeExpressionsMustHaveStart(#[label] Span), + #[error("range expressions must have a stop")] + #[diagnostic(code("Qasm.Lowerer.RangeExpressionsMustHaveStop"))] + RangeExpressionsMustHaveStop(#[label] Span), + #[error("redefined symbol: {0}")] + #[diagnostic(code("Qasm.Lowerer.RedefinedSymbol"))] + RedefinedSymbol(String, #[label] Span), + #[error("reset expression must have a gate operand")] + #[diagnostic(code("Qasm.Lowerer.ResetExpressionMustHaveGateOperand"))] + ResetExpressionMustHaveGateOperand(#[label] Span), + #[error("reset expression must have a name")] + #[diagnostic(code("Qasm.Lowerer.ResetExpressionMustHaveName"))] + ResetExpressionMustHaveName(#[label] Span), + #[error("cannot return an expression from a void subroutine")] + #[diagnostic(code("Qasm.Lowerer.ReturningExpressionFromVoidSubroutine"))] + ReturningExpressionFromVoidSubroutine(#[label] Span), + #[error("return statements are only allowed within subroutines")] + #[diagnostic(code("Qasm.Lowerer.ReturnNotInSubroutine"))] + ReturnNotInSubroutine(#[label] Span), + #[error("switch statement must have at least one non-default case")] + #[diagnostic(code("Qasm.Lowerer.SwitchStatementMustHaveAtLeastOneCase"))] + SwitchStatementMustHaveAtLeastOneCase(#[label] Span), + #[error("too many controls specified")] + #[diagnostic(code("Qasm.Lowerer.TooManyControls"))] + TooManyControls(#[label] Span), + #[error("too many indicies specified")] + #[diagnostic(code("Qasm.Lowerer.TooManyIndices"))] + TooManyIndices(#[label] Span), + #[error("bitwise not `~` is not allowed for instances of {0}")] + #[diagnostic(code("Qasm.Lowerer.TypeDoesNotSupportBitwiseNot"))] + TypeDoesNotSupportBitwiseNot(String, #[label] Span), + #[error("unary negation is not allowed for instances of {0}")] + #[diagnostic(code("Qasm.Lowerer.TypeDoesNotSupportedUnaryNegation"))] + TypeDoesNotSupportedUnaryNegation(String, #[label] Span), + #[error("{0} max width is {1} but {2} was provided")] + #[diagnostic(code("Qasm.Lowerer.TypeMaxWidthExceeded"))] + TypeMaxWidthExceeded(String, usize, usize, #[label] Span), + #[error("types differ by dimensions and are incompatible")] + #[diagnostic(code("Qasm.Lowerer.TypeRankError"))] + TypeRankError(#[label] Span), + #[error("type width must be a positive integer const expression")] + #[diagnostic(code("Qasm.Lowerer.TypeWidthMustBePositiveIntConstExpr"))] + TypeWidthMustBePositiveIntConstExpr(#[label] Span), + #[error("undefined symbol: {0}")] + #[diagnostic(code("Qasm.Lowerer.UndefinedSymbol"))] + UndefinedSymbol(String, #[label] Span), + #[error("unexpected parser error: {0}")] + #[diagnostic(code("Qasm.Lowerer.UnexpectedParserError"))] + UnexpectedParserError(String, #[label] Span), + #[error("this statement is not yet handled during OpenQASM 3 import: {0}")] + #[diagnostic(code("Qasm.Lowerer.Unimplemented"))] + Unimplemented(String, #[label] Span), + #[error("unknown index operation kind")] + #[diagnostic(code("Qasm.Lowerer.UnknownIndexedOperatorKind"))] + UnknownIndexedOperatorKind(#[label] Span), + #[error("unsupported version: '{0}'")] + #[diagnostic(code("Qasm.Lowerer.UnsupportedVersion"))] + UnsupportedVersion(String, #[label] Span), + #[error("while statement missing {0} expression")] + #[diagnostic(code("Qasm.Lowerer.WhileStmtMissingExpression"))] + WhileStmtMissingExpression(String, #[label] Span), +} + +impl From for crate::Error { + fn from(val: Error) -> Self { + crate::Error(crate::ErrorKind::Semantic(val)) + } +} diff --git a/compiler/qsc_qasm/src/semantic/lowerer.rs b/compiler/qsc_qasm/src/semantic/lowerer.rs new file mode 100644 index 0000000000..ef4ec4a099 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/lowerer.rs @@ -0,0 +1,3415 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::ops::ShlAssign; +use std::rc::Rc; + +use super::const_eval::ConstEvalError; +use super::symbols::ScopeKind; +use super::types::binop_requires_asymmetric_angle_op; +use super::types::binop_requires_int_conversion_for_type; +use super::types::binop_requires_symmetric_uint_conversion; +use super::types::is_complex_binop_supported; +use super::types::promote_to_uint_ty; +use super::types::promote_width; +use super::types::requires_symmetric_conversion; +use super::types::try_promote_with_casting; +use super::types::types_equal_except_const; +use super::types::unary_op_can_be_applied_to_type; +use super::types::Type; +use num_bigint::BigInt; +use num_traits::FromPrimitive; +use num_traits::Num; +use qsc_data_structures::span::Span; +use qsc_frontend::{compile::SourceMap, error::WithSource}; +use rustc_hash::FxHashMap; + +use super::symbols::{IOKind, Symbol, SymbolTable}; + +use crate::convert::safe_i64_to_f64; +use crate::parser::ast::list_from_iter; +use crate::parser::QasmSource; +use crate::semantic::types::can_cast_literal; +use crate::semantic::types::can_cast_literal_with_value_knowledge; +use crate::semantic::types::ArrayDimensions; +use crate::stdlib::angle::Angle; + +use super::ast as semantic; +use crate::parser::ast as syntax; + +use super::{ + ast::{Stmt, Version}, + SemanticErrorKind, +}; + +/// Macro to create an error expression. Used when we fail to +/// lower an expression. It is assumed that an error was +/// already reported. +macro_rules! err_expr { + ($ty:expr) => { + semantic::Expr { + span: Span::default(), + kind: Box::new(semantic::ExprKind::Err), + ty: $ty, + } + }; + + ($ty:expr, $span:expr) => { + semantic::Expr { + span: $span, + kind: Box::new(semantic::ExprKind::Err), + ty: $ty, + } + }; +} + +pub(crate) struct Lowerer { + /// The root QASM source to compile. + pub source: QasmSource, + /// The source map of QASM sources for error reporting. + pub source_map: SourceMap, + pub errors: Vec>, + /// The file stack is used to track the current file for error reporting. + /// When we include a file, we push the file path to the stack and pop it + /// when we are done with the file. + /// This allows us to report errors with the correct file path. + pub symbols: SymbolTable, + pub version: Option, + pub stmts: Vec, +} + +impl Lowerer { + pub fn new(source: QasmSource, source_map: SourceMap) -> Self { + let symbols = SymbolTable::default(); + let version = None; + let stmts = Vec::new(); + let errors = Vec::new(); + Self { + source, + source_map, + errors, + symbols, + version, + stmts, + } + } + + pub fn lower(mut self) -> crate::semantic::QasmSemanticParseResult { + // Should we fail if we see a version in included files? + let source = &self.source.clone(); + self.version = self.lower_version(source.program().version); + + self.lower_source(source); + + assert!( + self.symbols.is_current_scope_global(), + "scope stack was non popped correctly" + ); + + let program = semantic::Program { + version: self.version, + statements: syntax::list_from_iter(self.stmts), + }; + + super::QasmSemanticParseResult { + source: self.source, + source_map: self.source_map, + symbols: self.symbols, + program, + errors: self.errors, + } + } + + fn lower_version(&mut self, version: Option) -> Option { + if let Some(version) = version { + if version.major != 3 { + self.push_semantic_error(SemanticErrorKind::UnsupportedVersion( + format!("{version}"), + version.span, + )); + } else if let Some(minor) = version.minor { + if minor != 0 && minor != 1 { + self.push_semantic_error(SemanticErrorKind::UnsupportedVersion( + format!("{version}"), + version.span, + )); + } + } + return Some(crate::semantic::ast::Version { + span: version.span, + major: version.major, + minor: version.minor, + }); + } + None + } + + /// Root recursive function for lowering the source. + fn lower_source(&mut self, source: &QasmSource) { + // we keep an iterator of the includes so we can match them with the + // source includes. The include statements only have the path, but + // we have already loaded all of source files in the + // `source.includes()` + let mut includes = source.includes().iter(); + + for stmt in &source.program().statements { + if let syntax::StmtKind::Include(include) = &*stmt.kind { + // if we are not in the root we should not be able to include + // as this is a limitation of the QASM3 language + if !self.symbols.is_current_scope_global() { + let kind = SemanticErrorKind::IncludeNotInGlobalScope( + include.filename.to_string(), + include.span, + ); + self.push_semantic_error(kind); + continue; + } + + // special case for stdgates.inc + // it won't be in the includes list + if include.filename.to_lowercase() == "stdgates.inc" { + self.define_stdgates(include.span); + continue; + } + + let include = includes.next().expect("missing include"); + self.lower_source(include); + } else { + let stmt = self.lower_stmt(stmt); + self.stmts.push(stmt); + } + } + } + + #[allow(clippy::too_many_lines)] + fn lower_stmt(&mut self, stmt: &syntax::Stmt) -> semantic::Stmt { + let kind = match &*stmt.kind { + syntax::StmtKind::Alias(stmt) => self.lower_alias(stmt), + syntax::StmtKind::Assign(stmt) => self.lower_assign(stmt), + syntax::StmtKind::AssignOp(stmt) => self.lower_assign_op(stmt), + syntax::StmtKind::Barrier(stmt) => self.lower_barrier_stmt(stmt), + syntax::StmtKind::Box(stmt) => self.lower_box(stmt), + syntax::StmtKind::Break(stmt) => self.lower_break(stmt), + syntax::StmtKind::Block(stmt) => { + semantic::StmtKind::Block(Box::new(self.lower_block(stmt))) + } + syntax::StmtKind::Cal(stmt) => self.lower_calibration(stmt), + syntax::StmtKind::CalibrationGrammar(stmt) => self.lower_calibration_grammar(stmt), + syntax::StmtKind::ClassicalDecl(stmt) => self.lower_classical_decl(stmt), + syntax::StmtKind::ConstDecl(stmt) => self.lower_const_decl(stmt), + syntax::StmtKind::Continue(stmt) => self.lower_continue_stmt(stmt), + syntax::StmtKind::Def(stmt) => self.lower_def(stmt), + syntax::StmtKind::DefCal(stmt) => self.lower_def_cal(stmt), + syntax::StmtKind::Delay(stmt) => self.lower_delay(stmt), + syntax::StmtKind::End(stmt) => Self::lower_end_stmt(stmt), + syntax::StmtKind::ExprStmt(stmt) => self.lower_expr_stmt(stmt), + syntax::StmtKind::ExternDecl(extern_decl) => self.lower_extern(extern_decl), + syntax::StmtKind::For(stmt) => self.lower_for_stmt(stmt), + syntax::StmtKind::If(stmt) => self.lower_if_stmt(stmt), + syntax::StmtKind::GateCall(stmt) => self.lower_gate_call_stmt(stmt), + syntax::StmtKind::GPhase(stmt) => self.lower_gphase_stmt(stmt), + syntax::StmtKind::Include(stmt) => self.lower_include(stmt), + syntax::StmtKind::IODeclaration(stmt) => self.lower_io_decl(stmt), + syntax::StmtKind::Measure(stmt) => self.lower_measure(stmt), + syntax::StmtKind::Pragma(stmt) => self.lower_pragma(stmt), + syntax::StmtKind::QuantumGateDefinition(stmt) => self.lower_gate_def(stmt), + syntax::StmtKind::QuantumDecl(stmt) => self.lower_quantum_decl(stmt), + syntax::StmtKind::Reset(stmt) => self.lower_reset(stmt), + syntax::StmtKind::Return(stmt) => self.lower_return(stmt), + syntax::StmtKind::Switch(stmt) => self.lower_switch(stmt), + syntax::StmtKind::WhileLoop(stmt) => self.lower_while_stmt(stmt), + syntax::StmtKind::Err => semantic::StmtKind::Err, + }; + let annotations = Self::lower_annotations(&stmt.annotations); + semantic::Stmt { + span: stmt.span, + annotations: syntax::list_from_iter(annotations), + kind: Box::new(kind), + } + } + + /// Define the standard gates in the symbol table. + /// The sdg, tdg, crx, cry, crz, and ch are defined + /// as their bare gates, and modifiers are applied + /// when calling them. + fn define_stdgates(&mut self, span: Span) { + fn gate_symbol(name: &str, cargs: u32, qargs: u32) -> Symbol { + Symbol::new( + name, + Span::default(), + Type::Gate(cargs, qargs), + Default::default(), + Default::default(), + ) + } + let gates = vec![ + gate_symbol("p", 1, 1), + gate_symbol("x", 0, 1), + gate_symbol("y", 0, 1), + gate_symbol("z", 0, 1), + gate_symbol("h", 0, 1), + gate_symbol("s", 0, 1), + gate_symbol("t", 0, 1), + gate_symbol("sx", 0, 1), + gate_symbol("rx", 1, 1), + gate_symbol("ry", 1, 1), + gate_symbol("rz", 1, 1), + gate_symbol("cx", 0, 2), + gate_symbol("cy", 0, 2), + gate_symbol("cz", 0, 2), + gate_symbol("cp", 1, 2), + gate_symbol("swap", 0, 2), + gate_symbol("ccx", 0, 3), + gate_symbol("cu", 4, 2), + gate_symbol("CX", 0, 2), + gate_symbol("phase", 1, 1), + gate_symbol("id", 0, 1), + gate_symbol("u1", 1, 1), + gate_symbol("u2", 2, 1), + gate_symbol("u3", 3, 1), + ]; + for gate in gates { + let name = gate.name.clone(); + if self.symbols.insert_symbol(gate).is_err() { + self.push_redefined_symbol_error(name.as_str(), span); + } + } + } + + /// Define the Qiskit standard gates in the symbol table. + /// Qiskit emits QASM3 that can't compile because it omits + /// definitions for many gates that aren't included in the + /// standard gates include file. We define them here so that + /// the symbol table is complete and we can lower the QASM3. + /// We must also define the gates in the `QasmStd` module so + /// that we can compile the QASM3 to Q#. + fn define_qiskit_standard_gate_if_needed(&mut self, name: S, span: Span) + where + S: AsRef, + { + const QISKIT_STDGATES: [&str; 20] = [ + "rxx", + "ryy", + "rzz", + "dcx", + "ecr", + "r", + "rzx", + "cs", + "csdg", + "sxdg", + "csx", + "cu1", + "cu3", + "rccx", + "c3sqrtx", + "c3x", + "rc3x", + "xx_minus_yy", + "xx_plus_yy", + "ccz", + ]; + // only define the gate if it is not already defined + // and it is in the list of Qiskit standard gates + if self.symbols.get_symbol_by_name(&name).is_none() + && QISKIT_STDGATES.contains(&name.as_ref()) + { + self.define_qiskit_standard_gate(name, span); + } + } + + fn define_qiskit_standard_gate(&mut self, name: S, span: Span) + where + S: AsRef, + { + fn gate_symbol(name: &str, cargs: u32, qargs: u32) -> Symbol { + Symbol::new( + name, + Span::default(), + Type::Gate(cargs, qargs), + Default::default(), + Default::default(), + ) + } + // QIR intrinsics missing from qasm std library, that Qiskit won't emit qasm defs for + // rxx, ryy, rzz; + + // Remaining gates that are not in the qasm std library, but are standard gates in Qiskit + // that Qiskit wont emit correctly. + // dcx, ecr, r, rzx, cs, csdg, sxdg, csx, cu1, cu3, rccx, c3sqrtx, c3x, rc3x, xx_minus_yy, xx_plus_yy, ccz; + //iter) + let gates = FxHashMap::from_iter([ + ("rxx", gate_symbol("rxx", 1, 2)), + ("ryy", gate_symbol("ryy", 1, 2)), + ("rzz", gate_symbol("rzz", 1, 2)), + ("dcx", gate_symbol("dcx", 0, 2)), + ("ecr", gate_symbol("ecr", 0, 2)), + ("r", gate_symbol("r", 2, 1)), + ("rzx", gate_symbol("rzx", 1, 2)), + ("cs", gate_symbol("cs", 0, 2)), + ("csdg", gate_symbol("csdg", 0, 2)), + ("sxdg", gate_symbol("sxdg", 0, 1)), + ("csx", gate_symbol("csx", 0, 2)), + ("cu1", gate_symbol("cu1", 1, 2)), + ("cu3", gate_symbol("cu3", 3, 2)), + ("rccx", gate_symbol("rccx", 0, 3)), + ("c3sqrtx", gate_symbol("c3sqrtx", 0, 4)), + ("c3x", gate_symbol("c3x", 0, 4)), + ("rc3x", gate_symbol("rc3x", 0, 4)), + ("xx_minus_yy", gate_symbol("xx_minus_yy", 2, 2)), + ("xx_plus_yy", gate_symbol("xx_plus_yy", 2, 2)), + ("ccz", gate_symbol("ccz", 0, 3)), + ]); + let gate = gates.get(name.as_ref()).expect("missing gate symbol"); + if self.symbols.insert_symbol(gate.clone()).is_err() { + self.push_redefined_symbol_error(name.as_ref(), span); + } + } + + fn try_insert_or_get_existing_symbol_id( + &mut self, + name: S, + symbol: Symbol, + ) -> super::symbols::SymbolId + where + S: AsRef, + { + let symbol_span = symbol.span; + let symbol_id = match self.symbols.try_insert_or_get_existing(symbol) { + Ok(symbol_id) => symbol_id, + Err(symbol_id) => { + self.push_redefined_symbol_error(name.as_ref(), symbol_span); + symbol_id + } + }; + symbol_id + } + + fn try_get_existing_or_insert_err_symbol( + &mut self, + name: S, + span: Span, + ) -> (super::symbols::SymbolId, std::rc::Rc) + where + S: AsRef, + { + let (symbol_id, symbol) = match self + .symbols + .try_get_existing_or_insert_err_symbol(name.as_ref(), span) + { + Ok((symbol_id, symbol)) => (symbol_id, symbol), + Err((symbol_id, symbol)) => { + self.push_missing_symbol_error(name, span); + (symbol_id, symbol) + } + }; + (symbol_id, symbol) + } + + fn lower_alias(&mut self, alias: &syntax::AliasDeclStmt) -> semantic::StmtKind { + let name = get_identifier_name(&alias.ident); + // alias statements do their types backwards, you read the right side + // and assign it to the left side. + // the types of the rhs should be in the symbol table. + let rhs = alias + .exprs + .iter() + .map(|expr| self.lower_expr(expr)) + .collect::>(); + let first = rhs.first().expect("missing rhs"); + + let symbol = Symbol::new( + &name, + alias.ident.span(), + first.ty.clone(), + self.convert_semantic_type_to_qsharp_type(&first.ty, alias.ident.span()), + IOKind::Default, + ); + + let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol); + + if rhs.iter().any(|expr| expr.ty != first.ty) { + let tys = rhs + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "); + let kind = SemanticErrorKind::InconsistentTypesInAlias(tys, alias.span); + self.push_semantic_error(kind); + } + + semantic::StmtKind::Alias(semantic::AliasDeclStmt { + span: alias.span, + symbol_id, + exprs: syntax::list_from_iter(rhs), + }) + } + + fn lower_assign(&mut self, stmt: &syntax::AssignStmt) -> semantic::StmtKind { + if stmt.lhs.indices.is_empty() { + self.lower_simple_assign_expr(&stmt.lhs.name, &stmt.rhs, stmt.span) + } else { + self.lower_indexed_assign_expr(&stmt.lhs, &stmt.rhs, stmt.span) + } + } + + fn lower_simple_assign_expr( + &mut self, + ident: &syntax::Ident, + rhs: &syntax::ValueExpr, + span: Span, + ) -> semantic::StmtKind { + let (symbol_id, symbol) = + self.try_get_existing_or_insert_err_symbol(&ident.name, ident.span); + + let ty = symbol.ty.clone(); + let rhs = match rhs { + syntax::ValueExpr::Expr(expr) => { + let expr = self.lower_expr(expr); + self.cast_expr_with_target_type_or_default(Some(expr), &ty, span) + } + syntax::ValueExpr::Measurement(measure_expr) => { + let expr = self.lower_measure_expr(measure_expr); + self.cast_expr_to_type(&ty, &expr) + } + }; + + if ty.is_const() { + let kind = + SemanticErrorKind::CannotUpdateConstVariable(ident.name.to_string(), ident.span); + self.push_semantic_error(kind); + } + + semantic::StmtKind::Assign(semantic::AssignStmt { + symbol_id, + lhs_span: ident.span, + rhs, + span, + }) + } + + fn lower_indexed_assign_expr( + &mut self, + index_expr: &syntax::IndexedIdent, + rhs: &syntax::ValueExpr, + span: Span, + ) -> semantic::StmtKind { + let ident = index_expr.name.clone(); + let (symbol_id, symbol) = + self.try_get_existing_or_insert_err_symbol(&ident.name, ident.span); + + let indexed_ty = + &self.get_indexed_type(&symbol.ty, index_expr.name.span, index_expr.indices.len()); + + let indices = list_from_iter( + index_expr + .indices + .iter() + .map(|index| self.lower_index_element(index)), + ); + + let rhs = match rhs { + syntax::ValueExpr::Expr(expr) => { + let expr = self.lower_expr(expr); + self.cast_expr_with_target_type_or_default(Some(expr), indexed_ty, span) + } + syntax::ValueExpr::Measurement(measure_expr) => { + let expr = self.lower_measure_expr(measure_expr); + self.cast_expr_to_type(indexed_ty, &expr) + } + }; + + if symbol.ty.is_const() { + let kind = + SemanticErrorKind::CannotUpdateConstVariable(ident.name.to_string(), ident.span); + self.push_semantic_error(kind); + } + + semantic::StmtKind::IndexedAssign(semantic::IndexedAssignStmt { + span, + symbol_id, + name_span: index_expr.name.span, + indices, + rhs, + }) + } + + fn lower_assign_op(&mut self, stmt: &syntax::AssignOpStmt) -> semantic::StmtKind { + let op = stmt.op.into(); + let lhs = &stmt.lhs; + let rhs = &stmt.rhs; + let ident = lhs.name.clone(); + + let (symbol_id, symbol) = + self.try_get_existing_or_insert_err_symbol(&ident.name, ident.span); + + let ty = if lhs.indices.is_empty() { + &symbol.ty + } else { + &self.get_indexed_type(&symbol.ty, lhs.name.span, lhs.indices.len()) + }; + let indices = list_from_iter( + lhs.indices + .iter() + .map(|index| self.lower_index_element(index)), + ); + + if ty.is_const() { + let kind = + SemanticErrorKind::CannotUpdateConstVariable(ident.name.to_string(), ident.span); + self.push_semantic_error(kind); + } + + let lhs = self.lower_indexed_ident_expr(lhs); + let rhs = match rhs { + syntax::ValueExpr::Expr(expr) => { + let expr = self.lower_expr(expr); + self.cast_expr_with_target_type_or_default(Some(expr), ty, stmt.span) + } + syntax::ValueExpr::Measurement(measure_expr) => { + let expr = self.lower_measure_expr(measure_expr); + self.cast_expr_to_type(ty, &expr) + } + }; + + semantic::StmtKind::AssignOp(semantic::AssignOpStmt { + span: stmt.span, + symbol_id, + indices, + op, + lhs, + rhs, + }) + } + + fn lower_expr(&mut self, expr: &syntax::Expr) -> semantic::Expr { + match &*expr.kind { + syntax::ExprKind::BinaryOp(bin_op_expr) => { + let lhs = self.lower_expr(&bin_op_expr.lhs); + let rhs = self.lower_expr(&bin_op_expr.rhs); + self.lower_binary_op_expr(bin_op_expr.op, lhs, rhs, expr.span) + } + syntax::ExprKind::Cast(_) => { + self.push_unimplemented_error_message("cast expr", expr.span); + err_expr!(Type::Err, expr.span) + } + syntax::ExprKind::Err => err_expr!(Type::Err, expr.span), + syntax::ExprKind::FunctionCall(expr) => self.lower_function_call_expr(expr), + syntax::ExprKind::Ident(ident) => self.lower_ident_expr(ident), + syntax::ExprKind::IndexExpr(expr) => self.lower_index_expr(expr), + + syntax::ExprKind::Lit(lit) => self.lower_lit_expr(lit), + + syntax::ExprKind::Paren(pexpr) => self.lower_paren_expr(pexpr, expr.span), + syntax::ExprKind::UnaryOp(expr) => self.lower_unary_op_expr(expr), + } + } + + fn lower_ident_expr(&mut self, ident: &syntax::Ident) -> semantic::Expr { + let name = ident.name.clone(); + + let (symbol_id, symbol) = self.try_get_existing_or_insert_err_symbol(&name, ident.span); + + // Design Note: The end goal of this const evaluation is to be able to compile qasm + // annotations as Q# attributes like `@SimulatableIntrinsic()`. + // + // QASM3 subroutines and gates can be recursive and capture const symbols + // outside their scope. In Q#, only lambdas can capture symbols, but only + // proper functions and operations can be recursive or have attributes on + // them. To get both, annotations & recursive gates/functions and the + // ability to capture const symbols outside the gate/function scope, we + // decided to compile the gates/functions as proper Q# operations/functions + // and evaluate at lowering-time all references to const symbols outside + // the current gate/function scope. + + // This is true if we are inside any gate or function scope. + let is_symbol_inside_gate_or_function_scope = + self.symbols.is_scope_rooted_in_gate_or_subroutine(); + + // This is true if the symbol is outside the most inner gate or function scope. + let is_symbol_outside_most_inner_gate_or_function_scope = self + .symbols + .is_symbol_outside_most_inner_gate_or_function_scope(symbol_id); + + let is_const_evaluation_necessary = symbol.is_const() + && is_symbol_inside_gate_or_function_scope + && is_symbol_outside_most_inner_gate_or_function_scope; + + let kind = if is_const_evaluation_necessary { + if let Some(val) = symbol.get_const_expr().const_eval(self) { + semantic::ExprKind::Lit(val) + } else { + self.push_semantic_error(SemanticErrorKind::ExprMustBeConst( + "a captured variable".into(), + ident.span, + )); + semantic::ExprKind::Err + } + } else { + semantic::ExprKind::Ident(symbol_id) + }; + + semantic::Expr { + span: ident.span, + kind: Box::new(kind), + ty: symbol.ty.clone(), + } + } + + fn lower_lit_expr(&mut self, expr: &syntax::Lit) -> semantic::Expr { + let (kind, ty) = match &expr.kind { + syntax::LiteralKind::BigInt(value) => { + // this case is only valid when there is an integer literal + // that requires more than 64 bits to represent. We should probably + // introduce a new type for this as openqasm promotion rules don't + // cover this case as far as I know. + ( + semantic::ExprKind::Lit(semantic::LiteralKind::BigInt(value.clone())), + Type::Err, + ) + } + syntax::LiteralKind::Bitstring(value, size) => ( + semantic::ExprKind::Lit(semantic::LiteralKind::Bitstring(value.clone(), *size)), + Type::BitArray(super::types::ArrayDimensions::from(*size), true), + ), + syntax::LiteralKind::Bool(value) => ( + semantic::ExprKind::Lit(semantic::LiteralKind::Bool(*value)), + Type::Bool(true), + ), + syntax::LiteralKind::Int(value) => ( + semantic::ExprKind::Lit(semantic::LiteralKind::Int(*value)), + Type::Int(None, true), + ), + syntax::LiteralKind::Float(value) => ( + semantic::ExprKind::Lit(semantic::LiteralKind::Float(*value)), + Type::Float(None, true), + ), + syntax::LiteralKind::Imaginary(value) => ( + semantic::ExprKind::Lit(semantic::LiteralKind::Complex(0.0, *value)), + Type::Complex(None, true), + ), + syntax::LiteralKind::String(_) => { + self.push_unsupported_error_message("string literals", expr.span); + (semantic::ExprKind::Err, Type::Err) + } + syntax::LiteralKind::Duration(value, time_unit) => ( + semantic::ExprKind::Lit(semantic::LiteralKind::Duration( + *value, + (*time_unit).into(), + )), + Type::Duration(true), + ), + syntax::LiteralKind::Array(exprs) => { + // array literals are only valid in classical decals (const and mut) + // and we have to know the expected type of the array in order to lower it + // So we can't lower array literals in general. + self.push_semantic_error(SemanticErrorKind::ArrayLiteralInNonClassicalDecl( + expr.span, + )); + // place holder for now, this code will need to move to the correct place when we + // add support for classical decls + let texprs = exprs + .iter() + .map(|expr| self.lower_expr(expr)) + .collect::>(); + + ( + semantic::ExprKind::Lit(semantic::LiteralKind::Array(syntax::list_from_iter( + texprs, + ))), + Type::Err, + ) + } + }; + semantic::Expr { + span: expr.span, + kind: Box::new(kind), + ty, + } + } + + fn lower_paren_expr(&mut self, expr: &syntax::Expr, span: Span) -> semantic::Expr { + let expr = self.lower_expr(expr); + let ty = expr.ty.clone(); + let kind = semantic::ExprKind::Paren(expr); + semantic::Expr { + span, + kind: Box::new(kind), + ty, + } + } + + fn lower_unary_op_expr(&mut self, expr: &syntax::UnaryOpExpr) -> semantic::Expr { + match expr.op { + syntax::UnaryOp::Neg => { + let op = expr.op; + let expr = self.lower_expr(&expr.expr); + let ty = expr.ty.clone(); + if !unary_op_can_be_applied_to_type(op, &ty) { + let kind = SemanticErrorKind::TypeDoesNotSupportedUnaryNegation( + expr.ty.to_string(), + expr.span, + ); + self.push_semantic_error(kind); + } + let span = expr.span; + let unary = semantic::UnaryOpExpr { + span, + op: semantic::UnaryOp::Neg, + expr, + }; + semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::UnaryOp(unary)), + ty, + } + } + syntax::UnaryOp::NotB => { + let op = expr.op; + let expr = self.lower_expr(&expr.expr); + let ty = expr.ty.clone(); + if !unary_op_can_be_applied_to_type(op, &ty) { + let kind = SemanticErrorKind::TypeDoesNotSupportedUnaryNegation( + expr.ty.to_string(), + expr.span, + ); + self.push_semantic_error(kind); + } + let span = expr.span; + let unary = semantic::UnaryOpExpr { + span, + op: semantic::UnaryOp::NotB, + expr, + }; + semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::UnaryOp(unary)), + ty, + } + } + syntax::UnaryOp::NotL => { + // this is the only unary operator that tries to coerce the type + // I can't find it in the spec, but when looking at existing code + // it seems that the ! operator coerces to a bool if possible + let expr = self.lower_expr(&expr.expr); + let expr_span = expr.span; + let target_ty = Type::Bool(expr.ty.is_const()); + + let expr = + self.cast_expr_with_target_type_or_default(Some(expr), &target_ty, expr_span); + + let ty = expr.ty.clone(); + + semantic::Expr { + span: expr.span, + kind: Box::new(semantic::ExprKind::UnaryOp(semantic::UnaryOpExpr { + span: expr.span, + op: semantic::UnaryOp::NotL, + expr, + })), + ty, + } + } + } + } + + fn lower_annotations(annotations: &[Box]) -> Vec { + annotations + .iter() + .map(|annotation| Self::lower_annotation(annotation)) + .collect::>() + } + + fn lower_annotation(annotation: &syntax::Annotation) -> semantic::Annotation { + semantic::Annotation { + span: annotation.span, + identifier: annotation.identifier.clone(), + value: annotation.value.as_ref().map(Clone::clone), + } + } + + fn convert_semantic_type_to_qsharp_type( + &mut self, + ty: &super::types::Type, + span: Span, + ) -> crate::types::Type { + let is_const = ty.is_const(); + match ty { + Type::Bit(_) => crate::types::Type::Result(is_const), + Type::Qubit => crate::types::Type::Qubit, + Type::HardwareQubit => { + let message = "hardware qubit to Q# type"; + self.push_unsupported_error_message(message, span); + crate::types::Type::Err + } + Type::Int(width, _) | Type::UInt(width, _) => { + if let Some(width) = width { + if *width > 64 { + crate::types::Type::BigInt(is_const) + } else { + crate::types::Type::Int(is_const) + } + } else { + crate::types::Type::Int(is_const) + } + } + Type::Float(_, _) => crate::types::Type::Double(is_const), + Type::Angle(_, _) => crate::types::Type::Angle(is_const), + Type::Complex(_, _) => crate::types::Type::Complex(is_const), + Type::Bool(_) => crate::types::Type::Bool(is_const), + Type::Duration(_) => { + self.push_unsupported_error_message("duration type values", span); + crate::types::Type::Err + } + Type::Stretch(_) => { + self.push_unsupported_error_message("stretch type values", span); + crate::types::Type::Err + } + Type::BitArray(dims, _) => crate::types::Type::ResultArray(dims.into(), is_const), + Type::QubitArray(dims) => crate::types::Type::QubitArray(dims.into()), + Type::IntArray(size, dims) | Type::UIntArray(size, dims) => { + if let Some(size) = size { + if *size > 64 { + crate::types::Type::BigIntArray(dims.into(), is_const) + } else { + crate::types::Type::IntArray(dims.into(), is_const) + } + } else { + crate::types::Type::IntArray(dims.into(), is_const) + } + } + Type::FloatArray(_, dims) => crate::types::Type::DoubleArray(dims.into()), + Type::BoolArray(dims) => crate::types::Type::BoolArray(dims.into(), is_const), + Type::Gate(cargs, qargs) => { + crate::types::Type::Callable(crate::types::CallableKind::Operation, *cargs, *qargs) + } + Type::Range => crate::types::Type::Range, + Type::Void => crate::types::Type::Tuple(vec![]), + Type::Err => crate::types::Type::Err, + _ => { + let msg = format!("converting {ty:?} to Q# type"); + self.push_unimplemented_error_message(msg, span); + crate::types::Type::Err + } + } + } + + fn lower_barrier_stmt(&mut self, stmt: &syntax::BarrierStmt) -> semantic::StmtKind { + let qubits = stmt.qubits.iter().map(|q| self.lower_gate_operand(q)); + let qubits = list_from_iter(qubits); + semantic::StmtKind::Barrier(semantic::BarrierStmt { + span: stmt.span, + qubits, + }) + } + + /// The "boxable" stmts were taken from the reference parser at + /// . + /// Search for the definition of `Box` there, and then for all the classes + /// inhereting from `QuantumStatement`. + fn lower_box(&mut self, stmt: &syntax::BoxStmt) -> semantic::StmtKind { + let _stmts = stmt + .body + .iter() + .map(|stmt| self.lower_stmt(stmt)) + .collect::>(); + + let mut _has_invalid_stmt_kinds = false; + for stmt in &stmt.body { + match &*stmt.kind { + syntax::StmtKind::Barrier(_) + | syntax::StmtKind::Delay(_) + | syntax::StmtKind::Reset(_) + | syntax::StmtKind::GateCall(_) + | syntax::StmtKind::GPhase(_) + | syntax::StmtKind::Box(_) => { + // valid statements + } + _ => { + self.push_semantic_error(SemanticErrorKind::ClassicalStmtInBox(stmt.span)); + _has_invalid_stmt_kinds = true; + } + } + } + + if let Some(duration) = &stmt.duration { + self.push_unsupported_error_message("Box with duration", duration.span); + } + + // we semantically checked the stmts, but we still need to lower them + // with the correct behavior based on any pragmas that might be present + self.push_unimplemented_error_message("box stmt", stmt.span); + semantic::StmtKind::Err + } + + fn lower_break(&mut self, stmt: &syntax::BreakStmt) -> semantic::StmtKind { + if self.symbols.is_scope_rooted_in_loop_scope() { + semantic::StmtKind::Break(semantic::BreakStmt { span: stmt.span }) + } else { + self.push_semantic_error(SemanticErrorKind::InvalidScope( + "break".into(), + "loop".into(), + stmt.span, + )); + semantic::StmtKind::Err + } + } + + fn lower_block(&mut self, stmt: &syntax::Block) -> semantic::Block { + self.symbols.push_scope(ScopeKind::Block); + let stmts = stmt.stmts.iter().map(|stmt| self.lower_stmt(stmt)); + let stmts = list_from_iter(stmts); + self.symbols.pop_scope(); + + semantic::Block { + span: stmt.span, + stmts, + } + } + + fn lower_calibration(&mut self, stmt: &syntax::CalibrationStmt) -> semantic::StmtKind { + self.push_unimplemented_error_message("calibration stmt", stmt.span); + semantic::StmtKind::Err + } + + fn lower_calibration_grammar( + &mut self, + stmt: &syntax::CalibrationGrammarStmt, + ) -> semantic::StmtKind { + self.push_unimplemented_error_message("calibration grammar stmt", stmt.span); + semantic::StmtKind::Err + } + + fn lower_classical_decl( + &mut self, + stmt: &syntax::ClassicalDeclarationStmt, + ) -> semantic::StmtKind { + let is_const = false; // const decls are handled separately + let ty = self.get_semantic_type_from_tydef(&stmt.ty, is_const); + + let init_expr = stmt.init_expr.as_deref(); + let ty_span = stmt.ty.span(); + let stmt_span = stmt.span; + let name = stmt.identifier.name.clone(); + let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty.clone(), ty_span); + let symbol = Symbol::new( + &name, + stmt.identifier.span, + ty.clone(), + qsharp_ty, + IOKind::Default, + ); + + // process the symbol and init_expr gathering any errors + let init_expr = match init_expr { + Some(expr) => match expr { + syntax::ValueExpr::Expr(expr) => { + let expr = self.lower_expr(expr); + self.cast_expr_with_target_type_or_default(Some(expr), &ty, stmt_span) + } + syntax::ValueExpr::Measurement(measure_expr) => { + let expr = self.lower_measure_expr(measure_expr); + self.cast_expr_to_type(&ty, &expr) + } + }, + None => self.cast_expr_with_target_type_or_default(None, &ty, stmt_span), + }; + + let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol); + + semantic::StmtKind::ClassicalDecl(semantic::ClassicalDeclarationStmt { + span: stmt_span, + ty_span, + symbol_id, + init_expr: Box::new(init_expr), + }) + } + + fn lower_const_decl(&mut self, stmt: &syntax::ConstantDeclStmt) -> semantic::StmtKind { + let is_const = true; + let ty = self.get_semantic_type_from_tydef(&stmt.ty, is_const); + let ty_span = stmt.ty.span(); + let name = stmt.identifier.name.clone(); + let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty.clone(), stmt.ty.span()); + let init_expr = match &stmt.init_expr { + syntax::ValueExpr::Expr(expr) => { + let expr = self.lower_expr(expr); + self.cast_expr_with_target_type_or_default(Some(expr), &ty, stmt.span) + } + syntax::ValueExpr::Measurement(measure_expr) => self.lower_measure_expr(measure_expr), + }; + + let mut symbol = Symbol::new( + &name, + stmt.identifier.span, + ty.clone(), + qsharp_ty, + IOKind::Default, + ); + + if init_expr.ty.is_const() { + symbol = symbol.with_const_expr(Rc::new(init_expr.clone())); + } + + let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol); + + if !init_expr.ty.is_const() { + self.push_semantic_error(SemanticErrorKind::ExprMustBeConst( + "const decl init expr".to_string(), + init_expr.span, + )); + } + + semantic::StmtKind::ClassicalDecl(semantic::ClassicalDeclarationStmt { + span: stmt.span, + ty_span, + symbol_id, + init_expr: Box::new(init_expr), + }) + } + + fn lower_continue_stmt(&mut self, stmt: &syntax::ContinueStmt) -> semantic::StmtKind { + if self.symbols.is_scope_rooted_in_loop_scope() { + semantic::StmtKind::Continue(semantic::ContinueStmt { span: stmt.span }) + } else { + self.push_semantic_error(SemanticErrorKind::InvalidScope( + "continue".into(), + "loop".into(), + stmt.span, + )); + semantic::StmtKind::Err + } + } + + fn lower_def(&mut self, stmt: &syntax::DefStmt) -> semantic::StmtKind { + // 1. Check that we are in the global scope. QASM3 semantics + // only allow def declarations in the global scope. + if !self.symbols.is_current_scope_global() { + let kind = SemanticErrorKind::DefDeclarationInNonGlobalScope(stmt.span); + self.push_semantic_error(kind); + } + + // 2. Build the parameter's type. + let mut param_types = Vec::with_capacity(stmt.params.len()); + let mut param_symbols = Vec::with_capacity(stmt.params.len()); + + for param in &stmt.params { + let symbol = self.lower_typed_parameter(param); + param_types.push(symbol.ty.clone()); + param_symbols.push(symbol); + } + + // 3. Build the return type. + let (return_ty, qsharp_return_ty) = if let Some(ty) = &stmt.return_type { + let ty_span = ty.span; + let tydef = syntax::TypeDef::Scalar(*ty.clone()); + let ty = self.get_semantic_type_from_tydef(&tydef, false); + let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty, ty_span); + (Rc::new(ty), qsharp_ty) + } else { + ( + Rc::new(crate::semantic::types::Type::Void), + crate::types::Type::Tuple(Default::default()), + ) + }; + + // 2. Push the function symbol to the symbol table. + #[allow(clippy::cast_possible_truncation)] + let arity = stmt.params.len() as u32; + let name = stmt.name.name.clone(); + let name_span = stmt.name.span; + let ty = crate::semantic::types::Type::Function(param_types.into(), return_ty.clone()); + + let has_qubit_params = stmt + .params + .iter() + .any(|arg| matches!(&**arg, syntax::TypedParameter::Quantum(..))); + + let kind = if has_qubit_params { + crate::types::CallableKind::Operation + } else { + crate::types::CallableKind::Function + }; + + let qsharp_ty = crate::types::Type::Callable(kind, arity, 0); + + let symbol = Symbol::new(&name, name_span, ty, qsharp_ty, IOKind::Default); + let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol); + + // Push the scope where the def lives. + self.symbols.push_scope(ScopeKind::Function(return_ty)); + + let params = param_symbols + .into_iter() + .map(|symbol| { + let name = symbol.name.clone(); + self.try_insert_or_get_existing_symbol_id(name, symbol) + }) + .collect(); + + let body = semantic::Block { + span: stmt.body.span, + stmts: list_from_iter(stmt.body.stmts.iter().map(|stmt| self.lower_stmt(stmt))), + }; + + // Pop the scope where the def lives. + self.symbols.pop_scope(); + + semantic::StmtKind::Def(semantic::DefStmt { + span: stmt.span, + symbol_id, + has_qubit_params, + params, + body, + return_type: qsharp_return_ty, + }) + } + + fn lower_typed_parameter(&mut self, typed_param: &syntax::TypedParameter) -> Symbol { + match typed_param { + syntax::TypedParameter::ArrayReference(param) => { + self.lower_array_reference_parameter(param) + } + syntax::TypedParameter::Quantum(param) => self.lower_quantum_parameter(param), + syntax::TypedParameter::Scalar(param) => self.lower_scalar_parameter(param), + } + } + + fn lower_array_reference_parameter( + &mut self, + typed_param: &syntax::ArrayTypedParameter, + ) -> Symbol { + let tydef = syntax::TypeDef::ArrayReference(*typed_param.ty.clone()); + let ty = self.get_semantic_type_from_tydef(&tydef, false); + let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty, typed_param.ty.span); + + Symbol::new( + &typed_param.ident.name, + typed_param.ident.span, + ty, + qsharp_ty, + IOKind::Default, + ) + } + + fn lower_quantum_parameter(&mut self, typed_param: &syntax::QuantumTypedParameter) -> Symbol { + let (ty, qsharp_ty) = if let Some(size) = &typed_param.size { + if let Some(size) = self.const_eval_array_size_designator_from_expr(size) { + let ty = crate::semantic::types::Type::QubitArray(ArrayDimensions::One(size)); + let qsharp_ty = crate::types::Type::QubitArray(crate::types::ArrayDimensions::One( + size as usize, + )); + (ty, qsharp_ty) + } else { + (crate::semantic::types::Type::Err, crate::types::Type::Err) + } + } else { + ( + crate::semantic::types::Type::Qubit, + crate::types::Type::Qubit, + ) + }; + + Symbol::new( + &typed_param.ident.name, + typed_param.ident.span, + ty, + qsharp_ty, + IOKind::Default, + ) + } + + fn lower_scalar_parameter(&mut self, typed_param: &syntax::ScalarTypedParameter) -> Symbol { + let tydef = syntax::TypeDef::Scalar(*typed_param.ty.clone()); + let ty = self.get_semantic_type_from_tydef(&tydef, false); + let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty, typed_param.ty.span); + + Symbol::new( + &typed_param.ident.name, + typed_param.ident.span, + ty, + qsharp_ty, + IOKind::Default, + ) + } + + fn lower_def_cal(&mut self, stmt: &syntax::DefCalStmt) -> semantic::StmtKind { + self.push_unimplemented_error_message("def cal stmt", stmt.span); + semantic::StmtKind::Err + } + + fn lower_delay(&mut self, stmt: &syntax::DelayStmt) -> semantic::StmtKind { + self.push_unimplemented_error_message("delay stmt", stmt.span); + semantic::StmtKind::Err + } + + fn lower_end_stmt(stmt: &syntax::EndStmt) -> semantic::StmtKind { + semantic::StmtKind::End(semantic::EndStmt { span: stmt.span }) + } + + fn lower_expr_stmt(&mut self, stmt: &syntax::ExprStmt) -> semantic::StmtKind { + let expr = self.lower_expr(&stmt.expr); + semantic::StmtKind::ExprStmt(semantic::ExprStmt { + span: stmt.span, + expr, + }) + } + + fn lower_extern(&mut self, stmt: &syntax::ExternDecl) -> semantic::StmtKind { + // 1. Check that we are in the global scope. QASM3 semantics + // only allow extern declarations in the global scope. + if !self.symbols.is_current_scope_global() { + let kind = SemanticErrorKind::ExternDeclarationInNonGlobalScope(stmt.span); + self.push_semantic_error(kind); + } + + // 2. Build the parameter's type. + let mut params = Vec::with_capacity(stmt.params.len()); + let mut qsharp_params = Vec::with_capacity(stmt.params.len()); + + for param in &stmt.params { + let ty = self.lower_extern_param(param); + let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty, param.span()); + params.push(ty); + qsharp_params.push(qsharp_ty); + } + + // 2. Build the return type. + let (return_ty, qsharp_return_ty) = if let Some(ty) = &stmt.return_type { + let ty_span = ty.span; + let tydef = syntax::TypeDef::Scalar(ty.clone()); + let ty = self.get_semantic_type_from_tydef(&tydef, false); + let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty, ty_span); + (Rc::new(ty), qsharp_ty) + } else { + ( + Rc::new(crate::semantic::types::Type::Void), + crate::types::Type::Tuple(Default::default()), + ) + }; + + // 3. Push the extern symbol to the symbol table. + #[allow(clippy::cast_possible_truncation)] + let arity = stmt.params.len() as u32; + let name = stmt.ident.name.clone(); + let name_span = stmt.ident.span; + let ty = crate::semantic::types::Type::Function(params.into(), return_ty.clone()); + let kind = crate::types::CallableKind::Function; + let qsharp_ty = crate::types::Type::Callable(kind, arity, 0); + let symbol = Symbol::new(&name, name_span, ty, qsharp_ty, IOKind::Default); + let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol); + + semantic::StmtKind::ExternDecl(semantic::ExternDecl { + span: stmt.span, + symbol_id, + params: qsharp_params.into(), + return_type: qsharp_return_ty, + }) + } + + fn lower_extern_param(&mut self, param: &syntax::ExternParameter) -> Type { + let tydef = match param { + syntax::ExternParameter::ArrayReference(array_reference_type, _) => { + syntax::TypeDef::ArrayReference(array_reference_type.clone()) + } + syntax::ExternParameter::Scalar(scalar_type, _) => { + syntax::TypeDef::Scalar(scalar_type.clone()) + } + }; + self.get_semantic_type_from_tydef(&tydef, false) + } + + fn lower_for_stmt(&mut self, stmt: &syntax::ForStmt) -> semantic::StmtKind { + let set_declaration = self.lower_enumerable_set(&stmt.set_declaration); + + // Push scope where the loop variable lives. + self.symbols.push_scope(ScopeKind::Loop); + + let ty = self.get_semantic_type_from_scalar_ty(&stmt.ty, false); + let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty.clone(), stmt.ty.span); + let symbol = Symbol::new( + &stmt.ident.name, + stmt.ident.span, + ty.clone(), + qsharp_ty, + IOKind::Default, + ); + + // This is the first variable in this scope, so + // we don't need to check for redefined symbols. + let symbol_id = self + .symbols + .insert_symbol(symbol) + .expect("this should be the first variable in this scope"); + + // We lower the body after registering the loop variable symbol_id. + // The body of the for loop could be a single statement redefining + // the loop variable, in which case we need to push a redefined + // symbol error. + let body = self.lower_stmt(&stmt.body); + + // Pop the scope where the loop variable lives. + self.symbols.pop_scope(); + + semantic::StmtKind::For(semantic::ForStmt { + span: stmt.span, + loop_variable: symbol_id, + set_declaration: Box::new(set_declaration), + body, + }) + } + + fn lower_if_stmt(&mut self, stmt: &syntax::IfStmt) -> semantic::StmtKind { + let condition = self.lower_expr(&stmt.condition); + let if_body = self.lower_stmt(&stmt.if_body); + let else_body = stmt.else_body.as_ref().map(|body| self.lower_stmt(body)); + + // The semantics of a if statement is that the condition must be + // of type bool, so we try to cast it, inserting a cast if necessary. + let cond_ty = Type::Bool(condition.ty.is_const()); + let condition = self.cast_expr_to_type(&cond_ty, &condition); + + semantic::StmtKind::If(semantic::IfStmt { + span: stmt.span, + condition, + if_body, + else_body, + }) + } + + fn lower_function_call_expr(&mut self, expr: &syntax::FunctionCall) -> semantic::Expr { + // 1. Check that the function name actually refers to a function + // in the symbol table and get its symbol_id & symbol. + let name = expr.name.name.clone(); + let name_span = expr.name.span; + let (symbol_id, symbol) = self.try_get_existing_or_insert_err_symbol(name, name_span); + + let (params_ty, return_ty) = if let Type::Function(params_ty, return_ty) = &symbol.ty { + let arity = params_ty.len(); + + // 2. Check that function classical arity matches the number of classical args. + if arity != expr.args.len() { + self.push_semantic_error(SemanticErrorKind::InvalidNumberOfClassicalArgs( + arity, + expr.args.len(), + expr.span, + )); + } + + (params_ty.clone(), (**return_ty).clone()) + } else { + self.push_semantic_error(SemanticErrorKind::CannotCallNonFunction(symbol.span)); + (Rc::default(), crate::semantic::types::Type::Err) + }; + + // 3. Lower the args. There are three cases. + // 3.1 If there are fewer args than the arity of the function. + + // 3.2 If the number of args and the arity match. + + // 3.3 If there are more args than the arity of the function. + let mut params_ty_iter = params_ty.iter(); + let args = expr.args.iter().map(|arg| { + let arg = self.lower_expr(arg); + if let Some(ty) = params_ty_iter.next() { + self.cast_expr_to_type(ty, &arg) + } else { + arg + } + }); + let args = list_from_iter(args); + + let kind = Box::new(semantic::ExprKind::FunctionCall(semantic::FunctionCall { + span: expr.span, + fn_name_span: expr.name.span, + symbol_id, + args, + })); + + semantic::Expr { + span: expr.span, + kind, + ty: return_ty, + } + } + + fn lower_gate_call_stmt(&mut self, stmt: &syntax::GateCall) -> semantic::StmtKind { + // 1. Lower all the fields: + // 1.1. Lower the modifiers. + let mut modifiers = stmt + .modifiers + .iter() + .filter_map(|modifier| self.lower_modifier(modifier)) + .collect::>(); + // If we couldn't compute the modifiers there is no way to compile the gates + // correctly, since we can't check its arity. In this case we return an Err. + if modifiers.len() != stmt.modifiers.len() { + return semantic::StmtKind::Err; + } + + // 1.3. Lower the args. + let args = stmt.args.iter().map(|arg| { + let arg = self.lower_expr(arg); + self.cast_expr_to_type(&crate::semantic::types::Type::Angle(None, false), &arg) + }); + let args = list_from_iter(args); + // 1.4. Lower the qubits. + let qubits = stmt.qubits.iter().map(|q| self.lower_gate_operand(q)); + let qubits = list_from_iter(qubits); + // 1.5. Lower the duration. + let duration = stmt.duration.as_ref().map(|d| self.lower_expr(d)); + + if let Some(duration) = &duration { + self.push_unsupported_error_message("gate call duration", duration.span); + } + + let mut name = stmt.name.name.to_string(); + if let Some((gate_name, implicit_modifier)) = + try_get_qsharp_name_and_implicit_modifiers(&name, stmt.name.span) + { + // Override the gate name if we mapped with modifiers. + name = gate_name; + + // 2. Get implicit modifiers and make them explicit. + // Q: Do we need this during lowering? + // A: Yes, we need it to check the gate_call arity. + modifiers.push(implicit_modifier); + } + + // need a workaround for qiskit generating gate calls without having declared the gate + self.define_qiskit_standard_gate_if_needed(&name, stmt.name.span); + + // 3. Check that the gate_name actually refers to a gate in the symbol table + // and get its symbol_id & symbol. Make sure to use the name that could've + // been overriden by the Q# name and the span of the original name. + let (symbol_id, symbol) = self.try_get_existing_or_insert_err_symbol(name, stmt.name.span); + + let (classical_arity, quantum_arity) = + if let Type::Gate(classical_arity, quantum_arity) = &symbol.ty { + (*classical_arity, *quantum_arity) + } else { + self.push_semantic_error(SemanticErrorKind::CannotCallNonGate(symbol.span)); + (0, 0) + }; + + // 4. Check that gate_call classical arity matches the number of classical args. + if classical_arity as usize != args.len() { + self.push_semantic_error(SemanticErrorKind::InvalidNumberOfClassicalArgs( + classical_arity as usize, + args.len(), + stmt.span, + )); + } + + // 5. Check that gate_call quantum arity with modifiers matches the + // number of qubit args. + let mut quantum_arity_with_modifiers = quantum_arity; + for modifier in &modifiers { + match &modifier.kind { + semantic::GateModifierKind::Inv | semantic::GateModifierKind::Pow(_) => (), + semantic::GateModifierKind::Ctrl(n) | semantic::GateModifierKind::NegCtrl(n) => { + quantum_arity_with_modifiers += n; + } + } + } + + if quantum_arity_with_modifiers as usize != qubits.len() { + self.push_semantic_error(SemanticErrorKind::InvalidNumberOfQubitArgs( + quantum_arity_with_modifiers as usize, + qubits.len(), + stmt.span, + )); + } + + // 6. Return: + // 6.1. Gate symbol_id. + // 6.2. All controls made explicit. + // 6.3. Classical args. + // 6.4. Quantum args in the order expected by the compiler. + modifiers.reverse(); + let modifiers = list_from_iter(modifiers); + semantic::StmtKind::GateCall(semantic::GateCall { + span: stmt.span, + modifiers, + symbol_id, + gate_name_span: stmt.name.span, + args, + qubits, + duration, + classical_arity, + quantum_arity, + }) + + // The compiler will be left to do all things that need explicit Q# knowledge. + // But it won't need to check arities, know about implicit modifiers, or do + // any casting of classical args. There is still some inherit complexity to + // building a Q# gate call with this information, but it won't be cluttered + // by all the QASM semantic analysis. + } + + /// This is just syntax sugar around a gate call. + fn lower_gphase_stmt(&mut self, stmt: &syntax::GPhase) -> semantic::StmtKind { + let name = syntax::Ident { + span: stmt.gphase_token_span, + name: "gphase".into(), + }; + let gate_call_stmt = syntax::GateCall { + span: stmt.span, + modifiers: stmt.modifiers.clone(), + name, + args: stmt.args.clone(), + qubits: stmt.qubits.clone(), + duration: stmt.duration.clone(), + }; + self.lower_gate_call_stmt(&gate_call_stmt) + } + + fn lower_modifier( + &mut self, + modifier: &syntax::QuantumGateModifier, + ) -> Option { + let kind = match &modifier.kind { + syntax::GateModifierKind::Inv => semantic::GateModifierKind::Inv, + syntax::GateModifierKind::Pow(expr) => { + semantic::GateModifierKind::Pow(self.lower_expr(expr)) + } + syntax::GateModifierKind::Ctrl(expr) => { + let ctrl_args = self.lower_modifier_ctrl_args(expr.as_ref())?; + semantic::GateModifierKind::Ctrl(ctrl_args) + } + syntax::GateModifierKind::NegCtrl(expr) => { + let ctrl_args = self.lower_modifier_ctrl_args(expr.as_ref())?; + semantic::GateModifierKind::NegCtrl(ctrl_args) + } + }; + + Some(semantic::QuantumGateModifier { + span: modifier.span, + modifier_keyword_span: modifier.modifier_keyword_span, + kind, + }) + } + + fn lower_modifier_ctrl_args(&mut self, expr: Option<&syntax::Expr>) -> Option { + let Some(expr) = expr else { + return Some(1); + }; + + let expr = self.lower_expr(expr); + + let target_ty = &Type::UInt(None, true); + let Some(expr) = Self::try_cast_expr_to_type(target_ty, &expr) else { + self.push_invalid_cast_error(target_ty, &expr.ty, expr.span); + return None; + }; + let Some(lit) = expr.const_eval(self) else { + self.push_semantic_error(SemanticErrorKind::ExprMustBeConst( + "ctrl modifier argument".into(), + expr.span, + )); + return None; + }; + + let semantic::LiteralKind::Int(n) = lit else { + unreachable!("we casted the expr to UInt before const evaluating it") + }; + + let Ok(n) = u32::try_from(n) else { + self.push_semantic_error(SemanticErrorKind::ExprMustFitInU32( + "ctrl modifier argument".into(), + expr.span, + )); + return None; + }; + + Some(n) + } + + /// This function is always a indication of a error. Either the + /// program is declaring include in a non-global scope or the + /// include is not handled in `self.lower_source` properly. + fn lower_include(&mut self, stmt: &syntax::IncludeStmt) -> semantic::StmtKind { + // if we are not in the root we should not be able to include + if !self.symbols.is_current_scope_global() { + let name = stmt.filename.to_string(); + let kind = SemanticErrorKind::IncludeNotInGlobalScope(name, stmt.span); + self.push_semantic_error(kind); + return semantic::StmtKind::Err; + } + // if we are at the root and we have an include, we should have + // already handled it and we are in an invalid state + panic!("include should have been handled in lower_source") + } + + fn lower_io_decl(&mut self, stmt: &syntax::IODeclaration) -> semantic::StmtKind { + let is_const = false; + let ty = self.get_semantic_type_from_tydef(&stmt.ty, is_const); + let io_kind = stmt.io_identifier.into(); + + assert!( + io_kind == IOKind::Input || io_kind == IOKind::Output, + "IOKind should be Input or Output" + ); + + let ty_span = stmt.ty.span(); + let stmt_span = stmt.span; + let name = stmt.ident.name.clone(); + let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty, ty_span); + let symbol = Symbol::new(&name, stmt.ident.span, ty.clone(), qsharp_ty, io_kind); + + let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol); + + if io_kind == IOKind::Input { + return semantic::StmtKind::InputDeclaration(semantic::InputDeclaration { + span: stmt_span, + symbol_id, + }); + } + + // if we have output, we need to assign a default value to declare the variable + // if we have input, we can keep return none as we would promote the variable + // to a parameter in the function signature once we generate the function + let init_expr = self.get_default_value(&ty, stmt_span); + semantic::StmtKind::OutputDeclaration(semantic::OutputDeclaration { + span: stmt_span, + ty_span, + symbol_id, + init_expr: Box::new(init_expr), + }) + } + + fn lower_measure(&mut self, stmt: &syntax::MeasureArrowStmt) -> semantic::StmtKind { + // `measure q -> c;` is syntax sugar for `c = measure q;` + if let Some(target) = &stmt.target { + self.lower_assign(&syntax::AssignStmt { + span: stmt.span, + lhs: *target.clone(), + rhs: syntax::ValueExpr::Measurement(stmt.measurement.clone()), + }) + } else { + let measure = self.lower_measure_expr(&stmt.measurement); + semantic::StmtKind::ExprStmt(semantic::ExprStmt { + span: stmt.span, + expr: measure, + }) + } + } + + fn lower_pragma(&mut self, stmt: &syntax::Pragma) -> semantic::StmtKind { + self.push_unimplemented_error_message("pragma stmt", stmt.span); + semantic::StmtKind::Err + } + + fn lower_gate_def(&mut self, stmt: &syntax::QuantumGateDefinition) -> semantic::StmtKind { + // 1. Check that we are in the global scope. QASM3 semantics + // only allow gate definitions in the global scope. + if !self.symbols.is_current_scope_global() { + let kind = SemanticErrorKind::QuantumDeclarationInNonGlobalScope(stmt.span); + self.push_semantic_error(kind); + } + + // 2. Push the gate symbol to the symbol table. + #[allow(clippy::cast_possible_truncation)] + let classical_arity = stmt + .params + .iter() + .filter_map(|seq_item| seq_item.item_as_ref()) + .count() as u32; + #[allow(clippy::cast_possible_truncation)] + let quantum_arity = stmt + .qubits + .iter() + .filter_map(|seq_item| seq_item.item_as_ref()) + .count() as u32; + let name = stmt.ident.name.clone(); + let ty = crate::semantic::types::Type::Gate(classical_arity, quantum_arity); + let qsharp_ty = crate::types::Type::Callable( + crate::types::CallableKind::Operation, + classical_arity, + quantum_arity, + ); + let symbol = Symbol::new(&name, stmt.ident.span, ty, qsharp_ty, IOKind::Default); + let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol); + + // Push the scope where the gate definition lives. + self.symbols.push_scope(ScopeKind::Gate); + + // Design Note: If a formal parameter is missing (i.e. there are two consecutive commas and we + // have a missing item in the formal parameters list), we have two options: + // 1. Treat the missing item as if it wasn't there, and just push a parser + // error saying there is a missing item. This is what Rust does. + // 2. Treat the missing item as a Type::Err and make it part of the gate + // signature, this is what Q# does. + // We decided to go with (1) because it avoids propagating the SeqItem enum + // to the compiler, which is simpler. + let params = stmt + .params + .iter() + .filter_map(|seq_item| seq_item.item_as_ref()) + .map(|arg| { + let ty = crate::semantic::types::Type::Angle(None, false); + let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty, Span::default()); + let symbol = Symbol::new(&arg.name, arg.span, ty, qsharp_ty, IOKind::Default); + self.try_insert_or_get_existing_symbol_id(&arg.name, symbol) + }) + .collect::>(); + + let qubits = stmt + .qubits + .iter() + .filter_map(|seq_item| seq_item.item_as_ref()) + .map(|arg| { + let ty = crate::semantic::types::Type::Qubit; + let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty, Span::default()); + let symbol = Symbol::new(&arg.name, arg.span, ty, qsharp_ty, IOKind::Default); + self.try_insert_or_get_existing_symbol_id(&arg.name, symbol) + }) + .collect::>(); + + let body = semantic::Block { + span: stmt.body.span, + stmts: list_from_iter(stmt.body.stmts.iter().map(|stmt| self.lower_stmt(stmt))), + }; + + // Pop the scope where the gate definition lives. + self.symbols.pop_scope(); + + semantic::StmtKind::QuantumGateDefinition(semantic::QuantumGateDefinition { + span: stmt.span, + name_span: stmt.ident.span, + symbol_id, + params, + qubits, + body, + }) + } + + fn lower_quantum_decl(&mut self, stmt: &syntax::QubitDeclaration) -> semantic::StmtKind { + // If there wasn't an explicit size, infer the size to be 1. + let (ty, size_and_span) = if let Some(size_expr) = &stmt.size { + let size_expr = self.lower_expr(size_expr); + let span = size_expr.span; + let size_expr = Self::try_cast_expr_to_type(&Type::UInt(None, true), &size_expr); + + if let Some(Some(semantic::LiteralKind::Int(val))) = + size_expr.map(|expr| expr.const_eval(self)) + { + if let Ok(size) = u32::try_from(val) { + ( + Type::QubitArray(ArrayDimensions::One(size)), + Some((size, span)), + ) + } else { + let message = "quantum register size".into(); + self.push_semantic_error(SemanticErrorKind::ExprMustFitInU32(message, span)); + return semantic::StmtKind::Err; + } + } else { + let message = "quantum register size".into(); + self.push_semantic_error(SemanticErrorKind::ExprMustBeConst(message, span)); + return semantic::StmtKind::Err; + } + } else { + (Type::Qubit, None) + }; + + let name = stmt.qubit.name.clone(); + let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty.clone(), stmt.ty_span); + + let symbol = Symbol::new( + &name, + stmt.qubit.span, + ty.clone(), + qsharp_ty, + IOKind::Default, + ); + + let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol); + + if let Some((size, size_span)) = size_and_span { + semantic::StmtKind::QubitArrayDecl(semantic::QubitArrayDeclaration { + span: stmt.span, + symbol_id, + size, + size_span, + }) + } else { + semantic::StmtKind::QubitDecl(semantic::QubitDeclaration { + span: stmt.span, + symbol_id, + }) + } + } + + fn lower_reset(&mut self, stmt: &syntax::ResetStmt) -> semantic::StmtKind { + let operand = self.lower_gate_operand(&stmt.operand); + semantic::StmtKind::Reset(semantic::ResetStmt { + span: stmt.span, + reset_token_span: stmt.reset_token_span, + operand: Box::new(operand), + }) + } + + fn lower_return(&mut self, stmt: &syntax::ReturnStmt) -> semantic::StmtKind { + let mut expr = stmt + .expr + .as_ref() + .map(|expr| match &**expr { + syntax::ValueExpr::Expr(expr) => self.lower_expr(expr), + syntax::ValueExpr::Measurement(expr) => self.lower_measure_expr(expr), + }) + .map(Box::new); + + let return_ty = self.symbols.get_subroutine_return_ty(); + + match (&mut expr, return_ty) { + // If we don't have a return type then we are not rooted in a subroutine scope. + (_, None) => { + self.push_semantic_error(SemanticErrorKind::InvalidScope( + "return statements".into(), + "subroutine".into(), + stmt.span, + )); + return semantic::StmtKind::Err; + } + (None, Some(ty)) => { + if !matches!(ty.as_ref(), Type::Void) { + self.push_semantic_error( + SemanticErrorKind::MissingTargetExpressionInReturnStmt(stmt.span), + ); + return semantic::StmtKind::Err; + } + } + (Some(expr), Some(ty)) => { + if matches!(ty.as_ref(), Type::Void) { + self.push_semantic_error( + SemanticErrorKind::ReturningExpressionFromVoidSubroutine(expr.span), + ); + return semantic::StmtKind::Err; + } + *expr = Box::new(self.cast_expr_to_type(&ty, expr)); + } + } + + semantic::StmtKind::Return(semantic::ReturnStmt { + span: stmt.span, + expr, + }) + } + + fn lower_switch(&mut self, stmt: &syntax::SwitchStmt) -> semantic::StmtKind { + // Semantics of switch case is that the outer block doesn't introduce + // a new scope but each case rhs does. + + // Can we add a new scope anyway to hold a temporary variable? + // if we do that, we can refer to a new variable instead of the control + // expr this would allow us to avoid the need to resolve the control + // expr multiple times in the case where we have to coerce the control + // expr to the correct type. Introducing a new variable without a new + // scope would effect output semantics. + let cases = stmt + .cases + .iter() + .map(|case| self.lower_switch_case(case)) + .collect::>(); + let default = stmt.default.as_ref().map(|d| self.lower_block(d)); + let target = self.lower_expr(&stmt.target); + + // The condition for the switch statement must be an integer type + // so we use `cast_expr_to_type`, forcing the type to be an integer + // type with implicit casts if necessary. + let target_ty = Type::Int(None, target.ty.is_const()); + let target = self.cast_expr_to_type(&target_ty, &target); + + // We push a semantic error on switch statements if version is less than 3.1, + // as they were introduced in 3.1. + if let Some(ref version) = self.version { + const SWITCH_MINIMUM_SUPPORTED_VERSION: semantic::Version = semantic::Version { + major: 3, + minor: Some(1), + span: Span { lo: 0, hi: 0 }, + }; + if version < &SWITCH_MINIMUM_SUPPORTED_VERSION { + self.push_unsuported_in_this_version_error_message( + "switch statements", + &SWITCH_MINIMUM_SUPPORTED_VERSION, + stmt.span, + ); + } + } + + semantic::StmtKind::Switch(semantic::SwitchStmt { + span: stmt.span, + target, + cases: list_from_iter(cases), + default, + }) + } + + fn lower_switch_case(&mut self, switch_case: &syntax::SwitchCase) -> semantic::SwitchCase { + let label_ty = Type::Int(None, true); + let labels = switch_case + .labels + .iter() + .map(|label| { + // The labels for each switch case must be of integer type + // so we use `cast_expr_to_type`, forcing the type to be an integer + // type with implicit casts if necessary. + let label = self.lower_expr(label); + self.cast_expr_to_type(&label_ty, &label) + }) + .collect::>(); + + let block = self.lower_block(&switch_case.block); + + semantic::SwitchCase { + span: switch_case.span, + labels: list_from_iter(labels), + block, + } + } + + fn lower_while_stmt(&mut self, stmt: &syntax::WhileLoop) -> semantic::StmtKind { + // Push scope where the while loop lives. The while loop needs its own scope + // so that break and continue know if they are inside a valid scope. + self.symbols.push_scope(ScopeKind::Loop); + + let condition = self.lower_expr(&stmt.while_condition); + let body = self.lower_stmt(&stmt.body); + + // The semantics of a while statement is that the condition must be + // of type bool, so we try to cast it, inserting a cast if necessary. + let cond_ty = Type::Bool(condition.ty.is_const()); + let while_condition = self.cast_expr_to_type(&cond_ty, &condition); + + // Pop scope where the while loop lives. + self.symbols.pop_scope(); + + semantic::StmtKind::WhileLoop(semantic::WhileLoop { + span: stmt.span, + condition: while_condition, + body, + }) + } + + fn get_semantic_type_from_tydef( + &mut self, + ty: &syntax::TypeDef, + is_const: bool, + ) -> crate::semantic::types::Type { + match ty { + syntax::TypeDef::Scalar(scalar_type) => { + self.get_semantic_type_from_scalar_ty(scalar_type, is_const) + } + syntax::TypeDef::Array(array_type) => { + self.get_semantic_type_from_array_ty(array_type, is_const) + } + syntax::TypeDef::ArrayReference(array_reference_type) => { + self.get_semantic_type_from_array_reference_ty(array_reference_type, is_const) + } + } + } + + /// Helper function for const evaluating array sizes, type widths, and durations. + fn const_eval_designator(&mut self, expr: &syntax::Expr) -> Option { + let expr = self.lower_expr(expr); + let expr_span = expr.span; + let expr = self.cast_expr_with_target_type_or_default( + Some(expr), + &Type::UInt(None, true), + expr_span, + ); + + if let Some(lit) = expr.const_eval(self) { + Some(lit) + } else { + self.push_semantic_error(SemanticErrorKind::ExprMustBeConst( + "designator".to_string(), + expr.span, + )); + None + } + } + + fn const_eval_array_size_designator_from_expr(&mut self, expr: &syntax::Expr) -> Option { + let semantic::LiteralKind::Int(val) = self.const_eval_designator(expr)? else { + self.push_semantic_error(SemanticErrorKind::ArraySizeMustBeNonNegativeConstExpr( + expr.span, + )); + return None; + }; + + if val < 0 { + self.push_semantic_error(SemanticErrorKind::ArraySizeMustBeNonNegativeConstExpr( + expr.span, + )); + return None; + } + + let Ok(val) = u32::try_from(val) else { + self.push_semantic_error(SemanticErrorKind::DesignatorTooLarge(expr.span)); + return None; + }; + + Some(val) + } + + fn const_eval_type_width_designator_from_expr(&mut self, expr: &syntax::Expr) -> Option { + let semantic::LiteralKind::Int(val) = self.const_eval_designator(expr)? else { + self.push_semantic_error(SemanticErrorKind::TypeWidthMustBePositiveIntConstExpr( + expr.span, + )); + return None; + }; + + if val < 1 { + self.push_semantic_error(SemanticErrorKind::TypeWidthMustBePositiveIntConstExpr( + expr.span, + )); + return None; + } + + let Ok(val) = u32::try_from(val) else { + self.push_semantic_error(SemanticErrorKind::DesignatorTooLarge(expr.span)); + return None; + }; + + Some(val) + } + + fn get_semantic_type_from_scalar_ty( + &mut self, + scalar_ty: &syntax::ScalarType, + is_const: bool, + ) -> crate::semantic::types::Type { + match &scalar_ty.kind { + syntax::ScalarTypeKind::Bit(bit_type) => match &bit_type.size { + Some(size) => { + let Some(size) = self.const_eval_array_size_designator_from_expr(size) else { + return crate::semantic::types::Type::Err; + }; + crate::semantic::types::Type::BitArray( + super::types::ArrayDimensions::from(size), + is_const, + ) + } + None => crate::semantic::types::Type::Bit(is_const), + }, + syntax::ScalarTypeKind::Int(int_type) => match &int_type.size { + Some(size) => { + let Some(size) = self.const_eval_type_width_designator_from_expr(size) else { + return crate::semantic::types::Type::Err; + }; + crate::semantic::types::Type::Int(Some(size), is_const) + } + None => crate::semantic::types::Type::Int(None, is_const), + }, + syntax::ScalarTypeKind::UInt(uint_type) => match &uint_type.size { + Some(size) => { + let Some(size) = self.const_eval_type_width_designator_from_expr(size) else { + return crate::semantic::types::Type::Err; + }; + crate::semantic::types::Type::UInt(Some(size), is_const) + } + None => crate::semantic::types::Type::UInt(None, is_const), + }, + syntax::ScalarTypeKind::Float(float_type) => match &float_type.size { + Some(size) => { + let Some(size) = self.const_eval_type_width_designator_from_expr(size) else { + return crate::semantic::types::Type::Err; + }; + if size > 64 { + self.push_semantic_error(SemanticErrorKind::TypeMaxWidthExceeded( + "float".to_string(), + 64, + size as usize, + float_type.span, + )); + crate::semantic::types::Type::Err + } else { + crate::semantic::types::Type::Float(Some(size), is_const) + } + } + None => crate::semantic::types::Type::Float(None, is_const), + }, + syntax::ScalarTypeKind::Complex(complex_type) => match &complex_type.base_size { + Some(float_type) => match &float_type.size { + Some(size) => { + let Some(size) = self.const_eval_type_width_designator_from_expr(size) + else { + return crate::semantic::types::Type::Err; + }; + crate::semantic::types::Type::Complex(Some(size), is_const) + } + None => crate::semantic::types::Type::Complex(None, is_const), + }, + None => crate::semantic::types::Type::Complex(None, is_const), + }, + syntax::ScalarTypeKind::Angle(angle_type) => match &angle_type.size { + Some(size) => { + let Some(size) = self.const_eval_type_width_designator_from_expr(size) else { + return crate::semantic::types::Type::Err; + }; + + if size > 64 { + self.push_semantic_error(SemanticErrorKind::TypeMaxWidthExceeded( + "angle".to_string(), + 64, + size as usize, + angle_type.span, + )); + crate::semantic::types::Type::Err + } else { + crate::semantic::types::Type::Angle(Some(size), is_const) + } + } + None => crate::semantic::types::Type::Angle(None, is_const), + }, + syntax::ScalarTypeKind::BoolType => crate::semantic::types::Type::Bool(is_const), + syntax::ScalarTypeKind::Duration => crate::semantic::types::Type::Duration(is_const), + syntax::ScalarTypeKind::Stretch => crate::semantic::types::Type::Stretch(is_const), + syntax::ScalarTypeKind::Err => crate::semantic::types::Type::Err, + } + } + + fn get_semantic_type_from_array_ty( + &mut self, + array_ty: &syntax::ArrayType, + _is_const: bool, + ) -> crate::semantic::types::Type { + self.push_unimplemented_error_message("semantic type from array type", array_ty.span); + crate::semantic::types::Type::Err + } + + fn get_semantic_type_from_array_reference_ty( + &mut self, + array_ref_ty: &syntax::ArrayReferenceType, + _is_const: bool, + ) -> crate::semantic::types::Type { + self.push_unimplemented_error_message( + "semantic type from array refence type", + array_ref_ty.span, + ); + crate::semantic::types::Type::Err + } + + fn cast_expr_with_target_type_or_default( + &mut self, + expr: Option, + ty: &Type, + span: Span, + ) -> semantic::Expr { + let Some(mut rhs) = expr else { + // In OpenQASM, classical variables may be uninitialized, but in Q#, + // they must be initialized. We will use the default value for the type + // to initialize the variable. + return self.get_default_value(ty, span); + }; + + let rhs_ty = rhs.ty.clone(); + + // avoid the cast + if *ty == rhs_ty { + // if the types are the same, we can use the rhs as is + return rhs; + } + + // if we have an exact type match, we can use the rhs as is + if types_equal_except_const(ty, &rhs_ty) { + // Since one the two exprs is non-const, we need to relax + // the constness in the returned expr. + rhs.ty = rhs.ty.as_non_const(); + return rhs; + } + + // if the rhs is a literal, we can try to cast it to the target type + // if they share the same base type. + if let semantic::ExprKind::Lit(lit) = &*rhs.kind { + // if the rhs is a literal, we can try to coerce it to the lhs type + // we can do better than just types given we have a literal value + if can_cast_literal(ty, &rhs_ty) || can_cast_literal_with_value_knowledge(ty, lit) { + return self.coerce_literal_expr_to_type(ty, &rhs, lit); + } + // if we can't cast the literal, we can't proceed + // create a semantic error and return + let kind = SemanticErrorKind::CannotCastLiteral( + format!("{:?}", rhs.ty), + format!("{ty:?}"), + span, + ); + self.push_semantic_error(kind); + return rhs; + } + // the lhs has a type, but the rhs may be of a different type with + // implicit and explicit conversions. We need to cast the rhs to the + // lhs type, but if that cast fails, we will have already pushed an error + // and we can't proceed + self.cast_expr_to_type(ty, &rhs) + } + + fn lower_measure_expr(&mut self, expr: &syntax::MeasureExpr) -> semantic::Expr { + let measurement = semantic::MeasureExpr { + span: expr.span, + measure_token_span: expr.measure_token_span, + operand: self.lower_gate_operand(&expr.operand), + }; + semantic::Expr { + span: expr.span, + kind: Box::new(semantic::ExprKind::Measure(measurement)), + ty: Type::Bit(false), + } + } + + fn get_default_value(&mut self, ty: &Type, span: Span) -> semantic::Expr { + use semantic::Expr; + use semantic::ExprKind; + use semantic::LiteralKind; + let from_lit_kind = |kind| -> Expr { + Expr { + span: Span::default(), + kind: Box::new(ExprKind::Lit(kind)), + ty: ty.as_const(), + } + }; + let expr = match ty { + Type::Angle(_, _) => Some(from_lit_kind(LiteralKind::Angle(Default::default()))), + Type::Bit(_) => Some(from_lit_kind(LiteralKind::Bit(false))), + Type::Int(_, _) | Type::UInt(_, _) => Some(from_lit_kind(LiteralKind::Int(0))), + Type::Bool(_) => Some(from_lit_kind(LiteralKind::Bool(false))), + Type::Float(_, _) => Some(from_lit_kind(LiteralKind::Float(0.0))), + Type::Complex(_, _) => Some(from_lit_kind(LiteralKind::Complex(0.0, 0.0))), + Type::Stretch(_) => { + let message = "stretch default values"; + self.push_unsupported_error_message(message, span); + None + } + Type::Qubit => { + let message = "qubit default values"; + self.push_unsupported_error_message(message, span); + None + } + Type::HardwareQubit => { + let message = "hardware qubit default values"; + self.push_unsupported_error_message(message, span); + None + } + Type::QubitArray(_) => { + let message = "qubit array default values"; + self.push_unsupported_error_message(message, span); + None + } + Type::BitArray(dims, _) => match dims { + ArrayDimensions::One(size) => Some(from_lit_kind( + semantic::LiteralKind::Bitstring(BigInt::ZERO, *size), + )), + ArrayDimensions::Err => None, + _ => { + self.push_unimplemented_error_message( + "multidimensional bit array default value", + span, + ); + None + } + }, + Type::Duration(_) => Some(from_lit_kind(LiteralKind::Duration( + 0.0, + semantic::TimeUnit::Ns, + ))), + Type::BoolArray(_) => { + self.push_unimplemented_error_message("bool array default value", span); + None + } + Type::DurationArray(_) => { + self.push_unimplemented_error_message("duration array default value", span); + None + } + Type::AngleArray(_, _) => { + self.push_unimplemented_error_message("angle array default value", span); + None + } + Type::ComplexArray(_, _) => { + self.push_unimplemented_error_message("complex array default value", span); + None + } + Type::FloatArray(_, _) => { + self.push_unimplemented_error_message("float array default value", span); + None + } + Type::IntArray(_, _) => { + self.push_unimplemented_error_message("int array default value", span); + None + } + Type::UIntArray(_, _) => { + self.push_unimplemented_error_message("uint array default value", span); + None + } + Type::Gate(_, _) | Type::Function(..) | Type::Range | Type::Set | Type::Void => { + let message = format!("default values for {ty:?}"); + self.push_unsupported_error_message(message, span); + None + } + Type::Err => None, + }; + let Some(expr) = expr else { + return err_expr!(ty.as_const()); + }; + expr + } + + fn coerce_literal_expr_to_type( + &mut self, + ty: &Type, + expr: &semantic::Expr, + kind: &semantic::LiteralKind, + ) -> semantic::Expr { + let Some(expr) = self.try_coerce_literal_expr_to_type(ty, expr, kind) else { + self.push_invalid_literal_cast_error(ty, &expr.ty, expr.span); + return expr.clone(); + }; + expr + } + + #[allow(clippy::too_many_lines)] + fn try_coerce_literal_expr_to_type( + &mut self, + ty: &Type, + rhs: &semantic::Expr, + kind: &semantic::LiteralKind, + ) -> Option { + assert!(matches!(*rhs.kind, semantic::ExprKind::Lit(..))); + assert!(rhs.ty.is_const(), "literals must have const types"); + + if *ty == rhs.ty { + // Base case, we shouldn't have gotten here + // but if we did, we can just return the rhs + return Some(rhs.clone()); + } + + if types_equal_except_const(ty, &rhs.ty) { + // lhs isn't const, but rhs is, this is allowed + return Some(rhs.clone()); + } + assert!(can_cast_literal(ty, &rhs.ty) || can_cast_literal_with_value_knowledge(ty, kind)); + let lhs_ty = ty.clone(); + let rhs_ty = rhs.ty.clone(); + let span = rhs.span; + + if matches!(lhs_ty, Type::Bit(..)) { + if let semantic::LiteralKind::Int(value) = kind { + // can_cast_literal_with_value_knowledge guarantees that value is 0 or 1 + return Some(semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Bit( + *value != 0, + ))), + ty: lhs_ty.as_const(), + }); + } else if let semantic::LiteralKind::Bool(value) = kind { + return Some(semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Bit(*value))), + ty: lhs_ty.as_const(), + }); + } + } + // if lhs_ty is 1 dim bitarray and rhs is int/uint, we can cast + let (is_int_to_bit_array, size) = match &lhs_ty { + Type::BitArray(dims, _) => { + if matches!(rhs.ty, Type::Int(..) | Type::UInt(..)) { + match dims { + &ArrayDimensions::One(size) => (true, size), + _ => (false, 0), + } + } else { + (false, 0) + } + } + _ => (false, 0), + }; + if is_int_to_bit_array { + if let semantic::LiteralKind::Int(value) = kind { + if *value < 0 || *value >= (1 << size) { + return None; + } + + let u_size = size as usize; + let bitstring = format!("{value:0u_size$b}"); + let Ok(value) = BigInt::from_str_radix(&bitstring, 2) else { + return None; + }; + + return Some(semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Bitstring( + value, size, + ))), + ty: lhs_ty.as_const(), + }); + } + } + if matches!(lhs_ty, Type::UInt(..)) { + if let semantic::LiteralKind::Int(value) = kind { + // this should have been validated by can_cast_literal_with_value_knowledge + return Some(semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Int(*value))), + ty: lhs_ty.as_const(), + }); + } + } + let result = match (&lhs_ty, &rhs_ty) { + (Type::Float(..), Type::Int(..) | Type::UInt(..)) => { + if let semantic::LiteralKind::Int(value) = kind { + if let Some(value) = safe_i64_to_f64(*value) { + return Some(semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Float( + value, + ))), + ty: lhs_ty.as_const(), + }); + } + self.push_semantic_error(SemanticErrorKind::InvalidCastValueRange( + rhs_ty.to_string(), + lhs_ty.to_string(), + span, + )); + return None; + } + None + } + (Type::Angle(width, _), Type::Float(..)) => { + if let semantic::LiteralKind::Float(value) = kind { + return Some(semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Angle( + Angle::from_f64_maybe_sized(*value, *width), + ))), + ty: lhs_ty.as_const(), + }); + } + None + } + (Type::Float(..), Type::Float(..)) => { + if let semantic::LiteralKind::Float(value) = kind { + return Some(semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Float( + *value, + ))), + ty: lhs_ty.as_const(), + }); + } + None + } + (Type::Complex(..), Type::Complex(..)) => { + if let semantic::LiteralKind::Complex(real, imag) = kind { + return Some(semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Complex( + *real, *imag, + ))), + ty: lhs_ty.as_const(), + }); + } + None + } + (Type::Complex(..), Type::Float(..)) => { + if let semantic::LiteralKind::Float(value) = kind { + return Some(semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Complex( + *value, 0.0, + ))), + ty: lhs_ty.as_const(), + }); + } + None + } + (Type::Complex(..), Type::Int(..) | Type::UInt(..)) => { + // complex requires a double as input, so we need to + // convert the int to a double, then create the complex + if let semantic::LiteralKind::Int(value) = kind { + if let Some(value) = safe_i64_to_f64(*value) { + return Some(semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::Lit( + semantic::LiteralKind::Complex(value, 0.0), + )), + ty: lhs_ty.as_const(), + }); + } + let kind = SemanticErrorKind::InvalidCastValueRange( + "int".to_string(), + "float".to_string(), + span, + ); + self.push_semantic_error(kind); + return None; + } + None + } + (Type::Bit(..), Type::Int(..) | Type::UInt(..)) => { + // we've already checked that the value is 0 or 1 + if let semantic::LiteralKind::Int(value) = kind { + if *value == 0 || *value == 1 { + return Some(semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Int( + *value, + ))), + ty: lhs_ty.as_const(), + }); + } + panic!("value must be 0 or 1"); + } else { + panic!("literal must be an Int"); + } + } + (Type::Int(width, _), Type::Int(_, _) | Type::UInt(_, _)) => { + // we've already checked that this conversion can happen from a signed to unsigned int + match kind { + semantic::LiteralKind::Int(value) => { + return Some(semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Int( + *value, + ))), + ty: lhs_ty.as_const(), + }); + } + semantic::LiteralKind::BigInt(value) => { + if let Some(width) = width { + let mut cap = BigInt::from_i64(1).expect("1 is a valid i64"); + BigInt::shl_assign(&mut cap, width); + if *value >= cap { + self.push_semantic_error(SemanticErrorKind::InvalidCastValueRange( + value.to_string(), + format!("int[{width}]"), + span, + )); + return None; + } + } + return Some(semantic::Expr { + span, + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::BigInt( + value.clone(), + ))), + ty: lhs_ty.as_const(), + }); + } + _ => panic!("literal must be an Int or BigInt"), + } + } + _ => None, + }; + if result.is_none() { + // we assert that the type can be casted + // but we didn't cast it, so this is a bug + panic!("literal type cast failed lhs: {:?}, rhs: {:?}", ty, rhs.ty); + } else { + result + } + } + + fn cast_expr_to_type(&mut self, ty: &Type, expr: &semantic::Expr) -> semantic::Expr { + let Some(cast_expr) = Self::try_cast_expr_to_type(ty, expr) else { + self.push_invalid_cast_error(ty, &expr.ty, expr.span); + return expr.clone(); + }; + cast_expr + } + + fn try_cast_expr_to_type(ty: &Type, expr: &semantic::Expr) -> Option { + if *ty == expr.ty { + // Base case, we shouldn't have gotten here + // but if we did, we can just return the rhs + return Some(expr.clone()); + } + if types_equal_except_const(ty, &expr.ty) { + if expr.ty.is_const() { + // lhs isn't const, but rhs is, we can just return the rhs + let mut expr = expr.clone(); + // relax constness + expr.ty = expr.ty.as_non_const(); + return Some(expr); + } + // the lsh is supposed to be const but is being initialized + // to a non-const value, we can't allow this + return None; + } + // if the target type is wider, we can try to relax the rhs type + // We only do this for float and complex. Int types + // require extra handling for BigInts + match (ty, &expr.ty) { + (Type::Angle(w1, _), Type::Angle(w2, _)) + | (Type::Float(w1, _), Type::Float(w2, _)) + | (Type::Complex(w1, _), Type::Complex(w2, _)) => { + if w1.is_none() && w2.is_some() { + return Some(wrap_expr_in_implicit_cast_expr(ty.clone(), expr.clone())); + } + + if *w1 >= *w2 { + return Some(wrap_expr_in_implicit_cast_expr(ty.clone(), expr.clone())); + } + } + _ => {} + } + // Casting of literals is handled elsewhere. This is for casting expressions + // which cannot be bypassed and must be handled by built-in Q# casts from + // the standard library. + match &expr.ty { + Type::Angle(_, _) => Self::cast_angle_expr_to_type(ty, expr), + Type::Bit(_) => Self::cast_bit_expr_to_type(ty, expr), + Type::Bool(_) => Self::cast_bool_expr_to_type(ty, expr), + Type::Complex(_, _) => cast_complex_expr_to_type(ty, expr), + Type::Float(_, _) => Self::cast_float_expr_to_type(ty, expr), + Type::Int(_, _) | Type::UInt(_, _) => Self::cast_int_expr_to_type(ty, expr), + Type::BitArray(dims, _) => Self::cast_bitarray_expr_to_type(dims, ty, expr), + _ => None, + } + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | angle | Yes | No | No | No | - | Yes | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + fn cast_angle_expr_to_type(ty: &Type, rhs: &semantic::Expr) -> Option { + assert!(matches!(rhs.ty, Type::Angle(..))); + match ty { + Type::Angle(..) | Type::Bit(..) | Type::BitArray(..) | Type::Bool(..) => { + Some(wrap_expr_in_implicit_cast_expr(ty.clone(), rhs.clone())) + } + _ => None, + } + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | bit | Yes | Yes | Yes | No | Yes | - | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + fn cast_bit_expr_to_type(ty: &Type, rhs: &semantic::Expr) -> Option { + assert!(matches!(rhs.ty, Type::Bit(..))); + // There is no operand, choosing the span of the node + // but we could use the expr span as well. + match ty { + &Type::Float(..) => { + // The spec says that this cast isn't supported, but it + // casts to other types that cast to float. For now, we'll + // say it is invalid like the spec. + None + } + &Type::Angle(..) | &Type::Bool(_) | &Type::Int(_, _) | &Type::UInt(_, _) => { + Some(wrap_expr_in_implicit_cast_expr(ty.clone(), rhs.clone())) + } + + _ => None, + } + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | float | Yes | Yes | Yes | - | Yes | No | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// + /// Additional cast to complex + fn cast_float_expr_to_type(ty: &Type, rhs: &semantic::Expr) -> Option { + assert!(matches!(rhs.ty, Type::Float(..))); + match ty { + &Type::Angle(..) + | &Type::Complex(_, _) + | &Type::Int(_, _) + | &Type::UInt(_, _) + | &Type::Bool(_) => { + // this will eventually be a call into Complex(expr, 0.0) + Some(wrap_expr_in_implicit_cast_expr(ty.clone(), rhs.clone())) + } + _ => None, + } + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | bool | - | Yes | Yes | Yes | No | Yes | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + fn cast_bool_expr_to_type(ty: &Type, rhs: &semantic::Expr) -> Option { + assert!(matches!(rhs.ty, Type::Bool(..))); + match ty { + &Type::Bit(_) | &Type::Float(_, _) | &Type::Int(_, _) | &Type::UInt(_, _) => { + Some(wrap_expr_in_implicit_cast_expr(ty.clone(), rhs.clone())) + } + _ => None, + } + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | int | Yes | - | Yes | Yes | No | Yes | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | uint | Yes | Yes | - | Yes | No | Yes | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// + /// Additional cast to ``BigInt`` + #[allow(clippy::too_many_lines)] + fn cast_int_expr_to_type(ty: &Type, rhs: &semantic::Expr) -> Option { + assert!(matches!(rhs.ty, Type::Int(..) | Type::UInt(..))); + + match ty { + Type::BitArray(_, _) + | Type::Float(_, _) + | Type::Int(_, _) + | Type::UInt(_, _) + | Type::Bool(..) + | Type::Bit(..) + | Type::Complex(..) => Some(wrap_expr_in_implicit_cast_expr(ty.clone(), rhs.clone())), + _ => None, + } + } + + fn cast_bitarray_expr_to_type( + dims: &ArrayDimensions, + ty: &Type, + rhs: &semantic::Expr, + ) -> Option { + let ArrayDimensions::One(array_width) = dims else { + return None; + }; + if !matches!(ty, Type::Int(..) | Type::UInt(..)) { + return None; + } + // we know we have a bit array being cast to an int/uint + // verfiy widths + let int_width = ty.width(); + + if int_width.is_none() || (int_width == Some(*array_width)) { + Some(wrap_expr_in_implicit_cast_expr(ty.clone(), rhs.clone())) + } else { + None + } + } + + #[allow(clippy::too_many_lines)] + fn lower_binary_op_expr( + &mut self, + op: syntax::BinOp, + lhs: semantic::Expr, + rhs: semantic::Expr, + span: Span, + ) -> semantic::Expr { + if lhs.ty.is_quantum() { + let kind = SemanticErrorKind::QuantumTypesInBinaryExpression(lhs.span); + self.push_semantic_error(kind); + } + + if rhs.ty.is_quantum() { + let kind = SemanticErrorKind::QuantumTypesInBinaryExpression(rhs.span); + self.push_semantic_error(kind); + } + + let left_type = lhs.ty.clone(); + let right_type = rhs.ty.clone(); + + if Self::binop_requires_bitwise_conversion(op, &left_type) { + // if the operator requires bitwise conversion, we need to determine + // what size of UInt to promote to. If we can't promote to a UInt, we + // push an error and return None. + let (ty, lhs_uint_promotion, rhs_uint_promotion) = + promote_to_uint_ty(&left_type, &right_type); + let Some(ty) = ty else { + let target_ty = Type::UInt(None, left_type.is_const() && right_type.is_const()); + if lhs_uint_promotion.is_none() { + let target_str: String = format!("{target_ty:?}"); + let kind = SemanticErrorKind::CannotCast( + format!("{left_type:?}"), + target_str, + lhs.span, + ); + self.push_semantic_error(kind); + } + if rhs_uint_promotion.is_none() { + let target_str: String = format!("{target_ty:?}"); + let kind = SemanticErrorKind::CannotCast( + format!("{right_type:?}"), + target_str, + rhs.span, + ); + self.push_semantic_error(kind); + } + let bin_expr = semantic::BinaryOpExpr { + op: op.into(), + lhs, + rhs, + }; + let kind = semantic::ExprKind::BinaryOp(bin_expr); + let expr = semantic::Expr { + span, + kind: Box::new(kind), + ty: target_ty, + }; + return expr; + }; + // Now that we know the effective Uint type, we can cast the lhs and rhs + // so that operations can be performed on them. + let new_lhs = self.cast_expr_to_type(&ty, &lhs); + // only cast the rhs if the operator requires symmetric conversion + let new_rhs = if Self::binop_requires_bitwise_symmetric_conversion(op) { + self.cast_expr_to_type(&ty, &rhs) + } else { + rhs + }; + + let bin_expr = semantic::BinaryOpExpr { + lhs: new_lhs, + rhs: new_rhs, + op: op.into(), + }; + let kind = semantic::ExprKind::BinaryOp(bin_expr); + let expr = semantic::Expr { + span, + kind: Box::new(kind), + ty, + }; + + let final_expr = self.cast_expr_to_type(&left_type, &expr); + return final_expr; + } + + // for int, uint, float, and complex, the lesser of the two types is cast to + // the greater of the two. Within each level of complex and float, types with + // greater width are greater than types with lower width. + // complex > float > int/uint + // Q# has built-in functions: IntAsDouble, IntAsBigInt to handle two cases. + // If the width of a float is greater than 64, we can't represent it as a double. + + let ty_constness = lhs.ty.is_const() && rhs.ty.is_const(); + + let (lhs, rhs, ty) = if matches!(op, syntax::BinOp::AndL | syntax::BinOp::OrL) { + let ty = Type::Bool(ty_constness); + let new_lhs = self.cast_expr_to_type(&ty, &lhs); + let new_rhs = self.cast_expr_to_type(&ty, &rhs); + (new_lhs, new_rhs, ty) + } else if binop_requires_asymmetric_angle_op(op, &left_type, &rhs.ty) { + if matches!(op, syntax::BinOp::Div) + && matches!(left_type, Type::Angle(..)) + && matches!(right_type, Type::Angle(..)) + { + // result is uint, we need to promote both sides to match width. + let angle_ty = Type::Angle(promote_width(&left_type, &right_type), ty_constness); + let new_lhs = self.cast_expr_to_type(&angle_ty, &lhs); + let new_rhs = self.cast_expr_to_type(&angle_ty, &rhs); + let int_ty = Type::UInt(angle_ty.width(), ty_constness); + (new_lhs, new_rhs, int_ty) + } else if matches!(left_type, Type::Angle(..)) { + let ty = Type::Angle(left_type.width(), ty_constness); + let new_lhs = self.cast_expr_to_type(&ty, &lhs); + let rhs_ty = Type::UInt(ty.width(), ty_constness); + let new_rhs = self.cast_expr_to_type(&rhs_ty, &rhs); + (new_lhs, new_rhs, ty) + } else { + let lhs_ty = Type::UInt(rhs.ty.width(), ty_constness); + let new_lhs = self.cast_expr_to_type(&lhs_ty, &lhs); + let ty = Type::Angle(rhs.ty.width(), ty_constness); + let new_rhs = self.cast_expr_to_type(&ty, &rhs); + (new_lhs, new_rhs, ty) + } + } else if binop_requires_int_conversion_for_type(op, &left_type, &rhs.ty) { + let ty = Type::Int(None, ty_constness); + let new_lhs = self.cast_expr_to_type(&ty, &lhs); + let new_rhs = self.cast_expr_to_type(&ty, &rhs); + (new_lhs, new_rhs, ty) + } else if requires_symmetric_conversion(op) { + let promoted_type = try_promote_with_casting(&left_type, &right_type); + let new_left = if promoted_type == left_type { + lhs + } else { + match &lhs.kind.as_ref() { + semantic::ExprKind::Lit(kind) => { + if can_cast_literal(&promoted_type, &left_type) + || can_cast_literal_with_value_knowledge(&promoted_type, kind) + { + self.coerce_literal_expr_to_type(&promoted_type, &lhs, kind) + } else { + self.cast_expr_to_type(&promoted_type, &lhs) + } + } + _ => self.cast_expr_to_type(&promoted_type, &lhs), + } + }; + let new_right = if promoted_type == right_type { + rhs + } else { + match &rhs.kind.as_ref() { + semantic::ExprKind::Lit(kind) => { + if can_cast_literal(&promoted_type, &right_type) + || can_cast_literal_with_value_knowledge(&promoted_type, kind) + { + self.coerce_literal_expr_to_type(&promoted_type, &rhs, kind) + } else { + self.cast_expr_to_type(&promoted_type, &rhs) + } + } + _ => self.cast_expr_to_type(&promoted_type, &rhs), + } + }; + (new_left, new_right, promoted_type) + } else if binop_requires_symmetric_uint_conversion(op) { + let ty = Type::UInt(None, ty_constness); + let new_rhs = self.cast_expr_to_type(&ty, &rhs); + (lhs, new_rhs, left_type) + } else { + (lhs, rhs, left_type) + }; + + // now that we have the lhs and rhs expressions, we can create the binary expression + // but we need to check if the chosen operator is supported by the types after + // promotion and conversion. + + let expr = if matches!(ty, Type::Complex(..)) { + if is_complex_binop_supported(op) { + let bin_expr = semantic::BinaryOpExpr { + op: op.into(), + lhs, + rhs, + }; + let kind = semantic::ExprKind::BinaryOp(bin_expr); + semantic::Expr { + span, + kind: Box::new(kind), + ty: ty.clone(), + } + } else { + let kind = SemanticErrorKind::OperatorNotSupportedForTypes( + format!("{op:?}"), + format!("{ty:?}"), + format!("{ty:?}"), + span, + ); + self.push_semantic_error(kind); + err_expr!(ty.clone()) + } + } else { + let bin_expr = semantic::BinaryOpExpr { + op: op.into(), + lhs, + rhs, + }; + let kind = semantic::ExprKind::BinaryOp(bin_expr); + semantic::Expr { + span, + kind: Box::new(kind), + ty: ty.clone(), + } + }; + + let ty = match op.into() { + semantic::BinOp::AndL + | semantic::BinOp::Eq + | semantic::BinOp::Gt + | semantic::BinOp::Gte + | semantic::BinOp::Lt + | semantic::BinOp::Lte + | semantic::BinOp::Neq + | semantic::BinOp::OrL => Type::Bool(ty_constness), + _ => ty, + }; + let mut expr = expr; + expr.ty = ty; + expr + } + + fn binop_requires_bitwise_conversion(op: syntax::BinOp, left_type: &Type) -> bool { + if (matches!( + op, + syntax::BinOp::AndB | syntax::BinOp::OrB | syntax::BinOp::XorB + ) && matches!( + left_type, + Type::Bit(..) | Type::UInt(..) | Type::BitArray(ArrayDimensions::One(_), _) + )) { + return true; + } + if (matches!(op, syntax::BinOp::Shl | syntax::BinOp::Shr) + && matches!( + left_type, + Type::Bit(..) | Type::UInt(..) | Type::BitArray(ArrayDimensions::One(_), _) + )) + { + return true; + } + false + } + + fn binop_requires_bitwise_symmetric_conversion(op: syntax::BinOp) -> bool { + matches!( + op, + syntax::BinOp::AndB + | syntax::BinOp::OrB + | syntax::BinOp::XorB + | syntax::BinOp::Shl + | syntax::BinOp::Shr + ) + } + + fn lower_index_element(&mut self, index: &syntax::IndexElement) -> semantic::IndexElement { + match index { + syntax::IndexElement::DiscreteSet(set) => { + semantic::IndexElement::DiscreteSet(self.lower_discrete_set(set)) + } + syntax::IndexElement::IndexSet(set) => { + semantic::IndexElement::IndexSet(self.lower_index_set(set)) + } + } + } + + fn lower_index_set_item(&mut self, item: &syntax::IndexSetItem) -> semantic::IndexSetItem { + match item { + syntax::IndexSetItem::RangeDefinition(range_definition) => { + semantic::IndexSetItem::RangeDefinition( + self.lower_range_definition(range_definition), + ) + } + syntax::IndexSetItem::Expr(expr) => semantic::IndexSetItem::Expr(self.lower_expr(expr)), + syntax::IndexSetItem::Err => semantic::IndexSetItem::Err, + } + } + + fn lower_enumerable_set(&mut self, set: &syntax::EnumerableSet) -> semantic::EnumerableSet { + match set { + syntax::EnumerableSet::DiscreteSet(set) => { + semantic::EnumerableSet::DiscreteSet(self.lower_discrete_set(set)) + } + syntax::EnumerableSet::RangeDefinition(range_definition) => { + semantic::EnumerableSet::RangeDefinition( + self.lower_range_definition(range_definition), + ) + } + syntax::EnumerableSet::Expr(expr) => { + semantic::EnumerableSet::Expr(self.lower_expr(expr)) + } + } + } + + fn lower_index_set(&mut self, set: &syntax::IndexSet) -> semantic::IndexSet { + let items = set + .values + .iter() + .map(|expr| self.lower_index_set_item(expr)) + .collect::>(); + + semantic::IndexSet { + span: set.span, + values: syntax::list_from_iter(items), + } + } + + fn lower_discrete_set(&mut self, set: &syntax::DiscreteSet) -> semantic::DiscreteSet { + let items = set + .values + .iter() + .map(|expr| self.lower_expr(expr)) + .collect::>(); + + semantic::DiscreteSet { + span: set.span, + values: list_from_iter(items), + } + } + + fn lower_range_definition( + &mut self, + range_definition: &syntax::RangeDefinition, + ) -> semantic::RangeDefinition { + let start = range_definition.start.as_ref().map(|e| self.lower_expr(e)); + let step = range_definition.step.as_ref().map(|e| self.lower_expr(e)); + let end = range_definition.end.as_ref().map(|e| self.lower_expr(e)); + + semantic::RangeDefinition { + span: range_definition.span, + start, + step, + end, + } + } + + fn lower_index_expr(&mut self, expr: &syntax::IndexExpr) -> semantic::Expr { + let collection = self.lower_expr(&expr.collection); + let index = self.lower_index_element(&expr.index); + let indexed_ty = self.get_indexed_type(&collection.ty, expr.span, 1); + + semantic::Expr { + span: expr.span, + kind: Box::new(semantic::ExprKind::IndexExpr(semantic::IndexExpr { + span: expr.span, + collection, + index, + })), + ty: indexed_ty, + } + } + + fn get_indexed_type( + &mut self, + ty: &Type, + span: Span, + num_indices: usize, + ) -> super::types::Type { + if !ty.is_array() { + let kind = SemanticErrorKind::CannotIndexType(format!("{ty:?}"), span); + self.push_semantic_error(kind); + return super::types::Type::Err; + } + + if num_indices > ty.num_dims() { + let kind = SemanticErrorKind::TooManyIndices(span); + self.push_semantic_error(kind); + return super::types::Type::Err; + } + + let mut indexed_ty = ty.clone(); + for _ in 0..num_indices { + let Some(ty) = indexed_ty.get_indexed_type() else { + // we failed to get the indexed type, unknown error + // we should have caught this earlier with the two checks above + let kind = SemanticErrorKind::CannotIndexType(format!("{ty:?}"), span); + self.push_semantic_error(kind); + return super::types::Type::Err; + }; + indexed_ty = ty; + } + indexed_ty + } + + /// Lower an indexed identifier expression + /// This is an identifier with *zero* or more indices + /// we tranform this into two different cases: + /// 1. An identifier with zero indices + /// 2. An identifier with one or more index + /// + /// This changes the type of expression we return to simplify downstream compilation + fn lower_indexed_ident_expr(&mut self, indexed_ident: &syntax::IndexedIdent) -> semantic::Expr { + let ident = indexed_ident.name.clone(); + + // if we have no indices, we can just lower the identifier + if indexed_ident.indices.is_empty() { + return self.lower_ident_expr(&ident); + } + + let indices = indexed_ident + .indices + .iter() + .map(|index| self.lower_index_element(index)); + let indices = list_from_iter(indices); + + let Some((symbol_id, lhs_symbol)) = self.symbols.get_symbol_by_name(&ident.name) else { + self.push_missing_symbol_error(ident.name, ident.span); + return err_expr!(Type::Err, indexed_ident.span); + }; + + let ty = lhs_symbol.ty.clone(); + // use the supplied number of indicies rathar than the number of indicies we lowered + let ty = self.get_indexed_type(&ty, indexed_ident.span, indexed_ident.indices.len()); + + semantic::Expr { + span: indexed_ident.span, + kind: Box::new(semantic::ExprKind::IndexedIdentifier( + semantic::IndexedIdent { + span: indexed_ident.span, + name_span: ident.span, + index_span: indexed_ident.index_span, + symbol_id, + indices, + }, + )), + ty, + } + } + + #[allow(clippy::unused_self)] + fn lower_gate_operand(&mut self, operand: &syntax::GateOperand) -> semantic::GateOperand { + let kind = match &operand.kind { + syntax::GateOperandKind::IndexedIdent(indexed_ident) => { + semantic::GateOperandKind::Expr(Box::new( + self.lower_indexed_ident_expr(indexed_ident), + )) + } + syntax::GateOperandKind::HardwareQubit(hw) => { + semantic::GateOperandKind::HardwareQubit(Self::lower_hardware_qubit(hw)) + } + syntax::GateOperandKind::Err => semantic::GateOperandKind::Err, + }; + semantic::GateOperand { + span: operand.span, + kind, + } + } + + fn lower_hardware_qubit(hw: &syntax::HardwareQubit) -> semantic::HardwareQubit { + semantic::HardwareQubit { + span: hw.span, + name: hw.name.clone(), + } + } + + fn push_invalid_cast_error(&mut self, target_ty: &Type, expr_ty: &Type, span: Span) { + let rhs_ty_name = format!("{expr_ty:?}"); + let lhs_ty_name = format!("{target_ty:?}"); + let kind = SemanticErrorKind::CannotCast(rhs_ty_name, lhs_ty_name, span); + self.push_semantic_error(kind); + } + + fn push_invalid_literal_cast_error(&mut self, target_ty: &Type, expr_ty: &Type, span: Span) { + let rhs_ty_name = format!("{expr_ty:?}"); + let lhs_ty_name = format!("{target_ty:?}"); + let kind = SemanticErrorKind::CannotCastLiteral(rhs_ty_name, lhs_ty_name, span); + self.push_semantic_error(kind); + } + + /// Pushes a missing symbol error with the given name + /// This is a convenience method for pushing a `SemanticErrorKind::UndefinedSymbol` error. + pub fn push_missing_symbol_error>(&mut self, name: S, span: Span) { + let kind = SemanticErrorKind::UndefinedSymbol(name.as_ref().to_string(), span); + self.push_semantic_error(kind); + } + + /// Pushes a redefined symbol error with the given name and span. + /// This is a convenience method for pushing a `SemanticErrorKind::RedefinedSymbol` error. + pub fn push_redefined_symbol_error>(&mut self, name: S, span: Span) { + let kind = SemanticErrorKind::RedefinedSymbol(name.as_ref().to_string(), span); + self.push_semantic_error(kind); + } + + /// Pushes an unsupported error with the supplied message. + pub fn push_unsupported_error_message>(&mut self, message: S, span: Span) { + let kind = SemanticErrorKind::NotSupported(message.as_ref().to_string(), span); + self.push_semantic_error(kind); + } + + pub fn push_unsuported_in_this_version_error_message>( + &mut self, + message: S, + minimum_supported_version: &Version, + span: Span, + ) { + let message = message.as_ref().to_string(); + let msv = minimum_supported_version.to_string(); + let kind = SemanticErrorKind::NotSupportedInThisVersion(message, msv, span); + self.push_semantic_error(kind); + } + + /// Pushes an unimplemented error with the supplied message. + pub fn push_unimplemented_error_message>(&mut self, message: S, span: Span) { + let kind = SemanticErrorKind::Unimplemented(message.as_ref().to_string(), span); + self.push_semantic_error(kind); + } + + /// Pushes a semantic error with the given kind. + pub fn push_semantic_error(&mut self, kind: SemanticErrorKind) { + let kind = crate::ErrorKind::Semantic(crate::semantic::Error(kind)); + let error = self.create_err(kind); + self.errors.push(error); + } + + /// Pushes a const eval error with the given kind. + pub fn push_const_eval_error(&mut self, kind: ConstEvalError) { + let kind = crate::ErrorKind::ConstEval(kind); + let error = self.create_err(kind); + self.errors.push(error); + } + + /// Creates an error from the given kind with the current source mapping. + fn create_err(&self, kind: crate::ErrorKind) -> WithSource { + let error = crate::Error(kind); + WithSource::from_map(&self.source_map, error) + } +} + +fn wrap_expr_in_implicit_cast_expr(ty: Type, rhs: semantic::Expr) -> semantic::Expr { + semantic::Expr { + span: rhs.span, + kind: Box::new(semantic::ExprKind::Cast(semantic::Cast { + span: Span::default(), + expr: rhs, + ty: ty.clone(), + })), + ty, + } +} + +/// +----------------+-------------------------------------------------------------+ +/// | Allowed casts | Casting To | +/// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ +/// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | +/// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ +/// | complex | ?? | ?? | ?? | ?? | No | ?? | No | No | +/// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ +fn cast_complex_expr_to_type(ty: &Type, rhs: &semantic::Expr) -> Option { + assert!(matches!(rhs.ty, Type::Complex(..))); + + if matches!((ty, &rhs.ty), (Type::Complex(..), Type::Complex(..))) { + // we are both complex, but our widths are different. If both + // had implicit widths, we would have already matched for the + // (None, None). If the rhs width is bigger, we will return + // None to let the cast fail + + // Here, we can safely cast the rhs to the lhs type if the + // lhs width can hold the rhs's width + if ty.width().is_none() && rhs.ty.width().is_some() { + return Some(wrap_expr_in_implicit_cast_expr(ty.clone(), rhs.clone())); + } + if ty.width() >= rhs.ty.width() { + return Some(wrap_expr_in_implicit_cast_expr(ty.clone(), rhs.clone())); + } + } + None +} + +fn get_identifier_name(identifier: &syntax::Identifier) -> std::rc::Rc { + match identifier { + syntax::Identifier::Ident(ident) => ident.name.clone(), + syntax::Identifier::IndexedIdent(ident) => ident.name.name.clone(), + } +} + +fn try_get_qsharp_name_and_implicit_modifiers>( + gate_name: S, + name_span: Span, +) -> Option<(String, semantic::QuantumGateModifier)> { + use semantic::GateModifierKind::*; + + let make_modifier = |kind| semantic::QuantumGateModifier { + span: name_span, + modifier_keyword_span: name_span, + kind, + }; + + match gate_name.as_ref() { + "cy" => Some(("y".to_string(), make_modifier(Ctrl(1)))), + "cz" => Some(("z".to_string(), make_modifier(Ctrl(1)))), + "ch" => Some(("h".to_string(), make_modifier(Ctrl(1)))), + "crx" => Some(("rx".to_string(), make_modifier(Ctrl(1)))), + "cry" => Some(("ry".to_string(), make_modifier(Ctrl(1)))), + "crz" => Some(("rz".to_string(), make_modifier(Ctrl(1)))), + "cswap" => Some(("swap".to_string(), make_modifier(Ctrl(1)))), + "sdg" => Some(("s".to_string(), make_modifier(Inv))), + "tdg" => Some(("t".to_string(), make_modifier(Inv))), + // Gates for OpenQASM 2 backwards compatibility + "CX" => Some(("x".to_string(), make_modifier(Ctrl(1)))), + "cphase" => Some(("phase".to_string(), make_modifier(Ctrl(1)))), + _ => None, + } +} diff --git a/compiler/qsc_qasm/src/semantic/symbols.rs b/compiler/qsc_qasm/src/semantic/symbols.rs new file mode 100644 index 0000000000..174a3d93c5 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/symbols.rs @@ -0,0 +1,664 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use core::f64; +use qsc_data_structures::{index_map::IndexMap, span::Span}; +use rustc_hash::FxHashMap; +use std::rc::Rc; + +use super::{ + ast::{Expr, ExprKind, LiteralKind}, + types::Type, +}; + +/// We need a symbol table to keep track of the symbols in the program. +/// The scoping rules for QASM3 are slightly different from Q#. This also +/// means that we need to keep track of the input and output symbols in the +/// program. Additionally, we need to keep track of the types of the symbols +/// in the program for type checking. +/// Q# and QASM have different type systems, so we track the QASM semantic. +/// +/// A symbol ID is a unique identifier for a symbol in the symbol table. +/// This is used to look up symbols in the symbol table. +/// Every symbol in the symbol table has a unique ID. +#[derive(Debug, Default, Clone, Copy)] +pub struct SymbolId(pub u32); + +impl SymbolId { + /// The successor of this ID. + #[must_use] + pub fn successor(self) -> Self { + Self(self.0 + 1) + } +} + +impl From for SymbolId { + fn from(val: u32) -> Self { + SymbolId(val) + } +} + +impl From for u32 { + fn from(id: SymbolId) -> Self { + id.0 + } +} + +impl From for usize { + fn from(value: SymbolId) -> Self { + value.0 as usize + } +} + +impl From for SymbolId { + fn from(value: usize) -> Self { + SymbolId( + value + .try_into() + .unwrap_or_else(|_| panic!("Value, {value}, does not fit into SymbolId")), + ) + } +} + +impl PartialEq for SymbolId { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for SymbolId {} + +impl PartialOrd for SymbolId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for SymbolId { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl std::hash::Hash for SymbolId { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl std::fmt::Display for SymbolId { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} + +#[derive(Debug, Clone)] +pub struct Symbol { + pub name: String, + pub span: Span, + pub ty: Type, + pub qsharp_ty: crate::types::Type, + pub io_kind: IOKind, + /// Used for const evaluation. This field should only be Some(_) + /// if the symbol is const. This Expr holds the whole const expr + /// unevaluated. + const_expr: Option>, +} + +impl Symbol { + #[must_use] + pub fn new( + name: &str, + span: Span, + ty: Type, + qsharp_ty: crate::types::Type, + io_kind: IOKind, + ) -> Self { + Self { + name: name.to_string(), + span, + ty, + qsharp_ty, + io_kind, + const_expr: None, + } + } + + #[must_use] + pub fn with_const_expr(self, value: Rc) -> Self { + assert!( + value.ty.is_const(), + "this builder pattern should only be used with const expressions" + ); + Symbol { + const_expr: Some(value), + ..self + } + } + + /// Returns true if they symbol's value is a const expr. + #[must_use] + pub fn is_const(&self) -> bool { + self.const_expr.is_some() + } + + /// Returns the value of the symbol. + #[must_use] + pub fn get_const_expr(&self) -> Rc { + if let Some(val) = &self.const_expr { + val.clone() + } else { + unreachable!("this function should only be called on const symbols"); + } + } +} + +impl std::fmt::Display for Symbol { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + use crate::display_utils; + display_utils::writeln_header(f, "Symbol", self.span)?; + display_utils::writeln_field(f, "name", &self.name)?; + display_utils::writeln_field(f, "type", &self.ty)?; + display_utils::writeln_field(f, "qsharp_type", &self.qsharp_ty)?; + display_utils::write_field(f, "io_kind", &self.io_kind) + } +} + +/// A symbol in the symbol table. +/// Default Q# type is Unit +impl Default for Symbol { + fn default() -> Self { + Self { + name: String::default(), + span: Span::default(), + ty: Type::Err, + qsharp_ty: crate::types::Type::Tuple(vec![]), + io_kind: IOKind::default(), + const_expr: None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SymbolError { + /// The symbol already exists in the symbol table, at the current scope. + AlreadyExists, +} + +/// Symbols have a an I/O kind that determines if they are input or output, or unspecified. +/// The default I/O kind means no explicit kind was part of the decl. +/// There is a specific statement for io decls which sets the I/O kind appropriately. +/// This is used to determine the input and output symbols in the program. +#[derive(Copy, Default, Debug, Clone, PartialEq, Eq)] +pub enum IOKind { + #[default] + Default, + Input, + Output, +} + +impl std::fmt::Display for IOKind { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + IOKind::Default => write!(f, "Default"), + IOKind::Input => write!(f, "Input"), + IOKind::Output => write!(f, "Output"), + } + } +} + +/// A scope is a collection of symbols and a kind. The kind determines semantic +/// rules for compliation as shadowing and decl rules vary by scope kind. +pub(crate) struct Scope { + /// A map from symbol name to symbol ID. + name_to_id: FxHashMap, + /// A map from symbol ID to symbol. + id_to_symbol: FxHashMap>, + /// The order in which symbols were inserted into the scope. + /// This is used to determine the order of symbols in the output. + order: Vec, + /// The kind of the scope. + kind: ScopeKind, +} + +impl Scope { + pub fn new(kind: ScopeKind) -> Self { + Self { + name_to_id: FxHashMap::default(), + id_to_symbol: FxHashMap::default(), + order: vec![], + kind, + } + } + + /// Inserts the symbol into the current scope. + /// Returns the ID of the symbol. + /// + /// # Errors + /// + /// This function will return an error if a symbol of the same name has already + /// been declared in this scope. + pub fn insert_symbol(&mut self, id: SymbolId, symbol: Rc) -> Result<(), SymbolError> { + if self.name_to_id.contains_key(&symbol.name) { + return Err(SymbolError::AlreadyExists); + } + self.name_to_id.insert(symbol.name.clone(), id); + self.id_to_symbol.insert(id, symbol); + self.order.push(id); + Ok(()) + } + + pub fn get_symbol_by_name(&self, name: &str) -> Option<(SymbolId, Rc)> { + self.name_to_id + .get(name) + .and_then(|id| self.id_to_symbol.get(id).map(|s| (*id, s.clone()))) + } + + fn get_ordered_symbols(&self) -> Vec> { + self.order + .iter() + .map(|id| self.id_to_symbol.get(id).expect("ID should exist").clone()) + .collect() + } +} + +/// A symbol table is a collection of scopes and manages the symbol ids. +pub struct SymbolTable { + scopes: Vec, + symbols: IndexMap>, + current_id: SymbolId, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ScopeKind { + /// Global scope, which is the current scope only when no other scopes are active. + /// This is the only scope where gates, qubits, and arrays can be declared. + Global, + /// Function scopes need to remember their return type, so that `return` stmts + /// can do an implicit cast to the correct type, if any; + Function(Rc), + Gate, + Block, + Loop, +} + +const BUILTIN_SYMBOLS: [(&str, f64); 6] = [ + ("pi", f64::consts::PI), + ("π", f64::consts::PI), + ("tau", f64::consts::TAU), + ("τ", f64::consts::TAU), + ("euler", f64::consts::E), + ("ℇ", f64::consts::E), +]; + +impl Default for SymbolTable { + fn default() -> Self { + let global = Scope::new(ScopeKind::Global); + + let mut slf = Self { + scopes: vec![global], + symbols: IndexMap::default(), + current_id: SymbolId::default(), + }; + + slf.insert_symbol(Symbol { + name: "U".to_string(), + span: Span::default(), + ty: Type::Gate(3, 1), + qsharp_ty: crate::types::Type::Callable(crate::types::CallableKind::Operation, 3, 1), + io_kind: IOKind::Default, + const_expr: None, + }) + .unwrap_or_else(|_| panic!("Failed to insert symbol: U")); + + slf.insert_symbol(Symbol { + name: "gphase".to_string(), + span: Span::default(), + ty: Type::Gate(1, 0), + qsharp_ty: crate::types::Type::Callable(crate::types::CallableKind::Operation, 1, 0), + io_kind: IOKind::Default, + const_expr: None, + }) + .unwrap_or_else(|_| panic!("Failed to insert symbol: gphase")); + + // Define global constants. + for (symbol, val) in BUILTIN_SYMBOLS { + let ty = Type::Float(None, true); + let expr = Expr { + span: Span::default(), + kind: Box::new(ExprKind::Lit(LiteralKind::Float(val))), + ty: ty.clone(), + }; + + slf.insert_symbol(Symbol { + name: symbol.to_string(), + span: Span::default(), + ty, + qsharp_ty: crate::types::Type::Double(true), + io_kind: IOKind::Default, + const_expr: Some(Rc::new(expr)), + }) + .unwrap_or_else(|_| panic!("Failed to insert symbol: {symbol}")); + } + + slf + } +} + +impl SymbolTable { + pub fn push_scope(&mut self, kind: ScopeKind) { + assert!(kind != ScopeKind::Global, "Cannot push a global scope"); + self.scopes.push(Scope::new(kind)); + } + + pub fn pop_scope(&mut self) { + assert!(self.scopes.len() != 1, "Cannot pop the global scope"); + self.scopes.pop(); + } + + pub fn insert_symbol(&mut self, symbol: Symbol) -> Result { + let symbol = Rc::new(symbol); + let id = self.current_id; + match self + .scopes + .last_mut() + .expect("At least one scope should be available") + .insert_symbol(id, symbol.clone()) + { + Ok(()) => { + self.current_id = self.current_id.successor(); + self.symbols.insert(id, symbol); + Ok(id) + } + Err(SymbolError::AlreadyExists) => Err(SymbolError::AlreadyExists), + } + } + + fn insert_err_symbol(&mut self, name: &str, span: Span) -> (SymbolId, Rc) { + let symbol = Rc::new(Symbol { + name: name.to_string(), + span, + ty: Type::Err, + qsharp_ty: crate::types::Type::Err, + io_kind: IOKind::Default, + const_expr: None, + }); + let id = self.current_id; + self.current_id = self.current_id.successor(); + self.symbols.insert(id, symbol.clone()); + (id, symbol) + } + + /// Gets the symbol with the given ID, or creates it with the given name and span. + /// the boolean value indicates if the symbol was created or not. + pub fn try_get_existing_or_insert_err_symbol( + &mut self, + name: &str, + span: Span, + ) -> Result<(SymbolId, Rc), (SymbolId, Rc)> { + // if we have the symbol, return it, otherswise create it with err values + if let Some((id, symbol)) = self.get_symbol_by_name(name) { + return Ok((id, symbol.clone())); + } + // if we don't have the symbol, create it with err values + Err(self.insert_err_symbol(name, span)) + } + + pub fn try_insert_or_get_existing(&mut self, symbol: Symbol) -> Result { + let name = symbol.name.clone(); + if let Ok(symbol_id) = self.insert_symbol(symbol) { + Ok(symbol_id) + } else { + let symbol_id = self + .get_symbol_by_name(&name) + .map(|(id, _)| id) + .expect("msg"); + Err(symbol_id) + } + } + + /// Gets the symbol with the given name. This should only be used if you don't + /// have the symbold ID. This function will search the scopes in reverse order + /// and return the first symbol with the given name following the scoping rules. + #[must_use] + pub fn get_symbol_by_name(&self, name: S) -> Option<(SymbolId, Rc)> + where + S: AsRef, + { + let scopes = self.scopes.iter().rev(); + let predicate = |x: &Scope| { + matches!( + x.kind, + ScopeKind::Block | ScopeKind::Loop | ScopeKind::Function(..) | ScopeKind::Gate + ) + }; + + // Use scan to track the last item that returned false + let mut last_false = None; + let _ = scopes + .scan(&mut last_false, |state, item| { + if !predicate(item) { + **state = Some(item); + } + Some(predicate(item)) + }) + .take_while(|&x| x) + .last(); + let mut scopes = self.scopes.iter().rev(); + while let Some(scope) = scopes + .by_ref() + .take_while(|arg0: &&Scope| predicate(arg0)) + .next() + { + if let Some((id, symbol)) = scope.get_symbol_by_name(name.as_ref()) { + return Some((id, symbol)); + } + } + + if let Some(scope) = last_false { + if let Some((id, symbol)) = scope.get_symbol_by_name(name.as_ref()) { + if symbol.ty.is_const() + || matches!(symbol.ty, Type::Gate(..) | Type::Void | Type::Function(..)) + || self.is_scope_rooted_in_global() + { + return Some((id, symbol)); + } + } + } + // we should be at the global, function, or gate scope now + for scope in scopes { + if let Some((id, symbol)) = scope.get_symbol_by_name(name.as_ref()) { + if symbol.ty.is_const() + || matches!(symbol.ty, Type::Gate(..) | Type::Void | Type::Function(..)) + { + return Some((id, symbol)); + } + } + } + + None + } + + #[must_use] + pub fn is_symbol_outside_most_inner_gate_or_function_scope(&self, symbol_id: SymbolId) -> bool { + for scope in self.scopes.iter().rev() { + if scope.id_to_symbol.contains_key(&symbol_id) { + return false; + } + if matches!( + scope.kind, + ScopeKind::Gate | ScopeKind::Function(..) | ScopeKind::Global + ) { + return true; + } + } + unreachable!("when the loop ends we will have visited at least the Global scope"); + } + + #[must_use] + pub fn is_current_scope_global(&self) -> bool { + matches!(self.scopes.last(), Some(scope) if scope.kind == ScopeKind::Global) + } + + #[must_use] + pub fn is_scope_rooted_in_subroutine(&self) -> bool { + self.scopes + .iter() + .rev() + .any(|scope| matches!(scope.kind, ScopeKind::Function(..))) + } + + /// Returns `None` if the current scope is not rooted in a subroutine. + /// Otherwise, returns the return type of the subroutine. + #[must_use] + pub fn get_subroutine_return_ty(&self) -> Option> { + for scope in self.scopes.iter().rev() { + if let ScopeKind::Function(return_ty) = &scope.kind { + return Some(return_ty.clone()); + } + } + None + } + + #[must_use] + pub fn is_scope_rooted_in_gate_or_subroutine(&self) -> bool { + self.scopes + .iter() + .rev() + .any(|scope| matches!(scope.kind, ScopeKind::Gate | ScopeKind::Function(..))) + } + + #[must_use] + pub fn is_scope_rooted_in_loop_scope(&self) -> bool { + for scope in self.scopes.iter().rev() { + if matches!(scope.kind, ScopeKind::Loop) { + return true; + } + + // Even though semantically correct qasm3 doesn't allow function + // or gate scopes outside the global scope, the user could write + // incorrect qasm3 while editing. This if statement warns the user + // if they write something like: + // while true { + // def f() { break; } + // } + // + // Note that the `break` in the example will be rooted in a loop + // scope unless we include the following condition. + if matches!(scope.kind, ScopeKind::Function(..) | ScopeKind::Gate) { + return false; + } + } + false + } + + #[must_use] + pub fn is_scope_rooted_in_global(&self) -> bool { + for scope in self.scopes.iter().rev() { + if matches!(scope.kind, ScopeKind::Function(..) | ScopeKind::Gate) { + return false; + } + } + true + } + + /// Get the input symbols in the program. + pub(crate) fn get_input(&self) -> Option>> { + let io_input = self.get_io_input(); + if io_input.is_empty() { + None + } else { + Some(io_input) + } + } + + /// Get the output symbols in the program. + /// Output symbols are either inferred or explicitly declared. + /// If there are no explicitly declared output symbols, then the inferred + /// output symbols are returned. + pub(crate) fn get_output(&self) -> Option>> { + let io_ouput = self.get_io_output(); + if io_ouput.is_some() { + io_ouput + } else { + self.get_inferred_output() + } + } + + /// Get all symbols in the global scope that are inferred output symbols. + /// Any global symbol that is not a built-in symbol and has a type that is + /// inferred to be an output type is considered an inferred output symbol. + fn get_inferred_output(&self) -> Option>> { + let mut symbols = vec![]; + self.scopes + .iter() + .filter(|scope| scope.kind == ScopeKind::Global) + .for_each(|scope| { + for symbol in scope + .get_ordered_symbols() + .into_iter() + .filter(|symbol| { + !BUILTIN_SYMBOLS + .map(|pair| pair.0) + .contains(&symbol.name.as_str()) + }) + .filter(|symbol| symbol.io_kind == IOKind::Default) + { + if symbol.ty.is_inferred_output_type() { + symbols.push(symbol); + } + } + }); + if symbols.is_empty() { + None + } else { + Some(symbols) + } + } + + /// Get all symbols in the global scope that are output symbols. + fn get_io_output(&self) -> Option>> { + let mut symbols = vec![]; + for scope in self + .scopes + .iter() + .filter(|scope| scope.kind == ScopeKind::Global) + { + for symbol in scope.get_ordered_symbols() { + if symbol.io_kind == IOKind::Output { + symbols.push(symbol); + } + } + } + if symbols.is_empty() { + None + } else { + Some(symbols) + } + } + + /// Get all symbols in the global scope that are input symbols. + fn get_io_input(&self) -> Vec> { + let mut symbols = vec![]; + for scope in self + .scopes + .iter() + .filter(|scope| scope.kind == ScopeKind::Global) + { + for symbol in scope.get_ordered_symbols() { + if symbol.io_kind == IOKind::Input { + symbols.push(symbol); + } + } + } + symbols + } +} + +impl std::ops::Index for SymbolTable { + type Output = Rc; + + fn index(&self, index: SymbolId) -> &Self::Output { + self.symbols.get(index).expect("Symbol should exist") + } +} diff --git a/compiler/qsc_qasm/src/semantic/tests.rs b/compiler/qsc_qasm/src/semantic/tests.rs new file mode 100644 index 0000000000..133dcb3c76 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests.rs @@ -0,0 +1,312 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::fmt::Write; + +pub mod assignment; +pub mod decls; + +pub mod expression; +pub mod statements; + +use super::parse_source; +use crate::io::InMemorySourceResolver; +use crate::io::SourceResolver; +use expect_test::expect; +use expect_test::Expect; +use miette::Report; +use std::path::Path; +use std::sync::Arc; + +pub(super) fn check(input: &str, expect: &Expect) { + check_map(input, expect, |p, _| p.to_string()); +} + +pub(super) fn check_classical_decl(input: &str, expect: &Expect) { + check_map(input, expect, |program, symbol_table| { + let kind = program + .statements + .first() + .expect("reading first statement") + .kind + .clone(); + let super::ast::StmtKind::ClassicalDecl(decl) = kind.as_ref() else { + panic!("expected classical declaration statement"); + }; + let mut value = decl.to_string(); + value.push('\n'); + let symbol = &symbol_table[decl.symbol_id]; + write!(value, "[{}] {symbol}", decl.symbol_id).expect("writing symbol id"); + value + }); +} + +pub(super) fn check_classical_decls(input: &str, expect: &Expect) { + check_map(input, expect, |program, symbol_table| { + let kinds = program + .statements + .iter() + .map(|stmt| stmt.kind.as_ref().clone()) + .collect::>(); + let mut value = String::new(); + for kind in &kinds { + let (symbol_id, str) = match kind { + super::ast::StmtKind::ClassicalDecl(decl) => (decl.symbol_id, decl.to_string()), + super::ast::StmtKind::OutputDeclaration(decl) => (decl.symbol_id, decl.to_string()), + super::ast::StmtKind::Assign(stmt) => (stmt.symbol_id, stmt.to_string()), + super::ast::StmtKind::AssignOp(stmt) => (stmt.symbol_id, stmt.to_string()), + _ => panic!("unsupported stmt type {kind}"), + }; + + value.push_str(&str); + value.push('\n'); + let symbol = &symbol_table[symbol_id]; + write!(value, "[{symbol_id}] {symbol}").expect("writing symbol id"); + value.push('\n'); + } + + value + }); +} + +pub(super) fn check_stmt_kind(input: &str, expect: &Expect) { + check_map(input, expect, |p, _| { + p.statements + .first() + .expect("reading first statement") + .kind + .to_string() + }); +} + +pub(super) fn check_stmt_kinds(input: &str, expect: &Expect) { + check_map(input, expect, |p, _| { + p.statements + .iter() + .fold(String::new(), |acc, x| format!("{acc}{}\n", x.kind)) + }); +} + +fn check_map( + input: S, + expect: &Expect, + selector: impl FnOnce(&super::ast::Program, &super::symbols::SymbolTable) -> String, +) where + S: AsRef, +{ + let mut resolver = InMemorySourceResolver::from_iter([("test".into(), input.as_ref().into())]); + let res = parse_source(input, "test", &mut resolver); + + let errors = res.all_errors(); + + assert!( + !res.has_syntax_errors(), + "syntax errors: {:?}", + res.sytax_errors() + ); + + if errors.is_empty() { + expect.assert_eq(&selector(&res.program, &res.symbols)); + } else { + expect.assert_eq(&format!( + "{}\n\n{:?}", + res.program, + errors + .iter() + .map(|e| Report::new(e.clone())) + .collect::>() + )); + } +} + +pub(super) fn check_all

( + path: P, + sources: impl IntoIterator, Arc)>, + expect: &Expect, +) where + P: AsRef, +{ + check_map_all(path, sources, expect, |p, _| p.to_string()); +} + +fn check_map_all

( + path: P, + sources: impl IntoIterator, Arc)>, + expect: &Expect, + selector: impl FnOnce(&super::ast::Program, &super::symbols::SymbolTable) -> String, +) where + P: AsRef, +{ + let mut resolver = InMemorySourceResolver::from_iter(sources); + let source = resolver + .resolve(path.as_ref()) + .map_err(|e| vec![e]) + .expect("could not load source") + .1; + let res = parse_source(source, path, &mut resolver); + + let errors = res.all_errors(); + + assert!( + !res.has_syntax_errors(), + "syntax errors: {:?}", + res.sytax_errors() + ); + + if errors.is_empty() { + expect.assert_eq(&selector(&res.program, &res.symbols)); + } else { + expect.assert_eq(&format!( + "{}\n\n{:?}", + res.program, + errors + .iter() + .map(|e| Report::new(e.clone())) + .collect::>() + )); + } +} + +#[test] +#[allow(clippy::too_many_lines)] +fn semantic_errors_map_to_their_corresponding_file_specific_spans() { + let source0 = r#"OPENQASM 3.0; + include "stdgates.inc"; + include "source1.qasm"; + bit c = r; // undefined symbol r + "#; + let source1 = r#"include "source2.qasm"; + angle j = 7.0; + float k = j + false; // invalid cast"#; + let source2 = "bit l = 1; + bool l = v && l; // undefined y, redefine l"; + let all_sources = [ + ("source0.qasm".into(), source0.into()), + ("source1.qasm".into(), source1.into()), + ("source2.qasm".into(), source2.into()), + ]; + + check_all( + "source0.qasm", + all_sources, + &expect![[r#" + Program: + version: 3.0 + statements: + Stmt [196-206]: + annotations: + kind: ClassicalDeclarationStmt [196-206]: + symbol_id: 32 + ty_span: [196-199] + init_expr: Expr [204-205]: + ty: Bit(true) + kind: Lit: Bit(1) + Stmt [211-227]: + annotations: + kind: ClassicalDeclarationStmt [211-227]: + symbol_id: 32 + ty_span: [211-215] + init_expr: Expr [220-226]: + ty: Bool(false) + kind: BinaryOpExpr: + op: AndL + lhs: Expr [220-221]: + ty: Err + kind: SymbolId(33) + rhs: Expr [225-226]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [225-226]: + ty: Bit(false) + kind: SymbolId(32) + Stmt [140-154]: + annotations: + kind: ClassicalDeclarationStmt [140-154]: + symbol_id: 34 + ty_span: [140-145] + init_expr: Expr [150-153]: + ty: Angle(None, true) + kind: Lit: Angle(0.7168146928204138) + Stmt [159-179]: + annotations: + kind: ClassicalDeclarationStmt [159-179]: + symbol_id: 35 + ty_span: [159-164] + init_expr: Expr [169-178]: + ty: Float(None, false) + kind: BinaryOpExpr: + op: Add + lhs: Expr [169-170]: + ty: Angle(None, false) + kind: SymbolId(34) + rhs: Expr [173-178]: + ty: Float(None, false) + kind: Cast [0-0]: + ty: Float(None, false) + expr: Expr [173-178]: + ty: Bool(true) + kind: Lit: Bool(false) + Stmt [74-84]: + annotations: + kind: ClassicalDeclarationStmt [74-84]: + symbol_id: 37 + ty_span: [74-77] + init_expr: Expr [82-83]: + ty: Err + kind: SymbolId(36) + + [Qasm.Lowerer.UndefinedSymbol + + x undefined symbol: v + ,-[source2.qasm:2:14] + 1 | bit l = 1; + 2 | bool l = v && l; // undefined y, redefine l + : ^ + `---- + , Qasm.Lowerer.CannotCast + + x cannot cast expression of type Err to type Bool(false) + ,-[source2.qasm:2:14] + 1 | bit l = 1; + 2 | bool l = v && l; // undefined y, redefine l + : ^ + `---- + , Qasm.Lowerer.RedefinedSymbol + + x redefined symbol: l + ,-[source2.qasm:2:10] + 1 | bit l = 1; + 2 | bool l = v && l; // undefined y, redefine l + : ^ + `---- + , Qasm.Lowerer.CannotCast + + x cannot cast expression of type Angle(None, false) to type Float(None, + | false) + ,-[source1.qasm:3:15] + 2 | angle j = 7.0; + 3 | float k = j + false; // invalid cast + : ^ + `---- + , Qasm.Lowerer.UndefinedSymbol + + x undefined symbol: r + ,-[source0.qasm:4:13] + 3 | include "source1.qasm"; + 4 | bit c = r; // undefined symbol r + : ^ + 5 | + `---- + , Qasm.Lowerer.CannotCast + + x cannot cast expression of type Err to type Bit(false) + ,-[source0.qasm:4:13] + 3 | include "source1.qasm"; + 4 | bit c = r; // undefined symbol r + : ^ + 5 | + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/assignment.rs b/compiler/qsc_qasm/src/semantic/tests/assignment.rs new file mode 100644 index 0000000000..170dd08e2e --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/assignment.rs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use super::check; + +#[test] +#[ignore = "Not yet implemented"] +fn too_many_indices_in_indexed_assignment() { + check( + r#" + array[float[32], 3, 2] multiDim = {{1.1, 1.2}, {2.1, 2.2}, {3.1, 3.2}}; + multiDim[1, 1, 3] = 2.3; + "#, + &expect![[r#""#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/decls.rs b/compiler/qsc_qasm/src/semantic/tests/decls.rs new file mode 100644 index 0000000000..38f20f5508 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/decls.rs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +mod angle; +mod bit; +mod bool; +mod complex; +mod creg; +mod duration; +mod extern_decl; +mod float; +mod int; +mod qreg; +mod stretch; +mod uint; + +use expect_test::expect; + +use super::check; + +#[test] +#[ignore = "Not yet implemented"] +fn duration_and_stretch_types_without_init_exprs() { + check( + r#" + duration i; + stretch n; + "#, + &expect![[r#" + + + [Qasm.Compile.NotSupported + + x Duration type values are not supported. + ,-[test:2:9] + 1 | + 2 | duration i; + : ^^^^^^^^ + 3 | stretch n; + `---- + , Qasm.Compile.NotSupported + + x Stretch type values are not supported. + ,-[test:3:9] + 2 | duration i; + 3 | stretch n; + : ^^^^^^^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn scalar_ty_designator_must_be_positive() { + check( + "int[-5] i;", + &expect![[r#" + Program: + version: + statements: + Stmt [0-10]: + annotations: + kind: ClassicalDeclarationStmt [0-10]: + symbol_id: 8 + ty_span: [0-7] + init_expr: Expr [0-0]: + ty: Err + kind: Err + + [Qasm.Lowerer.TypeWidthMustBePositiveIntConstExpr + + x type width must be a positive integer const expression + ,-[test:1:5] + 1 | int[-5] i; + : ^^ + `---- + ]"#]], + ); +} + +#[test] +fn scalar_ty_designator_must_be_castable_to_const_int() { + check( + r#"const angle size = 2.0; int[size] i;"#, + &expect![[r#" + Program: + version: + statements: + Stmt [0-23]: + annotations: + kind: ClassicalDeclarationStmt [0-23]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(2.0000000000000004) + Stmt [24-36]: + annotations: + kind: ClassicalDeclarationStmt [24-36]: + symbol_id: 9 + ty_span: [24-33] + init_expr: Expr [0-0]: + ty: Err + kind: Err + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Angle(None, true) to type UInt(None, true) + ,-[test:1:29] + 1 | const angle size = 2.0; int[size] i; + : ^^^^ + `---- + , Qasm.Lowerer.TypeWidthMustBePositiveIntConstExpr + + x type width must be a positive integer const expression + ,-[test:1:29] + 1 | const angle size = 2.0; int[size] i; + : ^^^^ + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/decls/angle.rs b/compiler/qsc_qasm/src/semantic/tests/decls/angle.rs new file mode 100644 index 0000000000..cf0e7ff96e --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/decls/angle.rs @@ -0,0 +1,477 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decl; + +#[test] +fn implicit_bitness_default() { + check_classical_decl( + "angle x;", + &expect![[r#" + ClassicalDeclarationStmt [0-8]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [0-0]: + ty: Angle(None, true) + kind: Lit: Angle(0) + [8] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn lit() { + check_classical_decl( + "angle x = 42.1;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-14]: + ty: Angle(None, true) + kind: Lit: Angle(4.400888156922484) + [8] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit() { + check_classical_decl( + "const angle x = 42.1;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-20]: + ty: Angle(None, true) + kind: Lit: Angle(4.400888156922484) + [8] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn lit_explicit_width() { + check_classical_decl( + "angle[64] x = 42.1;", + &expect![[r#" + ClassicalDeclarationStmt [0-19]: + symbol_id: 8 + ty_span: [0-9] + init_expr: Expr [14-18]: + ty: Angle(Some(64), true) + kind: Lit: Angle(4.400888156922484) + [8] Symbol [10-11]: + name: x + type: Angle(Some(64), false) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn const_explicit_width_lit() { + check_classical_decl( + "const angle[64] x = 42.1;", + &expect![[r#" + ClassicalDeclarationStmt [0-25]: + symbol_id: 8 + ty_span: [6-15] + init_expr: Expr [20-24]: + ty: Angle(Some(64), true) + kind: Lit: Angle(4.400888156922484) + [8] Symbol [16-17]: + name: x + type: Angle(Some(64), true) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_leading_dot() { + check_classical_decl( + "angle x = .421;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-14]: + ty: Angle(None, true) + kind: Lit: Angle(0.4210000000000001) + [8] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_leading_dot() { + check_classical_decl( + "const angle x = .421;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-20]: + ty: Angle(None, true) + kind: Lit: Angle(0.4210000000000001) + [8] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_leading_dot_scientific() { + check_classical_decl( + "const angle x = .421e2;", + &expect![[r#" + ClassicalDeclarationStmt [0-23]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.400888156922484) + [8] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_trailing_dot() { + check_classical_decl( + "angle x = 421.;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-14]: + ty: Angle(None, true) + kind: Lit: Angle(0.02658441896772248) + [8] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_trailing_dot() { + check_classical_decl( + "const angle x = 421.;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-20]: + ty: Angle(None, true) + kind: Lit: Angle(0.02658441896772248) + [8] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_scientific() { + check_classical_decl( + "angle x = 4.21e1;", + &expect![[r#" + ClassicalDeclarationStmt [0-17]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-16]: + ty: Angle(None, true) + kind: Lit: Angle(4.400888156922484) + [8] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_scientific() { + check_classical_decl( + "const angle x = 4.21e1;", + &expect![[r#" + ClassicalDeclarationStmt [0-23]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.400888156922484) + [8] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_scientific_signed_pos() { + check_classical_decl( + "angle x = 4.21e+1;", + &expect![[r#" + ClassicalDeclarationStmt [0-18]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-17]: + ty: Angle(None, true) + kind: Lit: Angle(4.400888156922484) + [8] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_scientific_signed_pos() { + check_classical_decl( + "const angle x = 4.21e+1;", + &expect![[r#" + ClassicalDeclarationStmt [0-24]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-23]: + ty: Angle(None, true) + kind: Lit: Angle(4.400888156922484) + [8] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_scientific_cap_e() { + check_classical_decl( + "angle x = 4.21E1;", + &expect![[r#" + ClassicalDeclarationStmt [0-17]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-16]: + ty: Angle(None, true) + kind: Lit: Angle(4.400888156922484) + [8] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_scientific_cap_e() { + check_classical_decl( + "const angle x = 4.21E1;", + &expect![[r#" + ClassicalDeclarationStmt [0-23]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.400888156922484) + [8] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_scientific_signed_neg() { + check_classical_decl( + "angle x = 421.0e-1;", + &expect![[r#" + ClassicalDeclarationStmt [0-19]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-18]: + ty: Angle(None, true) + kind: Lit: Angle(4.400888156922484) + [8] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_scientific_signed_neg() { + check_classical_decl( + "const angle x = 421.0e-1;", + &expect![[r#" + ClassicalDeclarationStmt [0-25]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-24]: + ty: Angle(None, true) + kind: Lit: Angle(4.400888156922484) + [8] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_signed_float_lit_cast_neg() { + check_classical_decl( + "const angle x = -7.;", + &expect![[r#" + ClassicalDeclarationStmt [0-20]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [17-19]: + ty: Angle(None, true) + kind: Cast [0-0]: + ty: Angle(None, true) + expr: Expr [17-19]: + ty: Float(None, true) + kind: UnaryOpExpr [17-19]: + op: Neg + expr: Expr [17-19]: + ty: Float(None, true) + kind: Lit: Float(7.0) + [8] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Angle + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_signed_int_lit_cast_neg_fails() { + check_classical_decl( + "const angle x = -7;", + &expect![[r#" + Program: + version: + statements: + Stmt [0-19]: + annotations: + kind: ClassicalDeclarationStmt [0-19]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [17-18]: + ty: Int(None, true) + kind: UnaryOpExpr [17-18]: + op: Neg + expr: Expr [17-18]: + ty: Int(None, true) + kind: Lit: Int(7) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Int(None, true) to type Angle(None, true) + ,-[test:1:18] + 1 | const angle x = -7; + : ^ + `---- + ]"#]], + ); +} + +#[test] +fn explicit_zero_width_fails() { + check_classical_decl( + "angle[0] x = 42.1;", + &expect![[r#" + Program: + version: + statements: + Stmt [0-18]: + annotations: + kind: ClassicalDeclarationStmt [0-18]: + symbol_id: 8 + ty_span: [0-8] + init_expr: Expr [13-17]: + ty: Float(None, true) + kind: Lit: Float(42.1) + + [Qasm.Lowerer.TypeWidthMustBePositiveIntConstExpr + + x type width must be a positive integer const expression + ,-[test:1:7] + 1 | angle[0] x = 42.1; + : ^ + `---- + , Qasm.Lowerer.CannotCastLiteral + + x cannot cast literal expression of type Float(None, true) to type Err + ,-[test:1:1] + 1 | angle[0] x = 42.1; + : ^^^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn explicit_width_over_64_fails() { + check_classical_decl( + "const angle[65] x = 42.1;", + &expect![[r#" + Program: + version: + statements: + Stmt [0-25]: + annotations: + kind: ClassicalDeclarationStmt [0-25]: + symbol_id: 8 + ty_span: [6-15] + init_expr: Expr [20-24]: + ty: Float(None, true) + kind: Lit: Float(42.1) + + [Qasm.Lowerer.TypeMaxWidthExceeded + + x angle max width is 64 but 65 was provided + ,-[test:1:7] + 1 | const angle[65] x = 42.1; + : ^^^^^^^^^ + `---- + , Qasm.Lowerer.CannotCastLiteral + + x cannot cast literal expression of type Float(None, true) to type Err + ,-[test:1:1] + 1 | const angle[65] x = 42.1; + : ^^^^^^^^^^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/decls/bit.rs b/compiler/qsc_qasm/src/semantic/tests/decls/bit.rs new file mode 100644 index 0000000000..6a12f8b39e --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/decls/bit.rs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decl; + +#[test] +fn with_no_init_expr_has_generated_lit_expr() { + check_classical_decl( + "bit a;", + &expect![[r#" + ClassicalDeclarationStmt [0-6]: + symbol_id: 8 + ty_span: [0-3] + init_expr: Expr [0-0]: + ty: Bit(true) + kind: Lit: Bit(0) + [8] Symbol [4-5]: + name: a + type: Bit(false) + qsharp_type: Result + io_kind: Default"#]], + ); +} + +#[test] +fn array_with_no_init_expr_has_generated_lit_expr() { + check_classical_decl( + "bit[4] a;", + &expect![[r#" + ClassicalDeclarationStmt [0-9]: + symbol_id: 8 + ty_span: [0-6] + init_expr: Expr [0-0]: + ty: BitArray(One(4), true) + kind: Lit: Bitstring("0000") + [8] Symbol [7-8]: + name: a + type: BitArray(One(4), false) + qsharp_type: Result[] + io_kind: Default"#]], + ); +} + +#[test] +fn decl_with_lit_0_init_expr() { + check_classical_decl( + "bit a = 0;", + &expect![[r#" + ClassicalDeclarationStmt [0-10]: + symbol_id: 8 + ty_span: [0-3] + init_expr: Expr [8-9]: + ty: Bit(true) + kind: Lit: Bit(0) + [8] Symbol [4-5]: + name: a + type: Bit(false) + qsharp_type: Result + io_kind: Default"#]], + ); +} + +#[test] +fn decl_with_lit_1_init_expr() { + check_classical_decl( + "bit a = 1;", + &expect![[r#" + ClassicalDeclarationStmt [0-10]: + symbol_id: 8 + ty_span: [0-3] + init_expr: Expr [8-9]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [4-5]: + name: a + type: Bit(false) + qsharp_type: Result + io_kind: Default"#]], + ); +} + +#[test] +fn const_decl_with_lit_0_init_expr() { + check_classical_decl( + "const bit a = 0;", + &expect![[r#" + ClassicalDeclarationStmt [0-16]: + symbol_id: 8 + ty_span: [6-9] + init_expr: Expr [14-15]: + ty: Bit(true) + kind: Lit: Bit(0) + [8] Symbol [10-11]: + name: a + type: Bit(true) + qsharp_type: Result + io_kind: Default"#]], + ); +} + +#[test] +fn const_decl_with_lit_1_init_expr() { + check_classical_decl( + "const bit a = 1;", + &expect![[r#" + ClassicalDeclarationStmt [0-16]: + symbol_id: 8 + ty_span: [6-9] + init_expr: Expr [14-15]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [10-11]: + name: a + type: Bit(true) + qsharp_type: Result + io_kind: Default"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/decls/bool.rs b/compiler/qsc_qasm/src/semantic/tests/decls/bool.rs new file mode 100644 index 0000000000..d29a252984 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/decls/bool.rs @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decl; + +#[test] +fn with_no_init_expr_has_generated_lit_expr() { + check_classical_decl( + "bool a;", + &expect![[r#" + ClassicalDeclarationStmt [0-7]: + symbol_id: 8 + ty_span: [0-4] + init_expr: Expr [0-0]: + ty: Bool(true) + kind: Lit: Bool(false) + [8] Symbol [5-6]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default"#]], + ); +} + +#[test] +#[ignore = "Unimplemented"] +fn array_with_no_init_expr_has_generated_lit_expr() { + check_classical_decl( + "array[bool, 4] a;", + &expect![[r#" + Program: + version: + statements: + Stmt [0-17]: + annotations: + kind: ClassicalDeclarationStmt [0-17]: + symbol_id: 8 + ty_span: [0-14] + init_expr: Expr [0-0]: + ty: Err + kind: Err + + [Qasm.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: semantic type + | from array type + ,-[test:1:1] + 1 | array[bool, 4] a; + : ^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn decl_with_lit_false_init_expr() { + check_classical_decl( + "bool a = false;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 8 + ty_span: [0-4] + init_expr: Expr [9-14]: + ty: Bool(false) + kind: Lit: Bool(false) + [8] Symbol [5-6]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default"#]], + ); +} + +#[test] +fn decl_with_lit_true_init_expr() { + check_classical_decl( + "bool a = true;", + &expect![[r#" + ClassicalDeclarationStmt [0-14]: + symbol_id: 8 + ty_span: [0-4] + init_expr: Expr [9-13]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [5-6]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default"#]], + ); +} + +#[test] +fn const_decl_with_lit_false_init_expr() { + check_classical_decl( + "const bool a = false;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 8 + ty_span: [6-10] + init_expr: Expr [15-20]: + ty: Bool(true) + kind: Lit: Bool(false) + [8] Symbol [11-12]: + name: a + type: Bool(true) + qsharp_type: bool + io_kind: Default"#]], + ); +} + +#[test] +fn const_decl_with_lit_true_init_expr() { + check_classical_decl( + "const bool a = true;", + &expect![[r#" + ClassicalDeclarationStmt [0-20]: + symbol_id: 8 + ty_span: [6-10] + init_expr: Expr [15-19]: + ty: Bool(true) + kind: Lit: Bool(true) + [8] Symbol [11-12]: + name: a + type: Bool(true) + qsharp_type: bool + io_kind: Default"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/decls/complex.rs b/compiler/qsc_qasm/src/semantic/tests/decls/complex.rs new file mode 100644 index 0000000000..6cf6676fdb --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/decls/complex.rs @@ -0,0 +1,380 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decl; + +#[test] +fn implicit_bitness_default() { + check_classical_decl( + "complex[float] x;", + &expect![[r#" + ClassicalDeclarationStmt [0-17]: + symbol_id: 8 + ty_span: [0-14] + init_expr: Expr [0-0]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 0.0) + [8] Symbol [15-16]: + name: x + type: Complex(None, false) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn explicit_bitness_default() { + check_classical_decl( + "complex[float[42]] x;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 8 + ty_span: [0-18] + init_expr: Expr [0-0]: + ty: Complex(Some(42), true) + kind: Lit: Complex(0.0, 0.0) + [8] Symbol [19-20]: + name: x + type: Complex(Some(42), false) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_double_img_only() { + check_classical_decl( + "const complex[float] x = 1.01im;", + &expect![[r#" + ClassicalDeclarationStmt [0-32]: + symbol_id: 8 + ty_span: [6-20] + init_expr: Expr [25-31]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 1.01) + [8] Symbol [21-22]: + name: x + type: Complex(None, true) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_img_only() { + check_classical_decl( + "const complex[float] x = 1im;", + &expect![[r#" + ClassicalDeclarationStmt [0-29]: + symbol_id: 8 + ty_span: [6-20] + init_expr: Expr [25-28]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 1.0) + [8] Symbol [21-22]: + name: x + type: Complex(None, true) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn const_explicit_bitness_double_img_only() { + check_classical_decl( + "const complex[float[42]] x = 1.01im;", + &expect![[r#" + ClassicalDeclarationStmt [0-36]: + symbol_id: 8 + ty_span: [6-24] + init_expr: Expr [29-35]: + ty: Complex(Some(42), true) + kind: Lit: Complex(0.0, 1.01) + [8] Symbol [25-26]: + name: x + type: Complex(Some(42), true) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn const_explicit_bitness_int_img_only() { + check_classical_decl( + "const complex[float[42]] x = 1im;", + &expect![[r#" + ClassicalDeclarationStmt [0-33]: + symbol_id: 8 + ty_span: [6-24] + init_expr: Expr [29-32]: + ty: Complex(Some(42), true) + kind: Lit: Complex(0.0, 1.0) + [8] Symbol [25-26]: + name: x + type: Complex(Some(42), true) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_double_img_only() { + check_classical_decl( + "complex[float] x = 1.01im;", + &expect![[r#" + ClassicalDeclarationStmt [0-26]: + symbol_id: 8 + ty_span: [0-14] + init_expr: Expr [19-25]: + ty: Complex(None, false) + kind: Lit: Complex(0.0, 1.01) + [8] Symbol [15-16]: + name: x + type: Complex(None, false) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_img_only() { + check_classical_decl( + "complex[float] x = 1im;", + &expect![[r#" + ClassicalDeclarationStmt [0-23]: + symbol_id: 8 + ty_span: [0-14] + init_expr: Expr [19-22]: + ty: Complex(None, false) + kind: Lit: Complex(0.0, 1.0) + [8] Symbol [15-16]: + name: x + type: Complex(None, false) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_double_real_only() { + check_classical_decl( + "const complex[float] x = 1.01;", + &expect![[r#" + ClassicalDeclarationStmt [0-30]: + symbol_id: 8 + ty_span: [6-20] + init_expr: Expr [25-29]: + ty: Complex(None, true) + kind: Lit: Complex(1.01, 0.0) + [8] Symbol [21-22]: + name: x + type: Complex(None, true) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_real_only() { + check_classical_decl( + "const complex[float] x = 1;", + &expect![[r#" + ClassicalDeclarationStmt [0-27]: + symbol_id: 8 + ty_span: [6-20] + init_expr: Expr [25-26]: + ty: Complex(None, true) + kind: Lit: Complex(1.0, 0.0) + [8] Symbol [21-22]: + name: x + type: Complex(None, true) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_double_real_only() { + check_classical_decl( + "complex[float] x = 1.01;", + &expect![[r#" + ClassicalDeclarationStmt [0-24]: + symbol_id: 8 + ty_span: [0-14] + init_expr: Expr [19-23]: + ty: Complex(None, true) + kind: Lit: Complex(1.01, 0.0) + [8] Symbol [15-16]: + name: x + type: Complex(None, false) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_real_only() { + check_classical_decl( + "complex[float] x = 1;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 8 + ty_span: [0-14] + init_expr: Expr [19-20]: + ty: Complex(None, true) + kind: Lit: Complex(1.0, 0.0) + [8] Symbol [15-16]: + name: x + type: Complex(None, false) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_simple_double_pos_im() { + check_classical_decl( + "complex[float] x = 1.1 + 2.2im;", + &expect![[r#" + ClassicalDeclarationStmt [0-31]: + symbol_id: 8 + ty_span: [0-14] + init_expr: Expr [19-30]: + ty: Complex(None, false) + kind: BinaryOpExpr: + op: Add + lhs: Expr [19-22]: + ty: Complex(None, true) + kind: Lit: Complex(1.1, 0.0) + rhs: Expr [25-30]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 2.2) + [8] Symbol [15-16]: + name: x + type: Complex(None, false) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_simple_double_neg_im() { + check_classical_decl( + "complex[float] x = 1.1 - 2.2im;", + &expect![[r#" + ClassicalDeclarationStmt [0-31]: + symbol_id: 8 + ty_span: [0-14] + init_expr: Expr [19-30]: + ty: Complex(None, false) + kind: BinaryOpExpr: + op: Sub + lhs: Expr [19-22]: + ty: Complex(None, true) + kind: Lit: Complex(1.1, 0.0) + rhs: Expr [25-30]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 2.2) + [8] Symbol [15-16]: + name: x + type: Complex(None, false) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_simple_double_neg_im() { + check_classical_decl( + "const complex[float] x = 1.1 - 2.2im;", + &expect![[r#" + ClassicalDeclarationStmt [0-37]: + symbol_id: 8 + ty_span: [6-20] + init_expr: Expr [25-36]: + ty: Complex(None, true) + kind: BinaryOpExpr: + op: Sub + lhs: Expr [25-28]: + ty: Complex(None, true) + kind: Lit: Complex(1.1, 0.0) + rhs: Expr [31-36]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 2.2) + [8] Symbol [21-22]: + name: x + type: Complex(None, true) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_simple_double_neg_real() { + check_classical_decl( + "complex[float] x = -1.1 + 2.2im;", + &expect![[r#" + ClassicalDeclarationStmt [0-32]: + symbol_id: 8 + ty_span: [0-14] + init_expr: Expr [19-31]: + ty: Complex(None, false) + kind: BinaryOpExpr: + op: Add + lhs: Expr [20-23]: + ty: Complex(None, true) + kind: Cast [0-0]: + ty: Complex(None, true) + expr: Expr [20-23]: + ty: Float(None, true) + kind: UnaryOpExpr [20-23]: + op: Neg + expr: Expr [20-23]: + ty: Float(None, true) + kind: Lit: Float(1.1) + rhs: Expr [26-31]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 2.2) + [8] Symbol [15-16]: + name: x + type: Complex(None, false) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_simple_double_neg_real() { + check_classical_decl( + "const complex[float] x = -1.1 + 2.2im;", + &expect![[r#" + ClassicalDeclarationStmt [0-38]: + symbol_id: 8 + ty_span: [6-20] + init_expr: Expr [25-37]: + ty: Complex(None, true) + kind: BinaryOpExpr: + op: Add + lhs: Expr [26-29]: + ty: Complex(None, true) + kind: Cast [0-0]: + ty: Complex(None, true) + expr: Expr [26-29]: + ty: Float(None, true) + kind: UnaryOpExpr [26-29]: + op: Neg + expr: Expr [26-29]: + ty: Float(None, true) + kind: Lit: Float(1.1) + rhs: Expr [32-37]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 2.2) + [8] Symbol [21-22]: + name: x + type: Complex(None, true) + qsharp_type: Complex + io_kind: Default"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/decls/creg.rs b/compiler/qsc_qasm/src/semantic/tests/decls/creg.rs new file mode 100644 index 0000000000..7ac120ca04 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/decls/creg.rs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decl; + +#[test] +fn with_no_init_expr_has_generated_lit_expr() { + check_classical_decl( + "creg a;", + &expect![[r#" + ClassicalDeclarationStmt [0-7]: + symbol_id: 8 + ty_span: [0-7] + init_expr: Expr [0-0]: + ty: Bit(true) + kind: Lit: Bit(0) + [8] Symbol [5-6]: + name: a + type: Bit(false) + qsharp_type: Result + io_kind: Default"#]], + ); +} + +#[test] +fn array_with_no_init_expr_has_generated_lit_expr() { + check_classical_decl( + "creg a[4];", + &expect![[r#" + ClassicalDeclarationStmt [0-10]: + symbol_id: 8 + ty_span: [0-10] + init_expr: Expr [0-0]: + ty: BitArray(One(4), true) + kind: Lit: Bitstring("0000") + [8] Symbol [5-6]: + name: a + type: BitArray(One(4), false) + qsharp_type: Result[] + io_kind: Default"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/decls/duration.rs b/compiler/qsc_qasm/src/semantic/tests/decls/duration.rs new file mode 100644 index 0000000000..5d1c745f78 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/decls/duration.rs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decl; + +#[test] +fn with_no_init_expr_has_generated_lit_expr() { + check_classical_decl( + "duration a;", + &expect![[r#" + Program: + version: + statements: + Stmt [0-11]: + annotations: + kind: ClassicalDeclarationStmt [0-11]: + symbol_id: 8 + ty_span: [0-8] + init_expr: Expr [0-0]: + ty: Duration(true) + kind: Lit: Duration(0.0, Ns) + + [Qasm.Lowerer.NotSupported + + x duration type values are not supported + ,-[test:1:1] + 1 | duration a; + : ^^^^^^^^ + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/decls/extern_decl.rs b/compiler/qsc_qasm/src/semantic/tests/decls/extern_decl.rs new file mode 100644 index 0000000000..946ab03779 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/decls/extern_decl.rs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::semantic::tests::check_stmt_kind; +use expect_test::expect; + +#[test] +fn void_no_args() { + check_stmt_kind( + "extern f();", + &expect![[r#" + ExternDecl [0-11]: + symbol_id: 8 + parameters: + return_type: ()"#]], + ); +} + +#[test] +fn void_one_arg() { + check_stmt_kind( + "extern f(int);", + &expect![[r#" + ExternDecl [0-14]: + symbol_id: 8 + parameters: + Int + return_type: ()"#]], + ); +} + +#[test] +fn void_multiple_args() { + check_stmt_kind( + "extern f(uint, int, float, bit, bool);", + &expect![[r#" + ExternDecl [0-38]: + symbol_id: 8 + parameters: + Int + Int + Double + Result + bool + return_type: ()"#]], + ); +} + +#[test] +fn return_type() { + check_stmt_kind( + "extern f() -> int;", + &expect![[r#" + ExternDecl [0-18]: + symbol_id: 8 + parameters: + return_type: Int"#]], + ); +} + +#[test] +fn no_allowed_in_non_global_scope() { + check_stmt_kind( + "{ extern f(); }", + &expect![[r#" + Program: + version: + statements: + Stmt [0-15]: + annotations: + kind: Block [0-15]: + Stmt [2-13]: + annotations: + kind: ExternDecl [2-13]: + symbol_id: 8 + parameters: + return_type: () + + [Qasm.Lowerer.DefDeclarationInNonGlobalScope + + x extern declarations must be done in global scope + ,-[test:1:3] + 1 | { extern f(); } + : ^^^^^^^^^^^ + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/decls/float.rs b/compiler/qsc_qasm/src/semantic/tests/decls/float.rs new file mode 100644 index 0000000000..104e53ea9d --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/decls/float.rs @@ -0,0 +1,485 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decl; + +#[test] +fn implicit_bitness_default() { + check_classical_decl( + "float x;", + &expect![[r#" + ClassicalDeclarationStmt [0-8]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [0-0]: + ty: Float(None, true) + kind: Lit: Float(0.0) + [8] Symbol [6-7]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn lit() { + check_classical_decl( + "float x = 42.1;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-14]: + ty: Float(None, false) + kind: Lit: Float(42.1) + [8] Symbol [6-7]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit() { + check_classical_decl( + "const float x = 42.1;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-20]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [8] Symbol [12-13]: + name: x + type: Float(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn lit_explicit_width() { + check_classical_decl( + "float[64] x = 42.1;", + &expect![[r#" + ClassicalDeclarationStmt [0-19]: + symbol_id: 8 + ty_span: [0-9] + init_expr: Expr [14-18]: + ty: Float(Some(64), true) + kind: Lit: Float(42.1) + [8] Symbol [10-11]: + name: x + type: Float(Some(64), false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_explicit_width_lit() { + check_classical_decl( + "const float[64] x = 42.1;", + &expect![[r#" + ClassicalDeclarationStmt [0-25]: + symbol_id: 8 + ty_span: [6-15] + init_expr: Expr [20-24]: + ty: Float(Some(64), true) + kind: Lit: Float(42.1) + [8] Symbol [16-17]: + name: x + type: Float(Some(64), true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_leading_dot() { + check_classical_decl( + "float x = .421;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-14]: + ty: Float(None, false) + kind: Lit: Float(0.421) + [8] Symbol [6-7]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_leading_dot() { + check_classical_decl( + "const float x = .421;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-20]: + ty: Float(None, true) + kind: Lit: Float(0.421) + [8] Symbol [12-13]: + name: x + type: Float(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_leading_dot_scientific() { + check_classical_decl( + "const float x = .421e2;", + &expect![[r#" + ClassicalDeclarationStmt [0-23]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-22]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [8] Symbol [12-13]: + name: x + type: Float(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_trailing_dot() { + check_classical_decl( + "float x = 421.;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-14]: + ty: Float(None, false) + kind: Lit: Float(421.0) + [8] Symbol [6-7]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_trailing_dot() { + check_classical_decl( + "const float x = 421.;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-20]: + ty: Float(None, true) + kind: Lit: Float(421.0) + [8] Symbol [12-13]: + name: x + type: Float(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_scientific() { + check_classical_decl( + "float x = 4.21e1;", + &expect![[r#" + ClassicalDeclarationStmt [0-17]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-16]: + ty: Float(None, false) + kind: Lit: Float(42.1) + [8] Symbol [6-7]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_scientific() { + check_classical_decl( + "const float x = 4.21e1;", + &expect![[r#" + ClassicalDeclarationStmt [0-23]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-22]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [8] Symbol [12-13]: + name: x + type: Float(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_scientific_signed_pos() { + check_classical_decl( + "float x = 4.21e+1;", + &expect![[r#" + ClassicalDeclarationStmt [0-18]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-17]: + ty: Float(None, false) + kind: Lit: Float(42.1) + [8] Symbol [6-7]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_scientific_signed_pos() { + check_classical_decl( + "const float x = 4.21e+1;", + &expect![[r#" + ClassicalDeclarationStmt [0-24]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-23]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [8] Symbol [12-13]: + name: x + type: Float(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_scientific_cap_e() { + check_classical_decl( + "float x = 4.21E1;", + &expect![[r#" + ClassicalDeclarationStmt [0-17]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-16]: + ty: Float(None, false) + kind: Lit: Float(42.1) + [8] Symbol [6-7]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_scientific_cap_e() { + check_classical_decl( + "const float x = 4.21E1;", + &expect![[r#" + ClassicalDeclarationStmt [0-23]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-22]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [8] Symbol [12-13]: + name: x + type: Float(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_scientific_signed_neg() { + check_classical_decl( + "float x = 421.0e-1;", + &expect![[r#" + ClassicalDeclarationStmt [0-19]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-18]: + ty: Float(None, false) + kind: Lit: Float(42.1) + [8] Symbol [6-7]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_scientific_signed_neg() { + check_classical_decl( + "const float x = 421.0e-1;", + &expect![[r#" + ClassicalDeclarationStmt [0-25]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [16-24]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [8] Symbol [12-13]: + name: x + type: Float(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_signed_float_lit_cast_neg() { + check_classical_decl( + "const float x = -7.;", + &expect![[r#" + ClassicalDeclarationStmt [0-20]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [17-19]: + ty: Float(None, true) + kind: UnaryOpExpr [17-19]: + op: Neg + expr: Expr [17-19]: + ty: Float(None, true) + kind: Lit: Float(7.0) + [8] Symbol [12-13]: + name: x + type: Float(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_signed_int_lit_cast_neg() { + check_classical_decl( + "const float x = -7;", + &expect![[r#" + ClassicalDeclarationStmt [0-19]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [17-18]: + ty: Float(None, true) + kind: Cast [0-0]: + ty: Float(None, true) + expr: Expr [17-18]: + ty: Int(None, true) + kind: UnaryOpExpr [17-18]: + op: Neg + expr: Expr [17-18]: + ty: Int(None, true) + kind: Lit: Int(7) + [8] Symbol [12-13]: + name: x + type: Float(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn init_float_with_int_value_equal_max_safely_representable_values() { + let max_exact_int = 2i64.pow(f64::MANTISSA_DIGITS); + check_classical_decl( + &format!("float a = {max_exact_int};"), + &expect![[r#" + ClassicalDeclarationStmt [0-27]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-26]: + ty: Float(None, true) + kind: Lit: Float(9007199254740992.0) + [8] Symbol [6-7]: + name: a + type: Float(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn init_float_with_int_value_greater_than_safely_representable_values() { + let max_exact_int = 2i64.pow(f64::MANTISSA_DIGITS); + let next = max_exact_int + 1; + check_classical_decl( + &format!("float a = {next};"), + &expect![[r#" + Program: + version: + statements: + Stmt [0-27]: + annotations: + kind: ClassicalDeclarationStmt [0-27]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [10-26]: + ty: Int(None, true) + kind: Lit: Int(9007199254740993) + + [Qasm.Lowerer.InvalidCastValueRange + + x assigning Int(None, true) values to Float(None, false) must be in a range + | that be converted to Float(None, false) + ,-[test:1:11] + 1 | float a = 9007199254740993; + : ^^^^^^^^^^^^^^^^ + `---- + , Qasm.Lowerer.CannotCastLiteral + + x cannot cast literal expression of type Int(None, true) to type Float(None, + | false) + ,-[test:1:11] + 1 | float a = 9007199254740993; + : ^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn init_float_with_int_value_equal_min_safely_representable_values() { + let min_exact_int = -(2i64.pow(f64::MANTISSA_DIGITS)); + check_classical_decl( + &format!("float a = {min_exact_int};"), + &expect![[r#" + ClassicalDeclarationStmt [0-28]: + symbol_id: 8 + ty_span: [0-5] + init_expr: Expr [11-27]: + ty: Float(None, false) + kind: Cast [0-0]: + ty: Float(None, false) + expr: Expr [11-27]: + ty: Int(None, true) + kind: UnaryOpExpr [11-27]: + op: Neg + expr: Expr [11-27]: + ty: Int(None, true) + kind: Lit: Int(9007199254740992) + [8] Symbol [6-7]: + name: a + type: Float(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/decls/int.rs b/compiler/qsc_qasm/src/semantic/tests/decls/int.rs new file mode 100644 index 0000000000..6beda22ff3 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/decls/int.rs @@ -0,0 +1,383 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decl; + +#[test] +fn implicit_bitness_int_negative() { + check_classical_decl( + "int x = -42;", + &expect![[r#" + ClassicalDeclarationStmt [0-12]: + symbol_id: 8 + ty_span: [0-3] + init_expr: Expr [9-11]: + ty: Int(None, false) + kind: UnaryOpExpr [9-11]: + op: Neg + expr: Expr [9-11]: + ty: Int(None, true) + kind: Lit: Int(42) + [8] Symbol [4-5]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_const_negative() { + check_classical_decl( + "const int x = -42;", + &expect![[r#" + ClassicalDeclarationStmt [0-18]: + symbol_id: 8 + ty_span: [6-9] + init_expr: Expr [15-17]: + ty: Int(None, true) + kind: UnaryOpExpr [15-17]: + op: Neg + expr: Expr [15-17]: + ty: Int(None, true) + kind: Lit: Int(42) + [8] Symbol [10-11]: + name: x + type: Int(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_default() { + check_classical_decl( + "int x;", + &expect![[r#" + ClassicalDeclarationStmt [0-6]: + symbol_id: 8 + ty_span: [0-3] + init_expr: Expr [0-0]: + ty: Int(None, true) + kind: Lit: Int(0) + [8] Symbol [4-5]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_lit() { + check_classical_decl( + "const int x = 42;", + &expect![[r#" + ClassicalDeclarationStmt [0-17]: + symbol_id: 8 + ty_span: [6-9] + init_expr: Expr [14-16]: + ty: Int(None, true) + kind: Lit: Int(42) + [8] Symbol [10-11]: + name: x + type: Int(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_hex_cap() { + check_classical_decl( + "int x = 0XFa_1F;", + &expect![[r#" + ClassicalDeclarationStmt [0-16]: + symbol_id: 8 + ty_span: [0-3] + init_expr: Expr [8-15]: + ty: Int(None, false) + kind: Lit: Int(64031) + [8] Symbol [4-5]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_hex_cap() { + check_classical_decl( + "const int y = 0XFa_1F;", + &expect![[r#" + ClassicalDeclarationStmt [0-22]: + symbol_id: 8 + ty_span: [6-9] + init_expr: Expr [14-21]: + ty: Int(None, true) + kind: Lit: Int(64031) + [8] Symbol [10-11]: + name: y + type: Int(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_octal() { + check_classical_decl( + "int x = 0o42;", + &expect![[r#" + ClassicalDeclarationStmt [0-13]: + symbol_id: 8 + ty_span: [0-3] + init_expr: Expr [8-12]: + ty: Int(None, false) + kind: Lit: Int(34) + [8] Symbol [4-5]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_octal() { + check_classical_decl( + "const int x = 0o42;", + &expect![[r#" + ClassicalDeclarationStmt [0-19]: + symbol_id: 8 + ty_span: [6-9] + init_expr: Expr [14-18]: + ty: Int(None, true) + kind: Lit: Int(34) + [8] Symbol [10-11]: + name: x + type: Int(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_octal_cap() { + check_classical_decl( + "const int x = 0O42;", + &expect![[r#" + ClassicalDeclarationStmt [0-19]: + symbol_id: 8 + ty_span: [6-9] + init_expr: Expr [14-18]: + ty: Int(None, true) + kind: Lit: Int(34) + [8] Symbol [10-11]: + name: x + type: Int(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_binary_low() { + check_classical_decl( + "int x = 0b1001_1001;", + &expect![[r#" + ClassicalDeclarationStmt [0-20]: + symbol_id: 8 + ty_span: [0-3] + init_expr: Expr [8-19]: + ty: Int(None, false) + kind: Lit: Int(153) + [8] Symbol [4-5]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_binary_cap() { + check_classical_decl( + "int x = 0B1010;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 8 + ty_span: [0-3] + init_expr: Expr [8-14]: + ty: Int(None, false) + kind: Lit: Int(10) + [8] Symbol [4-5]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_binary_low() { + check_classical_decl( + "const int x = 0b1001_1001;", + &expect![[r#" + ClassicalDeclarationStmt [0-26]: + symbol_id: 8 + ty_span: [6-9] + init_expr: Expr [14-25]: + ty: Int(None, true) + kind: Lit: Int(153) + [8] Symbol [10-11]: + name: x + type: Int(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_binary_cap() { + check_classical_decl( + "const int x = 0B1010;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 8 + ty_span: [6-9] + init_expr: Expr [14-20]: + ty: Int(None, true) + kind: Lit: Int(10) + [8] Symbol [10-11]: + name: x + type: Int(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_formatted() { + check_classical_decl( + "int x = 2_0_00;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 8 + ty_span: [0-3] + init_expr: Expr [8-14]: + ty: Int(None, false) + kind: Lit: Int(2000) + [8] Symbol [4-5]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_formatted() { + check_classical_decl( + "const int x = 2_0_00;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 8 + ty_span: [6-9] + init_expr: Expr [14-20]: + ty: Int(None, true) + kind: Lit: Int(2000) + [8] Symbol [10-11]: + name: x + type: Int(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn explicit_bitness_int_default() { + check_classical_decl( + "int[10] x;", + &expect![[r#" + ClassicalDeclarationStmt [0-10]: + symbol_id: 8 + ty_span: [0-7] + init_expr: Expr [0-0]: + ty: Int(Some(10), true) + kind: Lit: Int(0) + [8] Symbol [8-9]: + name: x + type: Int(Some(10), false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn explicit_bitness_int() { + check_classical_decl( + "int[10] x = 42;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 8 + ty_span: [0-7] + init_expr: Expr [12-14]: + ty: Int(Some(10), true) + kind: Lit: Int(42) + [8] Symbol [8-9]: + name: x + type: Int(Some(10), false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_explicit_bitness_int() { + check_classical_decl( + "const int[10] x = 42;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 8 + ty_span: [6-13] + init_expr: Expr [18-20]: + ty: Int(Some(10), true) + kind: Lit: Int(42) + [8] Symbol [14-15]: + name: x + type: Int(Some(10), true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_negative_float_decl_is_runtime_conversion() { + check_classical_decl( + "int x = -42.;", + &expect![[r#" + ClassicalDeclarationStmt [0-13]: + symbol_id: 8 + ty_span: [0-3] + init_expr: Expr [9-12]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [9-12]: + ty: Float(None, true) + kind: UnaryOpExpr [9-12]: + op: Neg + expr: Expr [9-12]: + ty: Float(None, true) + kind: Lit: Float(42.0) + [8] Symbol [4-5]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/decls/qreg.rs b/compiler/qsc_qasm/src/semantic/tests/decls/qreg.rs new file mode 100644 index 0000000000..349b3bc768 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/decls/qreg.rs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_stmt_kind; + +#[test] +fn with_no_init_expr() { + check_stmt_kind( + "qreg a;", + &expect![[r#" + QubitDeclaration [0-7]: + symbol_id: 8"#]], + ); +} + +#[test] +fn array_with_no_init_expr() { + check_stmt_kind( + "qreg a[3];", + &expect![[r#" + QubitArrayDeclaration [0-10]: + symbol_id: 8 + size: 3 + size_span: [7-8]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/decls/stretch.rs b/compiler/qsc_qasm/src/semantic/tests/decls/stretch.rs new file mode 100644 index 0000000000..fa1474ffc7 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/decls/stretch.rs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decl; + +#[test] +fn with_no_init_expr_has_generated_lit_expr() { + check_classical_decl( + "stretch a;", + &expect![[r#" + Program: + version: + statements: + Stmt [0-10]: + annotations: + kind: ClassicalDeclarationStmt [0-10]: + symbol_id: 8 + ty_span: [0-7] + init_expr: Expr [0-0]: + ty: Stretch(true) + kind: Err + + [Qasm.Lowerer.NotSupported + + x stretch type values are not supported + ,-[test:1:1] + 1 | stretch a; + : ^^^^^^^ + `---- + , Qasm.Lowerer.NotSupported + + x stretch default values are not supported + ,-[test:1:1] + 1 | stretch a; + : ^^^^^^^^^^ + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/decls/uint.rs b/compiler/qsc_qasm/src/semantic/tests/decls/uint.rs new file mode 100644 index 0000000000..29eaf3c1ae --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/decls/uint.rs @@ -0,0 +1,391 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decl; + +#[test] +fn implicit_bitness_int_default() { + check_classical_decl( + "uint x;", + &expect![[r#" + ClassicalDeclarationStmt [0-7]: + symbol_id: 8 + ty_span: [0-4] + init_expr: Expr [0-0]: + ty: UInt(None, true) + kind: Lit: Int(0) + [8] Symbol [5-6]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_lit() { + check_classical_decl( + "const uint x = 42;", + &expect![[r#" + ClassicalDeclarationStmt [0-18]: + symbol_id: 8 + ty_span: [6-10] + init_expr: Expr [15-17]: + ty: UInt(None, true) + kind: Lit: Int(42) + [8] Symbol [11-12]: + name: x + type: UInt(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_hex_cap() { + check_classical_decl( + "uint x = 0XFa_1F;", + &expect![[r#" + ClassicalDeclarationStmt [0-17]: + symbol_id: 8 + ty_span: [0-4] + init_expr: Expr [9-16]: + ty: UInt(None, true) + kind: Lit: Int(64031) + [8] Symbol [5-6]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_hex_low() { + check_classical_decl( + "const uint x = 0xFa_1F;", + &expect![[r#" + ClassicalDeclarationStmt [0-23]: + symbol_id: 8 + ty_span: [6-10] + init_expr: Expr [15-22]: + ty: UInt(None, true) + kind: Lit: Int(64031) + [8] Symbol [11-12]: + name: x + type: UInt(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_hex_cap() { + check_classical_decl( + "const uint y = 0XFa_1F;", + &expect![[r#" + ClassicalDeclarationStmt [0-23]: + symbol_id: 8 + ty_span: [6-10] + init_expr: Expr [15-22]: + ty: UInt(None, true) + kind: Lit: Int(64031) + [8] Symbol [11-12]: + name: y + type: UInt(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_octal_low() { + check_classical_decl( + "uint x = 0o42;", + &expect![[r#" + ClassicalDeclarationStmt [0-14]: + symbol_id: 8 + ty_span: [0-4] + init_expr: Expr [9-13]: + ty: UInt(None, true) + kind: Lit: Int(34) + [8] Symbol [5-6]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_octal_cap() { + check_classical_decl( + "uint x = 0O42;", + &expect![[r#" + ClassicalDeclarationStmt [0-14]: + symbol_id: 8 + ty_span: [0-4] + init_expr: Expr [9-13]: + ty: UInt(None, true) + kind: Lit: Int(34) + [8] Symbol [5-6]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_octal_low() { + check_classical_decl( + "const uint x = 0o42;", + &expect![[r#" + ClassicalDeclarationStmt [0-20]: + symbol_id: 8 + ty_span: [6-10] + init_expr: Expr [15-19]: + ty: UInt(None, true) + kind: Lit: Int(34) + [8] Symbol [11-12]: + name: x + type: UInt(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_octal_cap() { + check_classical_decl( + "const uint x = 0O42;", + &expect![[r#" + ClassicalDeclarationStmt [0-20]: + symbol_id: 8 + ty_span: [6-10] + init_expr: Expr [15-19]: + ty: UInt(None, true) + kind: Lit: Int(34) + [8] Symbol [11-12]: + name: x + type: UInt(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_binary_low() { + check_classical_decl( + "uint x = 0b1001_1001;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 8 + ty_span: [0-4] + init_expr: Expr [9-20]: + ty: UInt(None, true) + kind: Lit: Int(153) + [8] Symbol [5-6]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_binary_cap() { + check_classical_decl( + "uint x = 0B1010;", + &expect![[r#" + ClassicalDeclarationStmt [0-16]: + symbol_id: 8 + ty_span: [0-4] + init_expr: Expr [9-15]: + ty: UInt(None, true) + kind: Lit: Int(10) + [8] Symbol [5-6]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_binary_low() { + check_classical_decl( + "const uint x = 0b1001_1001;", + &expect![[r#" + ClassicalDeclarationStmt [0-27]: + symbol_id: 8 + ty_span: [6-10] + init_expr: Expr [15-26]: + ty: UInt(None, true) + kind: Lit: Int(153) + [8] Symbol [11-12]: + name: x + type: UInt(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_binary_cap() { + check_classical_decl( + "const uint x = 0B1010;", + &expect![[r#" + ClassicalDeclarationStmt [0-22]: + symbol_id: 8 + ty_span: [6-10] + init_expr: Expr [15-21]: + ty: UInt(None, true) + kind: Lit: Int(10) + [8] Symbol [11-12]: + name: x + type: UInt(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_formatted() { + check_classical_decl( + "uint x = 2_0_00;", + &expect![[r#" + ClassicalDeclarationStmt [0-16]: + symbol_id: 8 + ty_span: [0-4] + init_expr: Expr [9-15]: + ty: UInt(None, true) + kind: Lit: Int(2000) + [8] Symbol [5-6]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_formatted() { + check_classical_decl( + "const uint x = 2_0_00;", + &expect![[r#" + ClassicalDeclarationStmt [0-22]: + symbol_id: 8 + ty_span: [6-10] + init_expr: Expr [15-21]: + ty: UInt(None, true) + kind: Lit: Int(2000) + [8] Symbol [11-12]: + name: x + type: UInt(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_explicit_bitness_int() { + check_classical_decl( + "uint[10] x;", + &expect![[r#" + ClassicalDeclarationStmt [0-11]: + symbol_id: 8 + ty_span: [0-8] + init_expr: Expr [0-0]: + ty: UInt(Some(10), true) + kind: Lit: Int(0) + [8] Symbol [9-10]: + name: x + type: UInt(Some(10), false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn assigning_uint_to_negative_lit() { + check_classical_decl( + "const uint[10] x = -42;", + &expect![[r#" + ClassicalDeclarationStmt [0-23]: + symbol_id: 8 + ty_span: [6-14] + init_expr: Expr [20-22]: + ty: UInt(Some(10), true) + kind: Cast [0-0]: + ty: UInt(Some(10), true) + expr: Expr [20-22]: + ty: Int(None, true) + kind: UnaryOpExpr [20-22]: + op: Neg + expr: Expr [20-22]: + ty: Int(None, true) + kind: Lit: Int(42) + [8] Symbol [15-16]: + name: x + type: UInt(Some(10), true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_uint_const_negative_decl() { + check_classical_decl( + "const uint x = -42;", + &expect![[r#" + ClassicalDeclarationStmt [0-19]: + symbol_id: 8 + ty_span: [6-10] + init_expr: Expr [16-18]: + ty: UInt(None, true) + kind: Cast [0-0]: + ty: UInt(None, true) + expr: Expr [16-18]: + ty: Int(None, true) + kind: UnaryOpExpr [16-18]: + op: Neg + expr: Expr [16-18]: + ty: Int(None, true) + kind: Lit: Int(42) + [8] Symbol [11-12]: + name: x + type: UInt(None, true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn explicit_bitness_uint_const_negative_decl() { + check_classical_decl( + "const uint[32] x = -42;", + &expect![[r#" + ClassicalDeclarationStmt [0-23]: + symbol_id: 8 + ty_span: [6-14] + init_expr: Expr [20-22]: + ty: UInt(Some(32), true) + kind: Cast [0-0]: + ty: UInt(Some(32), true) + expr: Expr [20-22]: + ty: Int(None, true) + kind: UnaryOpExpr [20-22]: + op: Neg + expr: Expr [20-22]: + ty: Int(None, true) + kind: Lit: Int(42) + [8] Symbol [15-16]: + name: x + type: UInt(Some(32), true) + qsharp_type: Int + io_kind: Default"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression.rs b/compiler/qsc_qasm/src/semantic/tests/expression.rs new file mode 100644 index 0000000000..8a2f3511ad --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression.rs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +mod binary; +mod implicit_cast_from_angle; +mod implicit_cast_from_bit; +mod implicit_cast_from_bitarray; +mod implicit_cast_from_bool; +mod implicit_cast_from_float; +mod implicit_cast_from_int; diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/binary.rs b/compiler/qsc_qasm/src/semantic/tests/expression/binary.rs new file mode 100644 index 0000000000..4e43914b2c --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/binary.rs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +mod arithmetic_conversions; +mod comparison; +mod complex; +mod ident; diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/binary/arithmetic_conversions.rs b/compiler/qsc_qasm/src/semantic/tests/expression/binary/arithmetic_conversions.rs new file mode 100644 index 0000000000..847c4abe36 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/binary/arithmetic_conversions.rs @@ -0,0 +1,308 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_stmt_kinds; + +#[test] +fn int_idents_without_width_can_be_multiplied() { + let input = " + int x = 5; + int y = 3; + x * y; + "; + + check_stmt_kinds( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, false) + kind: Lit: Int(5) + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, false) + kind: Lit: Int(3) + ExprStmt [47-53]: + expr: Expr [47-52]: + ty: Int(None, false) + kind: BinaryOpExpr: + op: Mul + lhs: Expr [47-48]: + ty: Int(None, false) + kind: SymbolId(8) + rhs: Expr [51-52]: + ty: Int(None, false) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn int_idents_with_same_width_can_be_multiplied() { + let input = " + int[32] x = 5; + int[32] y = 3; + x * y; + "; + + check_stmt_kinds( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-16] + init_expr: Expr [21-22]: + ty: Int(Some(32), true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [32-46]: + symbol_id: 9 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Int(Some(32), true) + kind: Lit: Int(3) + ExprStmt [55-61]: + expr: Expr [55-60]: + ty: Int(Some(32), false) + kind: BinaryOpExpr: + op: Mul + lhs: Expr [55-56]: + ty: Int(Some(32), false) + kind: SymbolId(8) + rhs: Expr [59-60]: + ty: Int(Some(32), false) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn int_idents_with_different_width_can_be_multiplied() { + let input = " + int[32] x = 5; + int[64] y = 3; + x * y; + "; + + check_stmt_kinds( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-16] + init_expr: Expr [21-22]: + ty: Int(Some(32), true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [32-46]: + symbol_id: 9 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Int(Some(64), true) + kind: Lit: Int(3) + ExprStmt [55-61]: + expr: Expr [55-60]: + ty: Int(Some(64), false) + kind: BinaryOpExpr: + op: Mul + lhs: Expr [55-56]: + ty: Int(Some(64), false) + kind: Cast [0-0]: + ty: Int(Some(64), false) + expr: Expr [55-56]: + ty: Int(Some(32), false) + kind: SymbolId(8) + rhs: Expr [59-60]: + ty: Int(Some(64), false) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn multiplying_int_idents_with_different_width_result_in_higher_width_result() { + let input = " + int[32] x = 5; + int[64] y = 3; + int[64] z = x * y; + "; + + check_stmt_kinds( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-16] + init_expr: Expr [21-22]: + ty: Int(Some(32), true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [32-46]: + symbol_id: 9 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Int(Some(64), true) + kind: Lit: Int(3) + ClassicalDeclarationStmt [55-73]: + symbol_id: 10 + ty_span: [55-62] + init_expr: Expr [67-72]: + ty: Int(Some(64), false) + kind: BinaryOpExpr: + op: Mul + lhs: Expr [67-68]: + ty: Int(Some(64), false) + kind: Cast [0-0]: + ty: Int(Some(64), false) + expr: Expr [67-68]: + ty: Int(Some(32), false) + kind: SymbolId(8) + rhs: Expr [71-72]: + ty: Int(Some(64), false) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn multiplying_int_idents_with_different_width_result_in_no_width_result() { + let input = " + int[32] x = 5; + int[64] y = 3; + int z = x * y; + "; + + check_stmt_kinds( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-16] + init_expr: Expr [21-22]: + ty: Int(Some(32), true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [32-46]: + symbol_id: 9 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Int(Some(64), true) + kind: Lit: Int(3) + ClassicalDeclarationStmt [55-69]: + symbol_id: 10 + ty_span: [55-58] + init_expr: Expr [63-68]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [63-68]: + ty: Int(Some(64), false) + kind: BinaryOpExpr: + op: Mul + lhs: Expr [63-64]: + ty: Int(Some(64), false) + kind: Cast [0-0]: + ty: Int(Some(64), false) + expr: Expr [63-64]: + ty: Int(Some(32), false) + kind: SymbolId(8) + rhs: Expr [67-68]: + ty: Int(Some(64), false) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn multiplying_int_idents_with_width_greater_than_64_result_in_bigint_result() { + let input = " + int[32] x = 5; + int[64] y = 3; + int[67] z = x * y; + "; + + check_stmt_kinds( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-16] + init_expr: Expr [21-22]: + ty: Int(Some(32), true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [32-46]: + symbol_id: 9 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Int(Some(64), true) + kind: Lit: Int(3) + ClassicalDeclarationStmt [55-73]: + symbol_id: 10 + ty_span: [55-62] + init_expr: Expr [67-72]: + ty: Int(Some(67), false) + kind: Cast [0-0]: + ty: Int(Some(67), false) + expr: Expr [67-72]: + ty: Int(Some(64), false) + kind: BinaryOpExpr: + op: Mul + lhs: Expr [67-68]: + ty: Int(Some(64), false) + kind: Cast [0-0]: + ty: Int(Some(64), false) + expr: Expr [67-68]: + ty: Int(Some(32), false) + kind: SymbolId(8) + rhs: Expr [71-72]: + ty: Int(Some(64), false) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn left_shift_casts_rhs_to_uint() { + let input = " + int x = 5; + int y = 3; + int z = x << y; + "; + + check_stmt_kinds( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, false) + kind: Lit: Int(5) + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, false) + kind: Lit: Int(3) + ClassicalDeclarationStmt [47-62]: + symbol_id: 10 + ty_span: [47-50] + init_expr: Expr [55-61]: + ty: Int(None, false) + kind: BinaryOpExpr: + op: Shl + lhs: Expr [55-56]: + ty: Int(None, false) + kind: SymbolId(8) + rhs: Expr [60-61]: + ty: UInt(None, false) + kind: Cast [0-0]: + ty: UInt(None, false) + expr: Expr [60-61]: + ty: Int(None, false) + kind: SymbolId(9) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison.rs b/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison.rs new file mode 100644 index 0000000000..2ec6e5c4e8 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison.rs @@ -0,0 +1,411 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +mod bit_to_bit; +mod bool_to_bool; +mod float_to_float; +mod int_to_int; + +mod uint_to_uint; + +use expect_test::expect; + +use crate::semantic::tests::check_stmt_kinds; + +#[allow(clippy::too_many_lines)] +#[test] +fn bitarray_var_comparisons_can_be_translated() { + let input = r#" + bit[1] x = "1"; + bit[1] y = "0"; + bool f = x > y; + bool e = x >= y; + bool a = x < y; + bool c = x <= y; + bool b = x == y; + bool d = x != y; + "#; + + check_stmt_kinds( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-24]: + symbol_id: 8 + ty_span: [9-15] + init_expr: Expr [20-23]: + ty: BitArray(One(1), false) + kind: Lit: Bitstring("1") + ClassicalDeclarationStmt [33-48]: + symbol_id: 9 + ty_span: [33-39] + init_expr: Expr [44-47]: + ty: BitArray(One(1), false) + kind: Lit: Bitstring("0") + ClassicalDeclarationStmt [57-72]: + symbol_id: 10 + ty_span: [57-61] + init_expr: Expr [66-71]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Gt + lhs: Expr [66-67]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [66-67]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + rhs: Expr [70-71]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [70-71]: + ty: BitArray(One(1), false) + kind: SymbolId(9) + ClassicalDeclarationStmt [81-97]: + symbol_id: 11 + ty_span: [81-85] + init_expr: Expr [90-96]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Gte + lhs: Expr [90-91]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [90-91]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + rhs: Expr [95-96]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [95-96]: + ty: BitArray(One(1), false) + kind: SymbolId(9) + ClassicalDeclarationStmt [106-121]: + symbol_id: 12 + ty_span: [106-110] + init_expr: Expr [115-120]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Lt + lhs: Expr [115-116]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [115-116]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + rhs: Expr [119-120]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [119-120]: + ty: BitArray(One(1), false) + kind: SymbolId(9) + ClassicalDeclarationStmt [130-146]: + symbol_id: 13 + ty_span: [130-134] + init_expr: Expr [139-145]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Lte + lhs: Expr [139-140]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [139-140]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + rhs: Expr [144-145]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [144-145]: + ty: BitArray(One(1), false) + kind: SymbolId(9) + ClassicalDeclarationStmt [155-171]: + symbol_id: 14 + ty_span: [155-159] + init_expr: Expr [164-170]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Eq + lhs: Expr [164-165]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [164-165]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + rhs: Expr [169-170]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [169-170]: + ty: BitArray(One(1), false) + kind: SymbolId(9) + ClassicalDeclarationStmt [180-196]: + symbol_id: 15 + ty_span: [180-184] + init_expr: Expr [189-195]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Neq + lhs: Expr [189-190]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [189-190]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + rhs: Expr [194-195]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [194-195]: + ty: BitArray(One(1), false) + kind: SymbolId(9) + "#]], + ); +} + +#[allow(clippy::too_many_lines)] +#[test] +fn bitarray_var_comparison_to_int_can_be_translated() { + let input = r#" + bit[1] x = "1"; + input int y; + bool a = x > y; + bool b = x >= y; + bool c = x < y; + bool d = x <= y; + bool e = x == y; + bool f = x != y; + bool g = y > x; + bool h = y >= x; + bool i = y < x; + bool j = y <= x; + bool k = y == x; + bool l = y != x; + "#; + + check_stmt_kinds( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-24]: + symbol_id: 8 + ty_span: [9-15] + init_expr: Expr [20-23]: + ty: BitArray(One(1), false) + kind: Lit: Bitstring("1") + InputDeclaration [33-45]: + symbol_id: 9 + ClassicalDeclarationStmt [54-69]: + symbol_id: 10 + ty_span: [54-58] + init_expr: Expr [63-68]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Gt + lhs: Expr [63-64]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [63-64]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + rhs: Expr [67-68]: + ty: Int(None, false) + kind: SymbolId(9) + ClassicalDeclarationStmt [78-94]: + symbol_id: 11 + ty_span: [78-82] + init_expr: Expr [87-93]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Gte + lhs: Expr [87-88]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [87-88]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + rhs: Expr [92-93]: + ty: Int(None, false) + kind: SymbolId(9) + ClassicalDeclarationStmt [103-118]: + symbol_id: 12 + ty_span: [103-107] + init_expr: Expr [112-117]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Lt + lhs: Expr [112-113]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [112-113]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + rhs: Expr [116-117]: + ty: Int(None, false) + kind: SymbolId(9) + ClassicalDeclarationStmt [127-143]: + symbol_id: 13 + ty_span: [127-131] + init_expr: Expr [136-142]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Lte + lhs: Expr [136-137]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [136-137]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + rhs: Expr [141-142]: + ty: Int(None, false) + kind: SymbolId(9) + ClassicalDeclarationStmt [152-168]: + symbol_id: 14 + ty_span: [152-156] + init_expr: Expr [161-167]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Eq + lhs: Expr [161-162]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [161-162]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + rhs: Expr [166-167]: + ty: Int(None, false) + kind: SymbolId(9) + ClassicalDeclarationStmt [177-193]: + symbol_id: 15 + ty_span: [177-181] + init_expr: Expr [186-192]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Neq + lhs: Expr [186-187]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [186-187]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + rhs: Expr [191-192]: + ty: Int(None, false) + kind: SymbolId(9) + ClassicalDeclarationStmt [202-217]: + symbol_id: 16 + ty_span: [202-206] + init_expr: Expr [211-216]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Gt + lhs: Expr [211-212]: + ty: Int(None, false) + kind: SymbolId(9) + rhs: Expr [215-216]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [215-216]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + ClassicalDeclarationStmt [226-242]: + symbol_id: 17 + ty_span: [226-230] + init_expr: Expr [235-241]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Gte + lhs: Expr [235-236]: + ty: Int(None, false) + kind: SymbolId(9) + rhs: Expr [240-241]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [240-241]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + ClassicalDeclarationStmt [251-266]: + symbol_id: 18 + ty_span: [251-255] + init_expr: Expr [260-265]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Lt + lhs: Expr [260-261]: + ty: Int(None, false) + kind: SymbolId(9) + rhs: Expr [264-265]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [264-265]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + ClassicalDeclarationStmt [275-291]: + symbol_id: 19 + ty_span: [275-279] + init_expr: Expr [284-290]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Lte + lhs: Expr [284-285]: + ty: Int(None, false) + kind: SymbolId(9) + rhs: Expr [289-290]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [289-290]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + ClassicalDeclarationStmt [300-316]: + symbol_id: 20 + ty_span: [300-304] + init_expr: Expr [309-315]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Eq + lhs: Expr [309-310]: + ty: Int(None, false) + kind: SymbolId(9) + rhs: Expr [314-315]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [314-315]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + ClassicalDeclarationStmt [325-341]: + symbol_id: 21 + ty_span: [325-329] + init_expr: Expr [334-340]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Neq + lhs: Expr [334-335]: + ty: Int(None, false) + kind: SymbolId(9) + rhs: Expr [339-340]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [339-340]: + ty: BitArray(One(1), false) + kind: SymbolId(8) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/bit_to_bit.rs b/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/bit_to_bit.rs new file mode 100644 index 0000000000..435eec3e15 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/bit_to_bit.rs @@ -0,0 +1,542 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decls; + +#[test] +fn logical_and() { + let input = " + bit x = 1; + bit y = 0; + bool a = x && y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Bit(0) + [9] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-63]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-62]: + ty: Bool(false) + kind: BinaryOpExpr: + op: AndL + lhs: Expr [56-57]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [56-57]: + ty: Bit(false) + kind: SymbolId(8) + rhs: Expr [61-62]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [61-62]: + ty: Bit(false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn logical_or() { + let input = " + bit x = 1; + bit y = 0; + bool a = x || y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Bit(0) + [9] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-63]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-62]: + ty: Bool(false) + kind: BinaryOpExpr: + op: OrL + lhs: Expr [56-57]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [56-57]: + ty: Bit(false) + kind: SymbolId(8) + rhs: Expr [61-62]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [61-62]: + ty: Bit(false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn unop_not_logical_and_unop_not() { + let input = " + bit x = 1; + bit y = 0; + bool a = !x && !y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Bit(0) + [9] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-65]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-64]: + ty: Bool(false) + kind: BinaryOpExpr: + op: AndL + lhs: Expr [57-58]: + ty: Bool(false) + kind: UnaryOpExpr [57-58]: + op: NotL + expr: Expr [57-58]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [57-58]: + ty: Bit(false) + kind: SymbolId(8) + rhs: Expr [63-64]: + ty: Bool(false) + kind: UnaryOpExpr [63-64]: + op: NotL + expr: Expr [63-64]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [63-64]: + ty: Bit(false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn unop_not_logical_or_unop_not() { + let input = " + bit x = 1; + bit y = 0; + bool a = !x || !y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Bit(0) + [9] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-65]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-64]: + ty: Bool(false) + kind: BinaryOpExpr: + op: OrL + lhs: Expr [57-58]: + ty: Bool(false) + kind: UnaryOpExpr [57-58]: + op: NotL + expr: Expr [57-58]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [57-58]: + ty: Bit(false) + kind: SymbolId(8) + rhs: Expr [63-64]: + ty: Bool(false) + kind: UnaryOpExpr [63-64]: + op: NotL + expr: Expr [63-64]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [63-64]: + ty: Bit(false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn unop_not_logical_and() { + let input = " + bit x = 1; + bit y = 0; + bool a = !x && y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Bit(0) + [9] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-64]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-63]: + ty: Bool(false) + kind: BinaryOpExpr: + op: AndL + lhs: Expr [57-58]: + ty: Bool(false) + kind: UnaryOpExpr [57-58]: + op: NotL + expr: Expr [57-58]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [57-58]: + ty: Bit(false) + kind: SymbolId(8) + rhs: Expr [62-63]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [62-63]: + ty: Bit(false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn unop_not_logical_or() { + let input = " + bit x = 1; + bit y = 0; + bool a = !x || y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Bit(0) + [9] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-64]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-63]: + ty: Bool(false) + kind: BinaryOpExpr: + op: OrL + lhs: Expr [57-58]: + ty: Bool(false) + kind: UnaryOpExpr [57-58]: + op: NotL + expr: Expr [57-58]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [57-58]: + ty: Bit(false) + kind: SymbolId(8) + rhs: Expr [62-63]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [62-63]: + ty: Bit(false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn logical_and_unop_not() { + let input = " + bit x = 1; + bit y = 0; + bool a = x && !y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Bit(0) + [9] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-64]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-63]: + ty: Bool(false) + kind: BinaryOpExpr: + op: AndL + lhs: Expr [56-57]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [56-57]: + ty: Bit(false) + kind: SymbolId(8) + rhs: Expr [62-63]: + ty: Bool(false) + kind: UnaryOpExpr [62-63]: + op: NotL + expr: Expr [62-63]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [62-63]: + ty: Bit(false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn logical_or_unop_not() { + let input = " + bit x = 1; + bit y = 0; + bool a = x || !y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Bit(0) + [9] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-64]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-63]: + ty: Bool(false) + kind: BinaryOpExpr: + op: OrL + lhs: Expr [56-57]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [56-57]: + ty: Bit(false) + kind: SymbolId(8) + rhs: Expr [62-63]: + ty: Bool(false) + kind: UnaryOpExpr [62-63]: + op: NotL + expr: Expr [62-63]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [62-63]: + ty: Bit(false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/bool_to_bool.rs b/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/bool_to_bool.rs new file mode 100644 index 0000000000..d1e2ee4edb --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/bool_to_bool.rs @@ -0,0 +1,478 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decls; + +#[test] +fn logical_and() { + let input = " + bool x = true; + bool y = false; + bool a = x && y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 9 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(false) + kind: Lit: Bool(false) + [9] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-72]: + symbol_id: 10 + ty_span: [56-60] + init_expr: Expr [65-71]: + ty: Bool(false) + kind: BinaryOpExpr: + op: AndL + lhs: Expr [65-66]: + ty: Bool(false) + kind: SymbolId(8) + rhs: Expr [70-71]: + ty: Bool(false) + kind: SymbolId(9) + [10] Symbol [61-62]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn logical_or() { + let input = " + bool x = true; + bool y = false; + bool a = x || y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 9 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(false) + kind: Lit: Bool(false) + [9] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-72]: + symbol_id: 10 + ty_span: [56-60] + init_expr: Expr [65-71]: + ty: Bool(false) + kind: BinaryOpExpr: + op: OrL + lhs: Expr [65-66]: + ty: Bool(false) + kind: SymbolId(8) + rhs: Expr [70-71]: + ty: Bool(false) + kind: SymbolId(9) + [10] Symbol [61-62]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn unop_not_logical_and_unop_not() { + let input = " + bool x = true; + bool y = false; + bool a = !x && !y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 9 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(false) + kind: Lit: Bool(false) + [9] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-74]: + symbol_id: 10 + ty_span: [56-60] + init_expr: Expr [65-73]: + ty: Bool(false) + kind: BinaryOpExpr: + op: AndL + lhs: Expr [66-67]: + ty: Bool(false) + kind: UnaryOpExpr [66-67]: + op: NotL + expr: Expr [66-67]: + ty: Bool(false) + kind: SymbolId(8) + rhs: Expr [72-73]: + ty: Bool(false) + kind: UnaryOpExpr [72-73]: + op: NotL + expr: Expr [72-73]: + ty: Bool(false) + kind: SymbolId(9) + [10] Symbol [61-62]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn unop_not_logical_or_unop_not() { + let input = " + bool x = true; + bool y = false; + bool a = !x || !y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 9 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(false) + kind: Lit: Bool(false) + [9] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-74]: + symbol_id: 10 + ty_span: [56-60] + init_expr: Expr [65-73]: + ty: Bool(false) + kind: BinaryOpExpr: + op: OrL + lhs: Expr [66-67]: + ty: Bool(false) + kind: UnaryOpExpr [66-67]: + op: NotL + expr: Expr [66-67]: + ty: Bool(false) + kind: SymbolId(8) + rhs: Expr [72-73]: + ty: Bool(false) + kind: UnaryOpExpr [72-73]: + op: NotL + expr: Expr [72-73]: + ty: Bool(false) + kind: SymbolId(9) + [10] Symbol [61-62]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn unop_not_logical_and() { + let input = " + bool x = true; + bool y = false; + bool a = !x && y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 9 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(false) + kind: Lit: Bool(false) + [9] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-73]: + symbol_id: 10 + ty_span: [56-60] + init_expr: Expr [65-72]: + ty: Bool(false) + kind: BinaryOpExpr: + op: AndL + lhs: Expr [66-67]: + ty: Bool(false) + kind: UnaryOpExpr [66-67]: + op: NotL + expr: Expr [66-67]: + ty: Bool(false) + kind: SymbolId(8) + rhs: Expr [71-72]: + ty: Bool(false) + kind: SymbolId(9) + [10] Symbol [61-62]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn unop_not_logical_or() { + let input = " + bool x = true; + bool y = false; + bool a = !x || y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 9 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(false) + kind: Lit: Bool(false) + [9] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-73]: + symbol_id: 10 + ty_span: [56-60] + init_expr: Expr [65-72]: + ty: Bool(false) + kind: BinaryOpExpr: + op: OrL + lhs: Expr [66-67]: + ty: Bool(false) + kind: UnaryOpExpr [66-67]: + op: NotL + expr: Expr [66-67]: + ty: Bool(false) + kind: SymbolId(8) + rhs: Expr [71-72]: + ty: Bool(false) + kind: SymbolId(9) + [10] Symbol [61-62]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn logical_and_unop_not() { + let input = " + bool x = true; + bool y = false; + bool a = x && !y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 9 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(false) + kind: Lit: Bool(false) + [9] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-73]: + symbol_id: 10 + ty_span: [56-60] + init_expr: Expr [65-72]: + ty: Bool(false) + kind: BinaryOpExpr: + op: AndL + lhs: Expr [65-66]: + ty: Bool(false) + kind: SymbolId(8) + rhs: Expr [71-72]: + ty: Bool(false) + kind: UnaryOpExpr [71-72]: + op: NotL + expr: Expr [71-72]: + ty: Bool(false) + kind: SymbolId(9) + [10] Symbol [61-62]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn logical_or_unop_not() { + let input = " + bool x = true; + bool y = false; + bool a = x || !y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 9 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(false) + kind: Lit: Bool(false) + [9] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-73]: + symbol_id: 10 + ty_span: [56-60] + init_expr: Expr [65-72]: + ty: Bool(false) + kind: BinaryOpExpr: + op: OrL + lhs: Expr [65-66]: + ty: Bool(false) + kind: SymbolId(8) + rhs: Expr [71-72]: + ty: Bool(false) + kind: UnaryOpExpr [71-72]: + op: NotL + expr: Expr [71-72]: + ty: Bool(false) + kind: SymbolId(9) + [10] Symbol [61-62]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/float_to_float.rs b/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/float_to_float.rs new file mode 100644 index 0000000000..9439a6c6d2 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/float_to_float.rs @@ -0,0 +1,336 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decls; + +#[test] +fn greater_than() { + let input = " + float x = 5.; + float y = 3.; + bool f = x > y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-22]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-21]: + ty: Float(None, false) + kind: Lit: Float(5.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [31-44]: + symbol_id: 9 + ty_span: [31-36] + init_expr: Expr [41-43]: + ty: Float(None, false) + kind: Lit: Float(3.0) + [9] Symbol [37-38]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [53-68]: + symbol_id: 10 + ty_span: [53-57] + init_expr: Expr [62-67]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Gt + lhs: Expr [62-63]: + ty: Float(None, false) + kind: SymbolId(8) + rhs: Expr [66-67]: + ty: Float(None, false) + kind: SymbolId(9) + [10] Symbol [58-59]: + name: f + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn greater_than_equals() { + let input = " + float x = 5.; + float y = 3.; + bool e = x >= y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-22]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-21]: + ty: Float(None, false) + kind: Lit: Float(5.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [31-44]: + symbol_id: 9 + ty_span: [31-36] + init_expr: Expr [41-43]: + ty: Float(None, false) + kind: Lit: Float(3.0) + [9] Symbol [37-38]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [53-69]: + symbol_id: 10 + ty_span: [53-57] + init_expr: Expr [62-68]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Gte + lhs: Expr [62-63]: + ty: Float(None, false) + kind: SymbolId(8) + rhs: Expr [67-68]: + ty: Float(None, false) + kind: SymbolId(9) + [10] Symbol [58-59]: + name: e + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn less_than() { + let input = " + float x = 5.; + float y = 3.; + bool a = x < y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-22]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-21]: + ty: Float(None, false) + kind: Lit: Float(5.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [31-44]: + symbol_id: 9 + ty_span: [31-36] + init_expr: Expr [41-43]: + ty: Float(None, false) + kind: Lit: Float(3.0) + [9] Symbol [37-38]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [53-68]: + symbol_id: 10 + ty_span: [53-57] + init_expr: Expr [62-67]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Lt + lhs: Expr [62-63]: + ty: Float(None, false) + kind: SymbolId(8) + rhs: Expr [66-67]: + ty: Float(None, false) + kind: SymbolId(9) + [10] Symbol [58-59]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn less_than_equals() { + let input = " + float x = 5.; + float y = 3.; + bool c = x <= y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-22]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-21]: + ty: Float(None, false) + kind: Lit: Float(5.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [31-44]: + symbol_id: 9 + ty_span: [31-36] + init_expr: Expr [41-43]: + ty: Float(None, false) + kind: Lit: Float(3.0) + [9] Symbol [37-38]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [53-69]: + symbol_id: 10 + ty_span: [53-57] + init_expr: Expr [62-68]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Lte + lhs: Expr [62-63]: + ty: Float(None, false) + kind: SymbolId(8) + rhs: Expr [67-68]: + ty: Float(None, false) + kind: SymbolId(9) + [10] Symbol [58-59]: + name: c + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn equals() { + let input = " + float x = 5.; + float y = 3.; + bool b = x == y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-22]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-21]: + ty: Float(None, false) + kind: Lit: Float(5.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [31-44]: + symbol_id: 9 + ty_span: [31-36] + init_expr: Expr [41-43]: + ty: Float(None, false) + kind: Lit: Float(3.0) + [9] Symbol [37-38]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [53-69]: + symbol_id: 10 + ty_span: [53-57] + init_expr: Expr [62-68]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Eq + lhs: Expr [62-63]: + ty: Float(None, false) + kind: SymbolId(8) + rhs: Expr [67-68]: + ty: Float(None, false) + kind: SymbolId(9) + [10] Symbol [58-59]: + name: b + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn not_equals() { + let input = " + float x = 5.; + float y = 3.; + bool d = x != y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-22]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-21]: + ty: Float(None, false) + kind: Lit: Float(5.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [31-44]: + symbol_id: 9 + ty_span: [31-36] + init_expr: Expr [41-43]: + ty: Float(None, false) + kind: Lit: Float(3.0) + [9] Symbol [37-38]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [53-69]: + symbol_id: 10 + ty_span: [53-57] + init_expr: Expr [62-68]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Neq + lhs: Expr [62-63]: + ty: Float(None, false) + kind: SymbolId(8) + rhs: Expr [67-68]: + ty: Float(None, false) + kind: SymbolId(9) + [10] Symbol [58-59]: + name: d + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/int_to_int.rs b/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/int_to_int.rs new file mode 100644 index 0000000000..a3ff2d14e1 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/int_to_int.rs @@ -0,0 +1,336 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decls; + +#[test] +fn greater_than() { + let input = " + int x = 5; + int y = 3; + bool f = x > y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, false) + kind: Lit: Int(5) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, false) + kind: Lit: Int(3) + [9] Symbol [32-33]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [47-62]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-61]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Gt + lhs: Expr [56-57]: + ty: Int(None, false) + kind: SymbolId(8) + rhs: Expr [60-61]: + ty: Int(None, false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: f + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn greater_than_equals() { + let input = " + int x = 5; + int y = 3; + bool e = x >= y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, false) + kind: Lit: Int(5) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, false) + kind: Lit: Int(3) + [9] Symbol [32-33]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [47-63]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-62]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Gte + lhs: Expr [56-57]: + ty: Int(None, false) + kind: SymbolId(8) + rhs: Expr [61-62]: + ty: Int(None, false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: e + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn less_than() { + let input = " + int x = 5; + int y = 3; + bool a = x < y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, false) + kind: Lit: Int(5) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, false) + kind: Lit: Int(3) + [9] Symbol [32-33]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [47-62]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-61]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Lt + lhs: Expr [56-57]: + ty: Int(None, false) + kind: SymbolId(8) + rhs: Expr [60-61]: + ty: Int(None, false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn less_than_equals() { + let input = " + int x = 5; + int y = 3; + bool c = x <= y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, false) + kind: Lit: Int(5) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, false) + kind: Lit: Int(3) + [9] Symbol [32-33]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [47-63]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-62]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Lte + lhs: Expr [56-57]: + ty: Int(None, false) + kind: SymbolId(8) + rhs: Expr [61-62]: + ty: Int(None, false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: c + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn equals() { + let input = " + int x = 5; + int y = 3; + bool b = x == y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, false) + kind: Lit: Int(5) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, false) + kind: Lit: Int(3) + [9] Symbol [32-33]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [47-63]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-62]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Eq + lhs: Expr [56-57]: + ty: Int(None, false) + kind: SymbolId(8) + rhs: Expr [61-62]: + ty: Int(None, false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: b + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn not_equals() { + let input = " + int x = 5; + int y = 3; + bool d = x != y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, false) + kind: Lit: Int(5) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, false) + kind: Lit: Int(3) + [9] Symbol [32-33]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [47-63]: + symbol_id: 10 + ty_span: [47-51] + init_expr: Expr [56-62]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Neq + lhs: Expr [56-57]: + ty: Int(None, false) + kind: SymbolId(8) + rhs: Expr [61-62]: + ty: Int(None, false) + kind: SymbolId(9) + [10] Symbol [52-53]: + name: d + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/uint_to_uint.rs b/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/uint_to_uint.rs new file mode 100644 index 0000000000..37847ee8e2 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/binary/comparison/uint_to_uint.rs @@ -0,0 +1,336 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decls; + +#[test] +fn greater_than() { + let input = " + uint x = 5; + uint y = 3; + bool f = x > y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-19]: + ty: UInt(None, true) + kind: Lit: Int(5) + [8] Symbol [14-15]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 9 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: UInt(None, true) + kind: Lit: Int(3) + [9] Symbol [34-35]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [49-64]: + symbol_id: 10 + ty_span: [49-53] + init_expr: Expr [58-63]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Gt + lhs: Expr [58-59]: + ty: UInt(None, false) + kind: SymbolId(8) + rhs: Expr [62-63]: + ty: UInt(None, false) + kind: SymbolId(9) + [10] Symbol [54-55]: + name: f + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn greater_than_equals() { + let input = " + uint x = 5; + uint y = 3; + bool e = x >= y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-19]: + ty: UInt(None, true) + kind: Lit: Int(5) + [8] Symbol [14-15]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 9 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: UInt(None, true) + kind: Lit: Int(3) + [9] Symbol [34-35]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [49-65]: + symbol_id: 10 + ty_span: [49-53] + init_expr: Expr [58-64]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Gte + lhs: Expr [58-59]: + ty: UInt(None, false) + kind: SymbolId(8) + rhs: Expr [63-64]: + ty: UInt(None, false) + kind: SymbolId(9) + [10] Symbol [54-55]: + name: e + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn less_than() { + let input = " + uint x = 5; + uint y = 3; + bool a = x < y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-19]: + ty: UInt(None, true) + kind: Lit: Int(5) + [8] Symbol [14-15]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 9 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: UInt(None, true) + kind: Lit: Int(3) + [9] Symbol [34-35]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [49-64]: + symbol_id: 10 + ty_span: [49-53] + init_expr: Expr [58-63]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Lt + lhs: Expr [58-59]: + ty: UInt(None, false) + kind: SymbolId(8) + rhs: Expr [62-63]: + ty: UInt(None, false) + kind: SymbolId(9) + [10] Symbol [54-55]: + name: a + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn less_than_equals() { + let input = " + uint x = 5; + uint y = 3; + bool c = x <= y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-19]: + ty: UInt(None, true) + kind: Lit: Int(5) + [8] Symbol [14-15]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 9 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: UInt(None, true) + kind: Lit: Int(3) + [9] Symbol [34-35]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [49-65]: + symbol_id: 10 + ty_span: [49-53] + init_expr: Expr [58-64]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Lte + lhs: Expr [58-59]: + ty: UInt(None, false) + kind: SymbolId(8) + rhs: Expr [63-64]: + ty: UInt(None, false) + kind: SymbolId(9) + [10] Symbol [54-55]: + name: c + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn equals() { + let input = " + uint x = 5; + uint y = 3; + bool b = x == y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-19]: + ty: UInt(None, true) + kind: Lit: Int(5) + [8] Symbol [14-15]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 9 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: UInt(None, true) + kind: Lit: Int(3) + [9] Symbol [34-35]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [49-65]: + symbol_id: 10 + ty_span: [49-53] + init_expr: Expr [58-64]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Eq + lhs: Expr [58-59]: + ty: UInt(None, false) + kind: SymbolId(8) + rhs: Expr [63-64]: + ty: UInt(None, false) + kind: SymbolId(9) + [10] Symbol [54-55]: + name: b + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn not_equals() { + let input = " + uint x = 5; + uint y = 3; + bool d = x != y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-19]: + ty: UInt(None, true) + kind: Lit: Int(5) + [8] Symbol [14-15]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 9 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: UInt(None, true) + kind: Lit: Int(3) + [9] Symbol [34-35]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [49-65]: + symbol_id: 10 + ty_span: [49-53] + init_expr: Expr [58-64]: + ty: Bool(false) + kind: BinaryOpExpr: + op: Neq + lhs: Expr [58-59]: + ty: UInt(None, false) + kind: SymbolId(8) + rhs: Expr [63-64]: + ty: UInt(None, false) + kind: SymbolId(9) + [10] Symbol [54-55]: + name: d + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/binary/complex.rs b/compiler/qsc_qasm/src/semantic/tests/expression/binary/complex.rs new file mode 100644 index 0000000000..489b357a02 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/binary/complex.rs @@ -0,0 +1,330 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::semantic::tests::check_stmt_kinds; +use expect_test::expect; + +#[test] +fn addition() { + let input = " + input complex[float] a; + input complex[float] b; + complex x = a + b; + "; + + check_stmt_kinds( + input, + &expect![[r#" + InputDeclaration [9-32]: + symbol_id: 8 + InputDeclaration [41-64]: + symbol_id: 9 + ClassicalDeclarationStmt [73-91]: + symbol_id: 10 + ty_span: [73-80] + init_expr: Expr [85-90]: + ty: Complex(None, false) + kind: BinaryOpExpr: + op: Add + lhs: Expr [85-86]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [89-90]: + ty: Complex(None, false) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn addition_assign_op() { + let input = " + input complex[float] a; + complex x = 0.0; + x += a; + "; + + check_stmt_kinds( + input, + &expect![[r#" + InputDeclaration [9-32]: + symbol_id: 8 + ClassicalDeclarationStmt [41-57]: + symbol_id: 9 + ty_span: [41-48] + init_expr: Expr [53-56]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 0.0) + AssignOpStmt [66-73]: + symbol_id: 9 + indices: + op: Add + lhs: Expr [71-72]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [71-72]: + ty: Complex(None, false) + kind: SymbolId(8) + "#]], + ); +} + +#[test] +fn subtraction() { + let input = " + input complex[float] a; + input complex[float] b; + complex x = a - b; + "; + + check_stmt_kinds( + input, + &expect![[r#" + InputDeclaration [9-32]: + symbol_id: 8 + InputDeclaration [41-64]: + symbol_id: 9 + ClassicalDeclarationStmt [73-91]: + symbol_id: 10 + ty_span: [73-80] + init_expr: Expr [85-90]: + ty: Complex(None, false) + kind: BinaryOpExpr: + op: Sub + lhs: Expr [85-86]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [89-90]: + ty: Complex(None, false) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn subtraction_assign_op() { + let input = " + input complex[float] a; + complex x = 0.0; + x -= a; + "; + + check_stmt_kinds( + input, + &expect![[r#" + InputDeclaration [9-32]: + symbol_id: 8 + ClassicalDeclarationStmt [41-57]: + symbol_id: 9 + ty_span: [41-48] + init_expr: Expr [53-56]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 0.0) + AssignOpStmt [66-73]: + symbol_id: 9 + indices: + op: Sub + lhs: Expr [71-72]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [71-72]: + ty: Complex(None, false) + kind: SymbolId(8) + "#]], + ); +} + +#[test] +fn multiplication() { + let input = " + input complex[float] a; + input complex[float] b; + complex x = a * b; + "; + + check_stmt_kinds( + input, + &expect![[r#" + InputDeclaration [9-32]: + symbol_id: 8 + InputDeclaration [41-64]: + symbol_id: 9 + ClassicalDeclarationStmt [73-91]: + symbol_id: 10 + ty_span: [73-80] + init_expr: Expr [85-90]: + ty: Complex(None, false) + kind: BinaryOpExpr: + op: Mul + lhs: Expr [85-86]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [89-90]: + ty: Complex(None, false) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn multiplication_assign_op() { + let input = " + input complex[float] a; + complex x = 0.0; + x *= a; + "; + + check_stmt_kinds( + input, + &expect![[r#" + InputDeclaration [9-32]: + symbol_id: 8 + ClassicalDeclarationStmt [41-57]: + symbol_id: 9 + ty_span: [41-48] + init_expr: Expr [53-56]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 0.0) + AssignOpStmt [66-73]: + symbol_id: 9 + indices: + op: Mul + lhs: Expr [71-72]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [71-72]: + ty: Complex(None, false) + kind: SymbolId(8) + "#]], + ); +} + +#[test] +fn division() { + let input = " + input complex[float] a; + input complex[float] b; + complex x = a / b; + "; + + check_stmt_kinds( + input, + &expect![[r#" + InputDeclaration [9-32]: + symbol_id: 8 + InputDeclaration [41-64]: + symbol_id: 9 + ClassicalDeclarationStmt [73-91]: + symbol_id: 10 + ty_span: [73-80] + init_expr: Expr [85-90]: + ty: Complex(None, false) + kind: BinaryOpExpr: + op: Div + lhs: Expr [85-86]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [89-90]: + ty: Complex(None, false) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn division_assign_op() { + let input = " + input complex[float] a; + complex x = 0.0; + x /= a; + "; + + check_stmt_kinds( + input, + &expect![[r#" + InputDeclaration [9-32]: + symbol_id: 8 + ClassicalDeclarationStmt [41-57]: + symbol_id: 9 + ty_span: [41-48] + init_expr: Expr [53-56]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 0.0) + AssignOpStmt [66-73]: + symbol_id: 9 + indices: + op: Div + lhs: Expr [71-72]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [71-72]: + ty: Complex(None, false) + kind: SymbolId(8) + "#]], + ); +} + +#[test] +fn power() { + let input = " + input complex[float] a; + input complex[float] b; + complex x = a ** b; + "; + + check_stmt_kinds( + input, + &expect![[r#" + InputDeclaration [9-32]: + symbol_id: 8 + InputDeclaration [41-64]: + symbol_id: 9 + ClassicalDeclarationStmt [73-92]: + symbol_id: 10 + ty_span: [73-80] + init_expr: Expr [85-91]: + ty: Complex(None, false) + kind: BinaryOpExpr: + op: Exp + lhs: Expr [85-86]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [90-91]: + ty: Complex(None, false) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn power_assign_op() { + let input = " + input complex[float] a; + complex x = 0.0; + x **= a; + "; + + check_stmt_kinds( + input, + &expect![[r#" + InputDeclaration [9-32]: + symbol_id: 8 + ClassicalDeclarationStmt [41-57]: + symbol_id: 9 + ty_span: [41-48] + init_expr: Expr [53-56]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 0.0) + AssignOpStmt [66-74]: + symbol_id: 9 + indices: + op: Exp + lhs: Expr [72-73]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [72-73]: + ty: Complex(None, false) + kind: SymbolId(8) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/binary/ident.rs b/compiler/qsc_qasm/src/semantic/tests/expression/binary/ident.rs new file mode 100644 index 0000000000..a88ba0f04f --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/binary/ident.rs @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_stmt_kinds; +#[test] +fn mutable_int_idents_without_width_can_be_multiplied() { + let input = " + int x = 5; + int y = 3; + x * y; + "; + + check_stmt_kinds( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, false) + kind: Lit: Int(5) + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, false) + kind: Lit: Int(3) + ExprStmt [47-53]: + expr: Expr [47-52]: + ty: Int(None, false) + kind: BinaryOpExpr: + op: Mul + lhs: Expr [47-48]: + ty: Int(None, false) + kind: SymbolId(8) + rhs: Expr [51-52]: + ty: Int(None, false) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn const_int_idents_without_width_can_be_multiplied() { + let input = " + const int x = 5; + const int y = 3; + x * y; + "; + + check_stmt_kinds( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-25]: + symbol_id: 8 + ty_span: [15-18] + init_expr: Expr [23-24]: + ty: Int(None, true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [34-50]: + symbol_id: 9 + ty_span: [40-43] + init_expr: Expr [48-49]: + ty: Int(None, true) + kind: Lit: Int(3) + ExprStmt [59-65]: + expr: Expr [59-64]: + ty: Int(None, true) + kind: BinaryOpExpr: + op: Mul + lhs: Expr [59-60]: + ty: Int(None, true) + kind: SymbolId(8) + rhs: Expr [63-64]: + ty: Int(None, true) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn const_and_mut_int_idents_without_width_can_be_multiplied() { + let input = " + int x = 5; + const int y = 3; + x * y; + "; + + check_stmt_kinds( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, false) + kind: Lit: Int(5) + ClassicalDeclarationStmt [28-44]: + symbol_id: 9 + ty_span: [34-37] + init_expr: Expr [42-43]: + ty: Int(None, true) + kind: Lit: Int(3) + ExprStmt [53-59]: + expr: Expr [53-58]: + ty: Int(None, false) + kind: BinaryOpExpr: + op: Mul + lhs: Expr [53-54]: + ty: Int(None, false) + kind: SymbolId(8) + rhs: Expr [57-58]: + ty: Int(None, false) + kind: SymbolId(9) + "#]], + ); +} + +#[test] +fn const_int_idents_widthless_lhs_can_be_multiplied_by_explicit_width_int() { + let input = " + const int[32] x = 5; + const int y = 3; + x * y; + "; + + check_stmt_kinds( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-29]: + symbol_id: 8 + ty_span: [15-22] + init_expr: Expr [27-28]: + ty: Int(Some(32), true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [38-54]: + symbol_id: 9 + ty_span: [44-47] + init_expr: Expr [52-53]: + ty: Int(None, true) + kind: Lit: Int(3) + ExprStmt [63-69]: + expr: Expr [63-68]: + ty: Int(None, true) + kind: BinaryOpExpr: + op: Mul + lhs: Expr [63-64]: + ty: Int(None, true) + kind: Cast [0-0]: + ty: Int(None, true) + expr: Expr [63-64]: + ty: Int(Some(32), true) + kind: SymbolId(8) + rhs: Expr [67-68]: + ty: Int(None, true) + kind: SymbolId(9) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_angle.rs b/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_angle.rs new file mode 100644 index 0000000000..7f8c8cdee3 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_angle.rs @@ -0,0 +1,711 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decls; + +#[test] +fn to_bit_implicitly() { + let input = " + angle x = 42.; + bit y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.300888156922483) + [8] Symbol [15-16]: + name: x + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default + ClassicalDeclarationStmt [32-42]: + symbol_id: 9 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Bit(false) + kind: Cast [0-0]: + ty: Bit(false) + expr: Expr [40-41]: + ty: Angle(None, false) + kind: SymbolId(8) + [9] Symbol [36-37]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + "#]], + ); +} + +#[test] +fn explicit_width_to_bit_implicitly_fails() { + let input = " + angle[64] x = 42.; + bit y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-27]: + symbol_id: 8 + ty_span: [9-18] + init_expr: Expr [23-26]: + ty: Angle(Some(64), true) + kind: Lit: Angle(4.300888156922483) + [8] Symbol [19-20]: + name: x + type: Angle(Some(64), false) + qsharp_type: Angle + io_kind: Default + ClassicalDeclarationStmt [36-46]: + symbol_id: 9 + ty_span: [36-39] + init_expr: Expr [44-45]: + ty: Bit(false) + kind: Cast [0-0]: + ty: Bit(false) + expr: Expr [44-45]: + ty: Angle(Some(64), false) + kind: SymbolId(8) + [9] Symbol [40-41]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + "#]], + ); +} + +#[test] +fn to_bool_implicitly() { + let input = " + angle x = 42.; + bool y = x; + "; + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.300888156922483) + [8] Symbol [15-16]: + name: x + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default + ClassicalDeclarationStmt [32-43]: + symbol_id: 9 + ty_span: [32-36] + init_expr: Expr [41-42]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [41-42]: + ty: Angle(None, false) + kind: SymbolId(8) + [9] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_int_implicitly_fails() { + let input = " + angle x = 42.; + int y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-23]: + annotations: + kind: ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.300888156922483) + Stmt [32-42]: + annotations: + kind: ClassicalDeclarationStmt [32-42]: + symbol_id: 9 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Angle(None, false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Angle(None, false) to type Int(None, false) + ,-[test:3:17] + 2 | angle x = 42.; + 3 | int y = x; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn to_explicit_int_implicitly_fails() { + let input = " + angle x = 42.; + int[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-23]: + annotations: + kind: ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.300888156922483) + Stmt [32-46]: + annotations: + kind: ClassicalDeclarationStmt [32-46]: + symbol_id: 9 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Angle(None, false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Angle(None, false) to type Int(Some(32), + | false) + ,-[test:3:21] + 2 | angle x = 42.; + 3 | int[32] y = x; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn to_implicit_uint_implicitly_fails() { + let input = " + angle x = 42.; + uint y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-23]: + annotations: + kind: ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.300888156922483) + Stmt [32-43]: + annotations: + kind: ClassicalDeclarationStmt [32-43]: + symbol_id: 9 + ty_span: [32-36] + init_expr: Expr [41-42]: + ty: Angle(None, false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Angle(None, false) to type UInt(None, + | false) + ,-[test:3:18] + 2 | angle x = 42.; + 3 | uint y = x; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn negative_lit_to_implicit_uint_implicitly_fails() { + let input = " + angle x = -42.; + uint y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-24]: + annotations: + kind: ClassicalDeclarationStmt [9-24]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [20-23]: + ty: Angle(None, false) + kind: Cast [0-0]: + ty: Angle(None, false) + expr: Expr [20-23]: + ty: Float(None, true) + kind: UnaryOpExpr [20-23]: + op: Neg + expr: Expr [20-23]: + ty: Float(None, true) + kind: Lit: Float(42.0) + Stmt [33-44]: + annotations: + kind: ClassicalDeclarationStmt [33-44]: + symbol_id: 9 + ty_span: [33-37] + init_expr: Expr [42-43]: + ty: Angle(None, false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Angle(None, false) to type UInt(None, + | false) + ,-[test:3:18] + 2 | angle x = -42.; + 3 | uint y = x; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn to_explicit_uint_implicitly_fails() { + let input = " + angle x = 42.; + uint[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-23]: + annotations: + kind: ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.300888156922483) + Stmt [32-47]: + annotations: + kind: ClassicalDeclarationStmt [32-47]: + symbol_id: 9 + ty_span: [32-40] + init_expr: Expr [45-46]: + ty: Angle(None, false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Angle(None, false) to type UInt(Some(32), + | false) + ,-[test:3:22] + 2 | angle x = 42.; + 3 | uint[32] y = x; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn to_explicit_bigint_implicitly_fails() { + let input = " + angle x = 42.; + int[65] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-23]: + annotations: + kind: ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.300888156922483) + Stmt [32-46]: + annotations: + kind: ClassicalDeclarationStmt [32-46]: + symbol_id: 9 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Angle(None, false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Angle(None, false) to type Int(Some(65), + | false) + ,-[test:3:21] + 2 | angle x = 42.; + 3 | int[65] y = x; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn to_implicit_float_implicitly_fails() { + let input = " + angle x = 42.; + float y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-23]: + annotations: + kind: ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.300888156922483) + Stmt [32-44]: + annotations: + kind: ClassicalDeclarationStmt [32-44]: + symbol_id: 9 + ty_span: [32-37] + init_expr: Expr [42-43]: + ty: Angle(None, false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Angle(None, false) to type Float(None, + | false) + ,-[test:3:19] + 2 | angle x = 42.; + 3 | float y = x; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn to_explicit_float_implicitly_fails() { + let input = " + angle x = 42.; + float[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-23]: + annotations: + kind: ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.300888156922483) + Stmt [32-48]: + annotations: + kind: ClassicalDeclarationStmt [32-48]: + symbol_id: 9 + ty_span: [32-41] + init_expr: Expr [46-47]: + ty: Angle(None, false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Angle(None, false) to type Float(Some(32), + | false) + ,-[test:3:23] + 2 | angle x = 42.; + 3 | float[32] y = x; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn to_implicit_complex_implicitly_fails() { + let input = " + angle x = 42.; + complex[float] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-23]: + annotations: + kind: ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.300888156922483) + Stmt [32-53]: + annotations: + kind: ClassicalDeclarationStmt [32-53]: + symbol_id: 9 + ty_span: [32-46] + init_expr: Expr [51-52]: + ty: Angle(None, false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Angle(None, false) to type Complex(None, + | false) + ,-[test:3:28] + 2 | angle x = 42.; + 3 | complex[float] y = x; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn to_explicit_complex_implicitly_fails() { + let input = " + angle x = 42.; + complex[float[32]] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-23]: + annotations: + kind: ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.300888156922483) + Stmt [32-57]: + annotations: + kind: ClassicalDeclarationStmt [32-57]: + symbol_id: 9 + ty_span: [32-50] + init_expr: Expr [55-56]: + ty: Angle(None, false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Angle(None, false) to type + | Complex(Some(32), false) + ,-[test:3:32] + 2 | angle x = 42.; + 3 | complex[float[32]] y = x; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn to_angle_implicitly() { + let input = " + angle x = 42.; + angle y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.300888156922483) + [8] Symbol [15-16]: + name: x + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default + ClassicalDeclarationStmt [32-44]: + symbol_id: 9 + ty_span: [32-37] + init_expr: Expr [42-43]: + ty: Angle(None, false) + kind: SymbolId(8) + [9] Symbol [38-39]: + name: y + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_angle_implicitly() { + let input = " + angle x = 42.; + angle[4] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Angle(4.300888156922483) + [8] Symbol [15-16]: + name: x + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 9 + ty_span: [32-40] + init_expr: Expr [45-46]: + ty: Angle(Some(4), false) + kind: Cast [0-0]: + ty: Angle(Some(4), false) + expr: Expr [45-46]: + ty: Angle(None, false) + kind: SymbolId(8) + [9] Symbol [41-42]: + name: y + type: Angle(Some(4), false) + qsharp_type: Angle + io_kind: Default + "#]], + ); +} + +#[test] +fn width_promotion() { + let input = " + angle[32] x = 1.0; + angle[48] y = 2.0; + bit z = x / y; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-27]: + symbol_id: 8 + ty_span: [9-18] + init_expr: Expr [23-26]: + ty: Angle(Some(32), true) + kind: Lit: Angle(1.000000000619646) + [8] Symbol [19-20]: + name: x + type: Angle(Some(32), false) + qsharp_type: Angle + io_kind: Default + ClassicalDeclarationStmt [36-54]: + symbol_id: 9 + ty_span: [36-45] + init_expr: Expr [50-53]: + ty: Angle(Some(48), true) + kind: Lit: Angle(1.999999999999999) + [9] Symbol [46-47]: + name: y + type: Angle(Some(48), false) + qsharp_type: Angle + io_kind: Default + ClassicalDeclarationStmt [63-77]: + symbol_id: 10 + ty_span: [63-66] + init_expr: Expr [71-76]: + ty: Bit(false) + kind: Cast [0-0]: + ty: Bit(false) + expr: Expr [71-76]: + ty: UInt(Some(48), false) + kind: BinaryOpExpr: + op: Div + lhs: Expr [71-72]: + ty: Angle(Some(48), false) + kind: Cast [0-0]: + ty: Angle(Some(48), false) + expr: Expr [71-72]: + ty: Angle(Some(32), false) + kind: SymbolId(8) + rhs: Expr [75-76]: + ty: Angle(Some(48), false) + kind: SymbolId(9) + [10] Symbol [67-68]: + name: z + type: Bit(false) + qsharp_type: Result + io_kind: Default + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_bit.rs b/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_bit.rs new file mode 100644 index 0000000000..a2c855f323 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_bit.rs @@ -0,0 +1,369 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decls; + +#[test] +fn to_angle_implicitly() { + let input = r#" + bit x = 1; + angle y = x; + "#; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [10-20]: + symbol_id: 8 + ty_span: [10-13] + init_expr: Expr [18-19]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [14-15]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [30-42]: + symbol_id: 9 + ty_span: [30-35] + init_expr: Expr [40-41]: + ty: Angle(None, false) + kind: Cast [0-0]: + ty: Angle(None, false) + expr: Expr [40-41]: + ty: Bit(false) + kind: SymbolId(8) + [9] Symbol [36-37]: + name: y + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_angle_implicitly() { + let input = r#" + bit x = 1; + angle[4] y = x; + "#; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [10-20]: + symbol_id: 8 + ty_span: [10-13] + init_expr: Expr [18-19]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [14-15]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [30-45]: + symbol_id: 9 + ty_span: [30-38] + init_expr: Expr [43-44]: + ty: Angle(Some(4), false) + kind: Cast [0-0]: + ty: Angle(Some(4), false) + expr: Expr [43-44]: + ty: Bit(false) + kind: SymbolId(8) + [9] Symbol [39-40]: + name: y + type: Angle(Some(4), false) + qsharp_type: Angle + io_kind: Default + "#]], + ); +} + +#[test] +fn to_bool_implicitly() { + let input = r#" + bit x = 1; + bool y = x; + "#; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [10-20]: + symbol_id: 8 + ty_span: [10-13] + init_expr: Expr [18-19]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [14-15]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [30-41]: + symbol_id: 9 + ty_span: [30-34] + init_expr: Expr [39-40]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [39-40]: + ty: Bit(false) + kind: SymbolId(8) + [9] Symbol [35-36]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_int_implicitly() { + let input = " + bit x = 1; + int y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 9 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [36-37]: + ty: Bit(false) + kind: SymbolId(8) + [9] Symbol [32-33]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_int_implicitly() { + let input = " + bit x = 1; + int[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-42]: + symbol_id: 9 + ty_span: [28-35] + init_expr: Expr [40-41]: + ty: Int(Some(32), false) + kind: Cast [0-0]: + ty: Int(Some(32), false) + expr: Expr [40-41]: + ty: Bit(false) + kind: SymbolId(8) + [9] Symbol [36-37]: + name: y + type: Int(Some(32), false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_uint_implicitly() { + let input = " + bit x = 1; + uint y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-39]: + symbol_id: 9 + ty_span: [28-32] + init_expr: Expr [37-38]: + ty: UInt(None, false) + kind: Cast [0-0]: + ty: UInt(None, false) + expr: Expr [37-38]: + ty: Bit(false) + kind: SymbolId(8) + [9] Symbol [33-34]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_uint_implicitly() { + let input = " + bit x = 1; + uint[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-43]: + symbol_id: 9 + ty_span: [28-36] + init_expr: Expr [41-42]: + ty: UInt(Some(32), false) + kind: Cast [0-0]: + ty: UInt(Some(32), false) + expr: Expr [41-42]: + ty: Bit(false) + kind: SymbolId(8) + [9] Symbol [37-38]: + name: y + type: UInt(Some(32), false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_bigint_implicitly() { + let input = " + bit x = 1; + int[65] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + [8] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-42]: + symbol_id: 9 + ty_span: [28-35] + init_expr: Expr [40-41]: + ty: Int(Some(65), false) + kind: Cast [0-0]: + ty: Int(Some(65), false) + expr: Expr [40-41]: + ty: Bit(false) + kind: SymbolId(8) + [9] Symbol [36-37]: + name: y + type: Int(Some(65), false) + qsharp_type: BigInt + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_float_implicitly_fails() { + let input = " + bit x = 1; + float y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-19]: + annotations: + kind: ClassicalDeclarationStmt [9-19]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Bit(1) + Stmt [28-40]: + annotations: + kind: ClassicalDeclarationStmt [28-40]: + symbol_id: 9 + ty_span: [28-33] + init_expr: Expr [38-39]: + ty: Bit(false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Bit(false) to type Float(None, false) + ,-[test:3:19] + 2 | bit x = 1; + 3 | float y = x; + : ^ + 4 | + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_bitarray.rs b/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_bitarray.rs new file mode 100644 index 0000000000..544b02b7d2 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_bitarray.rs @@ -0,0 +1,379 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decls; + +#[test] +fn to_int_decl_implicitly() { + let input = r#" + bit[5] reg; + int b = reg; + "#; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-15] + init_expr: Expr [0-0]: + ty: BitArray(One(5), true) + kind: Lit: Bitstring("00000") + [8] Symbol [16-19]: + name: reg + type: BitArray(One(5), false) + qsharp_type: Result[] + io_kind: Default + ClassicalDeclarationStmt [29-41]: + symbol_id: 9 + ty_span: [29-32] + init_expr: Expr [37-40]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [37-40]: + ty: BitArray(One(5), false) + kind: SymbolId(8) + [9] Symbol [33-34]: + name: b + type: Int(None, false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_int_assignment_implicitly() { + let input = r#" + bit[5] reg; + int a; + a = reg; + "#; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-15] + init_expr: Expr [0-0]: + ty: BitArray(One(5), true) + kind: Lit: Bitstring("00000") + [8] Symbol [16-19]: + name: reg + type: BitArray(One(5), false) + qsharp_type: Result[] + io_kind: Default + ClassicalDeclarationStmt [29-35]: + symbol_id: 9 + ty_span: [29-32] + init_expr: Expr [0-0]: + ty: Int(None, true) + kind: Lit: Int(0) + [9] Symbol [33-34]: + name: a + type: Int(None, false) + qsharp_type: Int + io_kind: Default + AssignStmt [44-52]: + symbol_id: 9 + lhs_span: [44-45] + rhs: Expr [48-51]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [48-51]: + ty: BitArray(One(5), false) + kind: SymbolId(8) + [9] Symbol [33-34]: + name: a + type: Int(None, false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_int_with_equal_width_in_assignment_implicitly() { + let input = r#" + bit[5] reg; + int[5] a; + a = reg; + "#; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-15] + init_expr: Expr [0-0]: + ty: BitArray(One(5), true) + kind: Lit: Bitstring("00000") + [8] Symbol [16-19]: + name: reg + type: BitArray(One(5), false) + qsharp_type: Result[] + io_kind: Default + ClassicalDeclarationStmt [29-38]: + symbol_id: 9 + ty_span: [29-35] + init_expr: Expr [0-0]: + ty: Int(Some(5), true) + kind: Lit: Int(0) + [9] Symbol [36-37]: + name: a + type: Int(Some(5), false) + qsharp_type: Int + io_kind: Default + AssignStmt [47-55]: + symbol_id: 9 + lhs_span: [47-48] + rhs: Expr [51-54]: + ty: Int(Some(5), false) + kind: Cast [0-0]: + ty: Int(Some(5), false) + expr: Expr [51-54]: + ty: BitArray(One(5), false) + kind: SymbolId(8) + [9] Symbol [36-37]: + name: a + type: Int(Some(5), false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_int_with_equal_width_in_decl_implicitly() { + let input = r#" + bit[5] reg; + int[5] a = reg; + "#; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-15] + init_expr: Expr [0-0]: + ty: BitArray(One(5), true) + kind: Lit: Bitstring("00000") + [8] Symbol [16-19]: + name: reg + type: BitArray(One(5), false) + qsharp_type: Result[] + io_kind: Default + ClassicalDeclarationStmt [29-44]: + symbol_id: 9 + ty_span: [29-35] + init_expr: Expr [40-43]: + ty: Int(Some(5), false) + kind: Cast [0-0]: + ty: Int(Some(5), false) + expr: Expr [40-43]: + ty: BitArray(One(5), false) + kind: SymbolId(8) + [9] Symbol [36-37]: + name: a + type: Int(Some(5), false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_int_with_higher_width_implicitly_fails() { + let input = " + int[6] a; + bit[5] reg; + a = reg; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-18]: + annotations: + kind: ClassicalDeclarationStmt [9-18]: + symbol_id: 8 + ty_span: [9-15] + init_expr: Expr [0-0]: + ty: Int(Some(6), true) + kind: Lit: Int(0) + Stmt [27-38]: + annotations: + kind: ClassicalDeclarationStmt [27-38]: + symbol_id: 9 + ty_span: [27-33] + init_expr: Expr [0-0]: + ty: BitArray(One(5), true) + kind: Lit: Bitstring("00000") + Stmt [47-55]: + annotations: + kind: AssignStmt [47-55]: + symbol_id: 8 + lhs_span: [47-48] + rhs: Expr [51-54]: + ty: BitArray(One(5), false) + kind: SymbolId(9) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type BitArray(One(5), false) to type + | Int(Some(6), false) + ,-[test:4:13] + 3 | bit[5] reg; + 4 | a = reg; + : ^^^ + 5 | + `---- + ]"#]], + ); +} + +#[test] +fn to_int_with_higher_width_decl_implicitly_fails() { + let input = " + bit[5] reg; + int[6] a = reg; + "; + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-20]: + annotations: + kind: ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-15] + init_expr: Expr [0-0]: + ty: BitArray(One(5), true) + kind: Lit: Bitstring("00000") + Stmt [29-44]: + annotations: + kind: ClassicalDeclarationStmt [29-44]: + symbol_id: 9 + ty_span: [29-35] + init_expr: Expr [40-43]: + ty: BitArray(One(5), false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type BitArray(One(5), false) to type + | Int(Some(6), false) + ,-[test:3:20] + 2 | bit[5] reg; + 3 | int[6] a = reg; + : ^^^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn to_int_with_lower_width_implicitly_fails() { + let input = " + input int[4] a; + bit[5] reg; + a = reg; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-24]: + annotations: + kind: InputDeclaration [9-24]: + symbol_id: 8 + Stmt [33-44]: + annotations: + kind: ClassicalDeclarationStmt [33-44]: + symbol_id: 9 + ty_span: [33-39] + init_expr: Expr [0-0]: + ty: BitArray(One(5), true) + kind: Lit: Bitstring("00000") + Stmt [53-61]: + annotations: + kind: AssignStmt [53-61]: + symbol_id: 8 + lhs_span: [53-54] + rhs: Expr [57-60]: + ty: BitArray(One(5), false) + kind: SymbolId(9) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type BitArray(One(5), false) to type + | Int(Some(4), false) + ,-[test:4:13] + 3 | bit[5] reg; + 4 | a = reg; + : ^^^ + 5 | + `---- + ]"#]], + ); +} + +#[test] +fn to_int_with_lower_width_decl_implicitly_fails() { + let input = " + bit[5] reg; + int[4] a = reg; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-20]: + annotations: + kind: ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-15] + init_expr: Expr [0-0]: + ty: BitArray(One(5), true) + kind: Lit: Bitstring("00000") + Stmt [29-44]: + annotations: + kind: ClassicalDeclarationStmt [29-44]: + symbol_id: 9 + ty_span: [29-35] + init_expr: Expr [40-43]: + ty: BitArray(One(5), false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type BitArray(One(5), false) to type + | Int(Some(4), false) + ,-[test:3:20] + 2 | bit[5] reg; + 3 | int[4] a = reg; + : ^^^ + 4 | + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_bool.rs b/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_bool.rs new file mode 100644 index 0000000000..5f444fb295 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_bool.rs @@ -0,0 +1,326 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decls; + +#[test] +fn to_bit_implicitly() { + let input = " + bool x = true; + bit y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-42]: + symbol_id: 9 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Bit(false) + kind: Cast [0-0]: + ty: Bit(false) + expr: Expr [40-41]: + ty: Bool(false) + kind: SymbolId(8) + [9] Symbol [36-37]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_int_implicitly() { + let input = " + bool x = true; + int y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-42]: + symbol_id: 9 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [40-41]: + ty: Bool(false) + kind: SymbolId(8) + [9] Symbol [36-37]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_int_implicitly() { + let input = " + bool x = true; + int[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-46]: + symbol_id: 9 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Int(Some(32), false) + kind: Cast [0-0]: + ty: Int(Some(32), false) + expr: Expr [44-45]: + ty: Bool(false) + kind: SymbolId(8) + [9] Symbol [40-41]: + name: y + type: Int(Some(32), false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_uint_implicitly() { + let input = " + bool x = true; + uint y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-43]: + symbol_id: 9 + ty_span: [32-36] + init_expr: Expr [41-42]: + ty: UInt(None, false) + kind: Cast [0-0]: + ty: UInt(None, false) + expr: Expr [41-42]: + ty: Bool(false) + kind: SymbolId(8) + [9] Symbol [37-38]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_uint_implicitly() { + let input = " + bool x = true; + uint[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 9 + ty_span: [32-40] + init_expr: Expr [45-46]: + ty: UInt(Some(32), false) + kind: Cast [0-0]: + ty: UInt(Some(32), false) + expr: Expr [45-46]: + ty: Bool(false) + kind: SymbolId(8) + [9] Symbol [41-42]: + name: y + type: UInt(Some(32), false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_bigint_implicitly() { + let input = " + bool x = true; + int[65] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-46]: + symbol_id: 9 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Int(Some(65), false) + kind: Cast [0-0]: + ty: Int(Some(65), false) + expr: Expr [44-45]: + ty: Bool(false) + kind: SymbolId(8) + [9] Symbol [40-41]: + name: y + type: Int(Some(65), false) + qsharp_type: BigInt + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_float_implicitly() { + let input = " + bool x = true; + float y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-44]: + symbol_id: 9 + ty_span: [32-37] + init_expr: Expr [42-43]: + ty: Float(None, false) + kind: Cast [0-0]: + ty: Float(None, false) + expr: Expr [42-43]: + ty: Bool(false) + kind: SymbolId(8) + [9] Symbol [38-39]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_float_implicitly() { + let input = " + bool x = true; + float[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(false) + kind: Lit: Bool(true) + [8] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-48]: + symbol_id: 9 + ty_span: [32-41] + init_expr: Expr [46-47]: + ty: Float(Some(32), false) + kind: Cast [0-0]: + ty: Float(Some(32), false) + expr: Expr [46-47]: + ty: Bool(false) + kind: SymbolId(8) + [9] Symbol [42-43]: + name: y + type: Float(Some(32), false) + qsharp_type: Double + io_kind: Default + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_float.rs b/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_float.rs new file mode 100644 index 0000000000..ac7c04da94 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_float.rs @@ -0,0 +1,611 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decls; + +#[test] +fn to_bit_implicitly_fails() { + let input = " + float x = 42.; + bit y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-23]: + annotations: + kind: ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, false) + kind: Lit: Float(42.0) + Stmt [32-42]: + annotations: + kind: ClassicalDeclarationStmt [32-42]: + symbol_id: 9 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Float(None, false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Float(None, false) to type Bit(false) + ,-[test:3:17] + 2 | float x = 42.; + 3 | bit y = x; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn explicit_width_to_bit_implicitly_fails() { + let input = " + float[64] x = 42.; + bit y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-27]: + annotations: + kind: ClassicalDeclarationStmt [9-27]: + symbol_id: 8 + ty_span: [9-18] + init_expr: Expr [23-26]: + ty: Float(Some(64), true) + kind: Lit: Float(42.0) + Stmt [36-46]: + annotations: + kind: ClassicalDeclarationStmt [36-46]: + symbol_id: 9 + ty_span: [36-39] + init_expr: Expr [44-45]: + ty: Float(Some(64), false) + kind: SymbolId(8) + + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Float(Some(64), false) to type Bit(false) + ,-[test:3:17] + 2 | float[64] x = 42.; + 3 | bit y = x; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn to_bool_implicitly() { + let input = " + float x = 42.; + bool y = x; + "; + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, false) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-43]: + symbol_id: 9 + ty_span: [32-36] + init_expr: Expr [41-42]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [41-42]: + ty: Float(None, false) + kind: SymbolId(8) + [9] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_int_implicitly() { + let input = " + float x = 42.; + int y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, false) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-42]: + symbol_id: 9 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [40-41]: + ty: Float(None, false) + kind: SymbolId(8) + [9] Symbol [36-37]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_int_implicitly() { + let input = " + float x = 42.; + int[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, false) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-46]: + symbol_id: 9 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Int(Some(32), false) + kind: Cast [0-0]: + ty: Int(Some(32), false) + expr: Expr [44-45]: + ty: Float(None, false) + kind: SymbolId(8) + [9] Symbol [40-41]: + name: y + type: Int(Some(32), false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_uint_implicitly() { + let input = " + float x = 42.; + uint y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, false) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-43]: + symbol_id: 9 + ty_span: [32-36] + init_expr: Expr [41-42]: + ty: UInt(None, false) + kind: Cast [0-0]: + ty: UInt(None, false) + expr: Expr [41-42]: + ty: Float(None, false) + kind: SymbolId(8) + [9] Symbol [37-38]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn negative_lit_to_implicit_uint_implicitly() { + let input = " + float x = -42.; + uint y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-24]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [20-23]: + ty: Float(None, false) + kind: UnaryOpExpr [20-23]: + op: Neg + expr: Expr [20-23]: + ty: Float(None, true) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [33-44]: + symbol_id: 9 + ty_span: [33-37] + init_expr: Expr [42-43]: + ty: UInt(None, false) + kind: Cast [0-0]: + ty: UInt(None, false) + expr: Expr [42-43]: + ty: Float(None, false) + kind: SymbolId(8) + [9] Symbol [38-39]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_uint_implicitly() { + let input = " + float x = 42.; + uint[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, false) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 9 + ty_span: [32-40] + init_expr: Expr [45-46]: + ty: UInt(Some(32), false) + kind: Cast [0-0]: + ty: UInt(Some(32), false) + expr: Expr [45-46]: + ty: Float(None, false) + kind: SymbolId(8) + [9] Symbol [41-42]: + name: y + type: UInt(Some(32), false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_bigint_implicitly() { + let input = " + float x = 42.; + int[65] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, false) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-46]: + symbol_id: 9 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Int(Some(65), false) + kind: Cast [0-0]: + ty: Int(Some(65), false) + expr: Expr [44-45]: + ty: Float(None, false) + kind: SymbolId(8) + [9] Symbol [40-41]: + name: y + type: Int(Some(65), false) + qsharp_type: BigInt + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_float_implicitly() { + let input = " + float x = 42.; + float y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, false) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-44]: + symbol_id: 9 + ty_span: [32-37] + init_expr: Expr [42-43]: + ty: Float(None, false) + kind: SymbolId(8) + [9] Symbol [38-39]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_float_implicitly() { + let input = " + float x = 42.; + float[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, false) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-48]: + symbol_id: 9 + ty_span: [32-41] + init_expr: Expr [46-47]: + ty: Float(Some(32), false) + kind: Cast [0-0]: + ty: Float(Some(32), false) + expr: Expr [46-47]: + ty: Float(None, false) + kind: SymbolId(8) + [9] Symbol [42-43]: + name: y + type: Float(Some(32), false) + qsharp_type: Double + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_complex_implicitly() { + let input = " + float x = 42.; + complex[float] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, false) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-53]: + symbol_id: 9 + ty_span: [32-46] + init_expr: Expr [51-52]: + ty: Complex(None, false) + kind: Cast [0-0]: + ty: Complex(None, false) + expr: Expr [51-52]: + ty: Float(None, false) + kind: SymbolId(8) + [9] Symbol [47-48]: + name: y + type: Complex(None, false) + qsharp_type: Complex + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_complex_implicitly() { + let input = " + float x = 42.; + complex[float[32]] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, false) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-57]: + symbol_id: 9 + ty_span: [32-50] + init_expr: Expr [55-56]: + ty: Complex(Some(32), false) + kind: Cast [0-0]: + ty: Complex(Some(32), false) + expr: Expr [55-56]: + ty: Float(None, false) + kind: SymbolId(8) + [9] Symbol [51-52]: + name: y + type: Complex(Some(32), false) + qsharp_type: Complex + io_kind: Default + "#]], + ); +} + +#[test] +fn to_angle_implicitly() { + let input = " + float x = 42.; + angle y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, false) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-44]: + symbol_id: 9 + ty_span: [32-37] + init_expr: Expr [42-43]: + ty: Angle(None, false) + kind: Cast [0-0]: + ty: Angle(None, false) + expr: Expr [42-43]: + ty: Float(None, false) + kind: SymbolId(8) + [9] Symbol [38-39]: + name: y + type: Angle(None, false) + qsharp_type: Angle + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_angle_implicitly() { + let input = " + float x = 42.; + angle[4] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, false) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 9 + ty_span: [32-40] + init_expr: Expr [45-46]: + ty: Angle(Some(4), false) + kind: Cast [0-0]: + ty: Angle(Some(4), false) + expr: Expr [45-46]: + ty: Float(None, false) + kind: SymbolId(8) + [9] Symbol [41-42]: + name: y + type: Angle(Some(4), false) + qsharp_type: Angle + io_kind: Default + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_int.rs b/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_int.rs new file mode 100644 index 0000000000..b1e756ab95 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/expression/implicit_cast_from_int.rs @@ -0,0 +1,442 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decls; + +#[test] +fn to_bit_implicitly() { + let input = " + int x = 42; + bit y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, false) + kind: Lit: Int(42) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-39]: + symbol_id: 9 + ty_span: [29-32] + init_expr: Expr [37-38]: + ty: Bit(false) + kind: Cast [0-0]: + ty: Bit(false) + expr: Expr [37-38]: + ty: Int(None, false) + kind: SymbolId(8) + [9] Symbol [33-34]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + "#]], + ); +} + +#[test] +fn to_bool_implicitly() { + let input = " + int x = 42; + bool y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, false) + kind: Lit: Int(42) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 9 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [38-39]: + ty: Int(None, false) + kind: SymbolId(8) + [9] Symbol [34-35]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_int_implicitly() { + let input = " + int x = 42; + int y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, false) + kind: Lit: Int(42) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-39]: + symbol_id: 9 + ty_span: [29-32] + init_expr: Expr [37-38]: + ty: Int(None, false) + kind: SymbolId(8) + [9] Symbol [33-34]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_int_implicitly() { + let input = " + int x = 42; + int[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, false) + kind: Lit: Int(42) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-43]: + symbol_id: 9 + ty_span: [29-36] + init_expr: Expr [41-42]: + ty: Int(Some(32), false) + kind: Cast [0-0]: + ty: Int(Some(32), false) + expr: Expr [41-42]: + ty: Int(None, false) + kind: SymbolId(8) + [9] Symbol [37-38]: + name: y + type: Int(Some(32), false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_uint_implicitly() { + let input = " + int x = 42; + uint y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, false) + kind: Lit: Int(42) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 9 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: UInt(None, false) + kind: Cast [0-0]: + ty: UInt(None, false) + expr: Expr [38-39]: + ty: Int(None, false) + kind: SymbolId(8) + [9] Symbol [34-35]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_uint_implicitly() { + let input = " + int x = 42; + uint[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, false) + kind: Lit: Int(42) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-44]: + symbol_id: 9 + ty_span: [29-37] + init_expr: Expr [42-43]: + ty: UInt(Some(32), false) + kind: Cast [0-0]: + ty: UInt(Some(32), false) + expr: Expr [42-43]: + ty: Int(None, false) + kind: SymbolId(8) + [9] Symbol [38-39]: + name: y + type: UInt(Some(32), false) + qsharp_type: Int + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_bigint_implicitly() { + let input = " + int x = 42; + int[65] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, false) + kind: Lit: Int(42) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-43]: + symbol_id: 9 + ty_span: [29-36] + init_expr: Expr [41-42]: + ty: Int(Some(65), false) + kind: Cast [0-0]: + ty: Int(Some(65), false) + expr: Expr [41-42]: + ty: Int(None, false) + kind: SymbolId(8) + [9] Symbol [37-38]: + name: y + type: Int(Some(65), false) + qsharp_type: BigInt + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_float_implicitly() { + let input = " + int x = 42; + float y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, false) + kind: Lit: Int(42) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-41]: + symbol_id: 9 + ty_span: [29-34] + init_expr: Expr [39-40]: + ty: Float(None, false) + kind: Cast [0-0]: + ty: Float(None, false) + expr: Expr [39-40]: + ty: Int(None, false) + kind: SymbolId(8) + [9] Symbol [35-36]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_float_implicitly() { + let input = " + int x = 42; + float[32] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, false) + kind: Lit: Int(42) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-45]: + symbol_id: 9 + ty_span: [29-38] + init_expr: Expr [43-44]: + ty: Float(Some(32), false) + kind: Cast [0-0]: + ty: Float(Some(32), false) + expr: Expr [43-44]: + ty: Int(None, false) + kind: SymbolId(8) + [9] Symbol [39-40]: + name: y + type: Float(Some(32), false) + qsharp_type: Double + io_kind: Default + "#]], + ); +} + +#[test] +fn to_implicit_complex_implicitly() { + let input = " + int x = 42; + complex[float] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, false) + kind: Lit: Int(42) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-50]: + symbol_id: 9 + ty_span: [29-43] + init_expr: Expr [48-49]: + ty: Complex(None, false) + kind: Cast [0-0]: + ty: Complex(None, false) + expr: Expr [48-49]: + ty: Int(None, false) + kind: SymbolId(8) + [9] Symbol [44-45]: + name: y + type: Complex(None, false) + qsharp_type: Complex + io_kind: Default + "#]], + ); +} + +#[test] +fn to_explicit_complex_implicitly() { + let input = " + int x = 42; + complex[float[32]] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + ClassicalDeclarationStmt [9-20]: + symbol_id: 8 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, false) + kind: Lit: Int(42) + [8] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-54]: + symbol_id: 9 + ty_span: [29-47] + init_expr: Expr [52-53]: + ty: Complex(Some(32), false) + kind: Cast [0-0]: + ty: Complex(Some(32), false) + expr: Expr [52-53]: + ty: Int(None, false) + kind: SymbolId(8) + [9] Symbol [48-49]: + name: y + type: Complex(Some(32), false) + qsharp_type: Complex + io_kind: Default + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/statements.rs b/compiler/qsc_qasm/src/semantic/tests/statements.rs new file mode 100644 index 0000000000..c31fcb3141 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/statements.rs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +mod box_stmt; +mod break_stmt; +mod continue_stmt; +mod for_stmt; +mod if_stmt; +mod switch_stmt; +mod while_stmt; diff --git a/compiler/qsc_qasm/src/semantic/tests/statements/box_stmt.rs b/compiler/qsc_qasm/src/semantic/tests/statements/box_stmt.rs new file mode 100644 index 0000000000..6383192124 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/statements/box_stmt.rs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::semantic::tests::check_stmt_kinds; +use expect_test::expect; + +#[test] +fn with_invalid_instruction_fails() { + check_stmt_kinds( + "box { + 2 + 4; + }", + &expect![[r#" + Program: + version: + statements: + Stmt [0-26]: + annotations: + kind: Err + + [Qasm.Lowerer.ClassicalStmtInBox + + x invalid classical statement in box + ,-[test:2:9] + 1 | box { + 2 | 2 + 4; + : ^^^^^^ + 3 | } + `---- + , Qasm.Lowerer.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: box stmt + ,-[test:1:1] + 1 | ,-> box { + 2 | | 2 + 4; + 3 | `-> } + `---- + ]"#]], + ); +} + +#[test] +fn with_duration_fails() { + check_stmt_kinds( + "box [4us] { }", + &expect![[r#" + Program: + version: + statements: + Stmt [0-13]: + annotations: + kind: Err + + [Qasm.Lowerer.NotSupported + + x Box with duration are not supported + ,-[test:1:6] + 1 | box [4us] { } + : ^^^ + `---- + , Qasm.Lowerer.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: box stmt + ,-[test:1:1] + 1 | box [4us] { } + : ^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/statements/break_stmt.rs b/compiler/qsc_qasm/src/semantic/tests/statements/break_stmt.rs new file mode 100644 index 0000000000..3a5fb1995a --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/statements/break_stmt.rs @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::semantic::tests::check_stmt_kinds; +use expect_test::expect; + +#[test] +fn for_loop() { + check_stmt_kinds( + "for int i in {1, 2, 3} break;", + &expect![[r#" + ForStmt [0-29]: + loop_variable: 8 + iterable: DiscreteSet [13-22]: + values: + Expr [14-15]: + ty: Int(None, true) + kind: Lit: Int(1) + Expr [17-18]: + ty: Int(None, true) + kind: Lit: Int(2) + Expr [20-21]: + ty: Int(None, true) + kind: Lit: Int(3) + body: Stmt [23-29]: + annotations: + kind: BreakStmt [23-29]: + "#]], + ); +} + +#[test] +fn while_loop() { + check_stmt_kinds( + "while (true) break;", + &expect![[r#" + WhileLoop [0-19]: + condition: Expr [7-11]: + ty: Bool(true) + kind: Lit: Bool(true) + body: Stmt [13-19]: + annotations: + kind: BreakStmt [13-19]: + "#]], + ); +} + +#[test] +fn nested_scopes() { + check_stmt_kinds( + "while (true) { { break; } }", + &expect![[r#" + WhileLoop [0-27]: + condition: Expr [7-11]: + ty: Bool(true) + kind: Lit: Bool(true) + body: Stmt [13-27]: + annotations: + kind: Block [13-27]: + Stmt [15-25]: + annotations: + kind: Block [15-25]: + Stmt [17-23]: + annotations: + kind: BreakStmt [17-23]: + "#]], + ); +} + +#[test] +fn break_in_non_loop_scope_fails() { + check_stmt_kinds( + "break;", + &expect![[r#" + Program: + version: + statements: + Stmt [0-6]: + annotations: + kind: Err + + [Qasm.Lowerer.InvalidScope + + x break can only appear in loop scopes + ,-[test:1:1] + 1 | break; + : ^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn intermediate_def_scope_fails() { + check_stmt_kinds( + " + while (true) { + def f() { break; } + } + ", + &expect![[r#" + Program: + version: + statements: + Stmt [9-64]: + annotations: + kind: WhileLoop [9-64]: + condition: Expr [16-20]: + ty: Bool(true) + kind: Lit: Bool(true) + body: Stmt [22-64]: + annotations: + kind: Block [22-64]: + Stmt [36-54]: + annotations: + kind: DefStmt [36-54]: + symbol_id: 8 + has_qubit_params: false + parameters: + return_type: () + body: Block [44-54]: + Stmt [46-52]: + annotations: + kind: Err + + [Qasm.Lowerer.DefDeclarationInNonGlobalScope + + x def declarations must be done in global scope + ,-[test:3:13] + 2 | while (true) { + 3 | def f() { break; } + : ^^^^^^^^^^^^^^^^^^ + 4 | } + `---- + , Qasm.Lowerer.InvalidScope + + x break can only appear in loop scopes + ,-[test:3:23] + 2 | while (true) { + 3 | def f() { break; } + : ^^^^^^ + 4 | } + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/statements/continue_stmt.rs b/compiler/qsc_qasm/src/semantic/tests/statements/continue_stmt.rs new file mode 100644 index 0000000000..9dab4ea4fa --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/statements/continue_stmt.rs @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::semantic::tests::check_stmt_kinds; +use expect_test::expect; + +#[test] +fn for_loop() { + check_stmt_kinds( + "for int i in {1, 2, 3} continue;", + &expect![[r#" + ForStmt [0-32]: + loop_variable: 8 + iterable: DiscreteSet [13-22]: + values: + Expr [14-15]: + ty: Int(None, true) + kind: Lit: Int(1) + Expr [17-18]: + ty: Int(None, true) + kind: Lit: Int(2) + Expr [20-21]: + ty: Int(None, true) + kind: Lit: Int(3) + body: Stmt [23-32]: + annotations: + kind: ContinueStmt [23-32]: + "#]], + ); +} + +#[test] +fn while_loop() { + check_stmt_kinds( + "while (true) continue;", + &expect![[r#" + WhileLoop [0-22]: + condition: Expr [7-11]: + ty: Bool(true) + kind: Lit: Bool(true) + body: Stmt [13-22]: + annotations: + kind: ContinueStmt [13-22]: + "#]], + ); +} + +#[test] +fn nested_scopes() { + check_stmt_kinds( + "while (true) { { continue; } }", + &expect![[r#" + WhileLoop [0-30]: + condition: Expr [7-11]: + ty: Bool(true) + kind: Lit: Bool(true) + body: Stmt [13-30]: + annotations: + kind: Block [13-30]: + Stmt [15-28]: + annotations: + kind: Block [15-28]: + Stmt [17-26]: + annotations: + kind: ContinueStmt [17-26]: + "#]], + ); +} + +#[test] +fn continue_in_non_loop_scope_fails() { + check_stmt_kinds( + "continue;", + &expect![[r#" + Program: + version: + statements: + Stmt [0-9]: + annotations: + kind: Err + + [Qasm.Lowerer.InvalidScope + + x continue can only appear in loop scopes + ,-[test:1:1] + 1 | continue; + : ^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn intermediate_def_scope_fails() { + check_stmt_kinds( + " + while (true) { + def f() { continue; } + } + ", + &expect![[r#" + Program: + version: + statements: + Stmt [9-67]: + annotations: + kind: WhileLoop [9-67]: + condition: Expr [16-20]: + ty: Bool(true) + kind: Lit: Bool(true) + body: Stmt [22-67]: + annotations: + kind: Block [22-67]: + Stmt [36-57]: + annotations: + kind: DefStmt [36-57]: + symbol_id: 8 + has_qubit_params: false + parameters: + return_type: () + body: Block [44-57]: + Stmt [46-55]: + annotations: + kind: Err + + [Qasm.Lowerer.DefDeclarationInNonGlobalScope + + x def declarations must be done in global scope + ,-[test:3:13] + 2 | while (true) { + 3 | def f() { continue; } + : ^^^^^^^^^^^^^^^^^^^^^ + 4 | } + `---- + , Qasm.Lowerer.InvalidScope + + x continue can only appear in loop scopes + ,-[test:3:23] + 2 | while (true) { + 3 | def f() { continue; } + : ^^^^^^^^^ + 4 | } + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/statements/for_stmt.rs b/compiler/qsc_qasm/src/semantic/tests/statements/for_stmt.rs new file mode 100644 index 0000000000..245f84bf67 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/statements/for_stmt.rs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::semantic::tests::check_stmt_kinds; +use expect_test::expect; + +#[test] +fn shadowing_loop_variable_in_single_stmt_body_fails() { + check_stmt_kinds( + " + for int x in {} + int x = 2; + ", + &expect![[r#" + Program: + version: + statements: + Stmt [5-39]: + annotations: + kind: ForStmt [5-39]: + loop_variable: 8 + iterable: DiscreteSet [18-20]: + values: + body: Stmt [29-39]: + annotations: + kind: ClassicalDeclarationStmt [29-39]: + symbol_id: 8 + ty_span: [29-32] + init_expr: Expr [37-38]: + ty: Int(None, false) + kind: Lit: Int(2) + + [Qasm.Lowerer.RedefinedSymbol + + x redefined symbol: x + ,-[test:3:13] + 2 | for int x in {} + 3 | int x = 2; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn shadowing_loop_variable_in_block_body_succeeds() { + check_stmt_kinds( + " + for int x in {} { + int x = 2; + } + ", + &expect![[r#" + ForStmt [5-47]: + loop_variable: 8 + iterable: DiscreteSet [18-20]: + values: + body: Stmt [21-47]: + annotations: + kind: Block [21-47]: + Stmt [31-41]: + annotations: + kind: ClassicalDeclarationStmt [31-41]: + symbol_id: 9 + ty_span: [31-34] + init_expr: Expr [39-40]: + ty: Int(None, false) + kind: Lit: Int(2) + "#]], + ); +} + +#[test] +fn loop_creates_its_own_scope() { + check_stmt_kinds( + " + int a = 0; + for int x in {} + // shadowing works because this + // declaration is in a different + // scope from `int a = 0;` scope. + int a = 1; + ", + &expect![[r#" + ClassicalDeclarationStmt [5-15]: + symbol_id: 8 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, false) + kind: Lit: Int(0) + ForStmt [20-177]: + loop_variable: 9 + iterable: DiscreteSet [33-35]: + values: + body: Stmt [167-177]: + annotations: + kind: ClassicalDeclarationStmt [167-177]: + symbol_id: 10 + ty_span: [167-170] + init_expr: Expr [175-176]: + ty: Int(None, false) + kind: Lit: Int(1) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/statements/if_stmt.rs b/compiler/qsc_qasm/src/semantic/tests/statements/if_stmt.rs new file mode 100644 index 0000000000..c027f578e9 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/statements/if_stmt.rs @@ -0,0 +1,207 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::semantic::tests::check_stmt_kinds; +use expect_test::expect; + +#[test] +fn if_branch_doesnt_create_its_own_scope() { + check_stmt_kinds( + " + int a = 2; + if (true) int a = 1; + ", + &expect![[r#" + Program: + version: + statements: + Stmt [5-15]: + annotations: + kind: ClassicalDeclarationStmt [5-15]: + symbol_id: 8 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, false) + kind: Lit: Int(2) + Stmt [20-40]: + annotations: + kind: IfStmt [20-40]: + condition: Expr [24-28]: + ty: Bool(true) + kind: Lit: Bool(true) + if_body: Stmt [30-40]: + annotations: + kind: ClassicalDeclarationStmt [30-40]: + symbol_id: 8 + ty_span: [30-33] + init_expr: Expr [38-39]: + ty: Int(None, false) + kind: Lit: Int(1) + else_body: + + [Qasm.Lowerer.RedefinedSymbol + + x redefined symbol: a + ,-[test:3:19] + 2 | int a = 2; + 3 | if (true) int a = 1; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn else_branch_doesnt_create_its_own_scope() { + check_stmt_kinds( + " + int a = 2; + if (true) {} + else int a = 1; + ", + &expect![[r#" + Program: + version: + statements: + Stmt [5-15]: + annotations: + kind: ClassicalDeclarationStmt [5-15]: + symbol_id: 8 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, false) + kind: Lit: Int(2) + Stmt [20-52]: + annotations: + kind: IfStmt [20-52]: + condition: Expr [24-28]: + ty: Bool(true) + kind: Lit: Bool(true) + if_body: Stmt [30-32]: + annotations: + kind: Block [30-32]: + else_body: Stmt [42-52]: + annotations: + kind: ClassicalDeclarationStmt [42-52]: + symbol_id: 8 + ty_span: [42-45] + init_expr: Expr [50-51]: + ty: Int(None, false) + kind: Lit: Int(1) + + [Qasm.Lowerer.RedefinedSymbol + + x redefined symbol: a + ,-[test:4:14] + 3 | if (true) {} + 4 | else int a = 1; + : ^ + 5 | + `---- + ]"#]], + ); +} + +#[test] +fn branch_block_creates_a_new_scope() { + check_stmt_kinds( + " + int a = 2; + if (true) { int a = 1; } + ", + &expect![[r#" + ClassicalDeclarationStmt [5-15]: + symbol_id: 8 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, false) + kind: Lit: Int(2) + IfStmt [20-44]: + condition: Expr [24-28]: + ty: Bool(true) + kind: Lit: Bool(true) + if_body: Stmt [30-44]: + annotations: + kind: Block [30-44]: + Stmt [32-42]: + annotations: + kind: ClassicalDeclarationStmt [32-42]: + symbol_id: 9 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Int(None, false) + kind: Lit: Int(1) + else_body: + "#]], + ); +} + +#[test] +fn if_scope_and_else_scope_are_different() { + check_stmt_kinds( + " + int a = 2; + if (true) { int a = 1; } + else { int a = 2; } + ", + &expect![[r#" + ClassicalDeclarationStmt [5-15]: + symbol_id: 8 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, false) + kind: Lit: Int(2) + IfStmt [20-68]: + condition: Expr [24-28]: + ty: Bool(true) + kind: Lit: Bool(true) + if_body: Stmt [30-44]: + annotations: + kind: Block [30-44]: + Stmt [32-42]: + annotations: + kind: ClassicalDeclarationStmt [32-42]: + symbol_id: 9 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Int(None, false) + kind: Lit: Int(1) + else_body: Stmt [54-68]: + annotations: + kind: Block [54-68]: + Stmt [56-66]: + annotations: + kind: ClassicalDeclarationStmt [56-66]: + symbol_id: 10 + ty_span: [56-59] + init_expr: Expr [64-65]: + ty: Int(None, false) + kind: Lit: Int(2) + "#]], + ); +} + +#[test] +fn condition_cast() { + check_stmt_kinds( + "if (1) true;", + &expect![[r#" + IfStmt [0-12]: + condition: Expr [4-5]: + ty: Bool(true) + kind: Cast [0-0]: + ty: Bool(true) + expr: Expr [4-5]: + ty: Int(None, true) + kind: Lit: Int(1) + if_body: Stmt [7-12]: + annotations: + kind: ExprStmt [7-12]: + expr: Expr [7-11]: + ty: Bool(true) + kind: Lit: Bool(true) + else_body: + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/statements/switch_stmt.rs b/compiler/qsc_qasm/src/semantic/tests/statements/switch_stmt.rs new file mode 100644 index 0000000000..dcc4463b00 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/statements/switch_stmt.rs @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::semantic::tests::check_stmt_kinds; +use expect_test::expect; + +#[test] +fn not_supported_before_version_3_1() { + check_stmt_kinds( + r#" + OPENQASM 3.0; + switch (1) { case 1 {} } + "#, + &expect![[r#" + Program: + version: 3.0 + statements: + Stmt [23-47]: + annotations: + kind: SwitchStmt [23-47]: + target: Expr [31-32]: + ty: Int(None, true) + kind: Lit: Int(1) + cases: + SwitchCase [36-45]: + labels: + Expr [41-42]: + ty: Int(None, true) + kind: Lit: Int(1) + block: Block [43-45]: + default_case: + + [Qasm.Lowerer.NotSupportedInThisVersion + + x switch statements were introduced in version 3.1 + ,-[test:3:5] + 2 | OPENQASM 3.0; + 3 | switch (1) { case 1 {} } + : ^^^^^^^^^^^^^^^^^^^^^^^^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn cases_introduce_their_own_scope() { + check_stmt_kinds( + r#" + int a = 3; + switch (1) { + case 1 { int a = 1; } + case 2, 3 { int a = 2; } + } + "#, + &expect![[r#" + ClassicalDeclarationStmt [5-15]: + symbol_id: 8 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, false) + kind: Lit: Int(3) + SwitchStmt [20-101]: + target: Expr [28-29]: + ty: Int(None, true) + kind: Lit: Int(1) + cases: + SwitchCase [41-62]: + labels: + Expr [46-47]: + ty: Int(None, true) + kind: Lit: Int(1) + block: Block [48-62]: + Stmt [50-60]: + annotations: + kind: ClassicalDeclarationStmt [50-60]: + symbol_id: 9 + ty_span: [50-53] + init_expr: Expr [58-59]: + ty: Int(None, false) + kind: Lit: Int(1) + SwitchCase [71-95]: + labels: + Expr [76-77]: + ty: Int(None, true) + kind: Lit: Int(2) + Expr [79-80]: + ty: Int(None, true) + kind: Lit: Int(3) + block: Block [81-95]: + Stmt [83-93]: + annotations: + kind: ClassicalDeclarationStmt [83-93]: + symbol_id: 10 + ty_span: [83-86] + init_expr: Expr [91-92]: + ty: Int(None, false) + kind: Lit: Int(2) + default_case: + "#]], + ); +} + +#[test] +fn target_cast() { + check_stmt_kinds( + "switch (true) { case false {} }", + &expect![[r#" + SwitchStmt [0-31]: + target: Expr [8-12]: + ty: Int(None, true) + kind: Cast [0-0]: + ty: Int(None, true) + expr: Expr [8-12]: + ty: Bool(true) + kind: Lit: Bool(true) + cases: + SwitchCase [16-29]: + labels: + Expr [21-26]: + ty: Int(None, true) + kind: Cast [0-0]: + ty: Int(None, true) + expr: Expr [21-26]: + ty: Bool(true) + kind: Lit: Bool(false) + block: Block [27-29]: + default_case: + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/tests/statements/while_stmt.rs b/compiler/qsc_qasm/src/semantic/tests/statements/while_stmt.rs new file mode 100644 index 0000000000..bf8d92833f --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/tests/statements/while_stmt.rs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::semantic::tests::check_stmt_kinds; +use expect_test::expect; + +#[test] +fn single_stmt_body_creates_its_own_scope() { + check_stmt_kinds( + " + int a = 3; + while(true) int a = 1; + ", + &expect![[r#" + ClassicalDeclarationStmt [5-15]: + symbol_id: 8 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, false) + kind: Lit: Int(3) + WhileLoop [20-42]: + condition: Expr [26-30]: + ty: Bool(true) + kind: Lit: Bool(true) + body: Stmt [32-42]: + annotations: + kind: ClassicalDeclarationStmt [32-42]: + symbol_id: 9 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Int(None, false) + kind: Lit: Int(1) + "#]], + ); +} + +#[test] +fn block_body_creates_its_own_scope() { + check_stmt_kinds( + " + int a = 3; + while(true) { int a = 1; } + ", + &expect![[r#" + ClassicalDeclarationStmt [5-15]: + symbol_id: 8 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, false) + kind: Lit: Int(3) + WhileLoop [20-46]: + condition: Expr [26-30]: + ty: Bool(true) + kind: Lit: Bool(true) + body: Stmt [32-46]: + annotations: + kind: Block [32-46]: + Stmt [34-44]: + annotations: + kind: ClassicalDeclarationStmt [34-44]: + symbol_id: 9 + ty_span: [34-37] + init_expr: Expr [42-43]: + ty: Int(None, false) + kind: Lit: Int(1) + "#]], + ); +} + +#[test] +fn condition_cast() { + check_stmt_kinds( + "while (1) true;", + &expect![[r#" + WhileLoop [0-15]: + condition: Expr [7-8]: + ty: Bool(true) + kind: Cast [0-0]: + ty: Bool(true) + expr: Expr [7-8]: + ty: Int(None, true) + kind: Lit: Int(1) + body: Stmt [10-15]: + annotations: + kind: ExprStmt [10-15]: + expr: Expr [10-14]: + ty: Bool(true) + kind: Lit: Bool(true) + "#]], + ); +} diff --git a/compiler/qsc_qasm/src/semantic/types.rs b/compiler/qsc_qasm/src/semantic/types.rs new file mode 100644 index 0000000000..7373338207 --- /dev/null +++ b/compiler/qsc_qasm/src/semantic/types.rs @@ -0,0 +1,791 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::{cmp::max, rc::Rc}; + +use core::fmt; +use std::fmt::{Display, Formatter}; + +use crate::parser::ast as syntax; + +use super::ast::LiteralKind; + +#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] +pub enum Type { + // scalar types + Bit(bool), + Bool(bool), + Duration(bool), + Stretch(bool), + + Angle(Option, bool), + Complex(Option, bool), + Float(Option, bool), + Int(Option, bool), + UInt(Option, bool), + + // quantum + Qubit, + HardwareQubit, + + // magic arrays + BitArray(ArrayDimensions, bool), + QubitArray(ArrayDimensions), + + // proper arrays + BoolArray(ArrayDimensions), + DurationArray(ArrayDimensions), + AngleArray(Option, ArrayDimensions), + ComplexArray(Option, ArrayDimensions), + FloatArray(Option, ArrayDimensions), + IntArray(Option, ArrayDimensions), + UIntArray(Option, ArrayDimensions), + + // realistically the sizes could be u3 + Gate(u32, u32), + Function(Rc<[Type]>, Rc), + Range, + Set, + Void, + #[default] + Err, +} + +impl Display for Type { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Type::Bit(is_const) => write!(f, "Bit({is_const})"), + Type::Bool(is_const) => write!(f, "Bool({is_const})"), + Type::Duration(is_const) => write!(f, "Duration({is_const})"), + Type::Stretch(is_const) => write!(f, "Stretch({is_const})"), + Type::Angle(width, is_const) => write!(f, "Angle({width:?}, {is_const})"), + Type::Complex(width, is_const) => write!(f, "Complex({width:?}, {is_const})"), + Type::Float(width, is_const) => write!(f, "Float({width:?}, {is_const})"), + Type::Int(width, is_const) => write!(f, "Int({width:?}, {is_const})"), + Type::UInt(width, is_const) => write!(f, "UInt({width:?}, {is_const})"), + Type::Qubit => write!(f, "Qubit"), + Type::HardwareQubit => write!(f, "HardwareQubit"), + Type::BitArray(dims, is_const) => write!(f, "BitArray({dims:?}, {is_const})"), + Type::QubitArray(dims) => write!(f, "QubitArray({dims:?})"), + Type::BoolArray(dims) => write!(f, "BoolArray({dims:?})"), + Type::DurationArray(dims) => write!(f, "DurationArray({dims:?})"), + Type::AngleArray(width, dims) => write!(f, "AngleArray({width:?}, {dims:?})"), + Type::ComplexArray(width, dims) => write!(f, "ComplexArray({width:?}, {dims:?})"), + Type::FloatArray(width, dims) => write!(f, "FloatArray({width:?}, {dims:?})"), + Type::IntArray(width, dims) => write!(f, "IntArray({width:?}, {dims:?})"), + Type::UIntArray(width, dims) => write!(f, "UIntArray({width:?}, {dims:?})"), + Type::Gate(cargs, qargs) => write!(f, "Gate({cargs}, {qargs})"), + Type::Function(params_ty, return_ty) => { + write!(f, "Function({params_ty:?}) -> {return_ty:?}") + } + Type::Range => write!(f, "Range"), + Type::Set => write!(f, "Set"), + Type::Void => write!(f, "Void"), + Type::Err => write!(f, "Err"), + } + } +} + +impl Type { + #[must_use] + pub fn is_array(&self) -> bool { + matches!( + self, + Type::AngleArray(..) + | Type::BitArray(..) + | Type::BoolArray(..) + | Type::ComplexArray(..) + | Type::DurationArray(..) + | Type::FloatArray(..) + | Type::IntArray(..) + | Type::QubitArray(..) + | Type::UIntArray(..) + ) + } + + #[must_use] + pub fn is_const(&self) -> bool { + match self { + Type::BitArray(_, is_const) + | Type::Bit(is_const) + | Type::Bool(is_const) + | Type::Duration(is_const) + | Type::Stretch(is_const) + | Type::Angle(_, is_const) + | Type::Complex(_, is_const) + | Type::Float(_, is_const) + | Type::Int(_, is_const) + | Type::UInt(_, is_const) => *is_const, + _ => false, + } + } + + #[must_use] + pub fn width(&self) -> Option { + match self { + Type::Angle(w, _) + | Type::Complex(w, _) + | Type::Float(w, _) + | Type::Int(w, _) + | Type::UInt(w, _) => *w, + _ => None, + } + } + + #[must_use] + pub fn is_inferred_output_type(&self) -> bool { + matches!( + self, + Type::Bit(_) + | Type::Int(_, _) + | Type::UInt(_, _) + | Type::Float(_, _) + | Type::Angle(_, _) + | Type::Complex(_, _) + | Type::Bool(_) + | Type::BitArray(_, _) + | Type::IntArray(_, _) + | Type::UIntArray(_, _) + | Type::FloatArray(_, _) + | Type::AngleArray(_, _) + | Type::ComplexArray(_, _) + | Type::BoolArray(_) + | Type::Range + | Type::Set + ) + } + + #[must_use] + pub fn num_dims(&self) -> usize { + match self { + Type::AngleArray(_, dims) + | Type::BitArray(dims, _) + | Type::BoolArray(dims) + | Type::DurationArray(dims) + | Type::ComplexArray(_, dims) + | Type::FloatArray(_, dims) + | Type::IntArray(_, dims) + | Type::QubitArray(dims) + | Type::UIntArray(_, dims) => dims.num_dims(), + _ => 0, + } + } + + /// Get the indexed type of a given type. + /// For example, if the type is `Int[2][3]`, the indexed type is `Int[2]`. + /// If the type is `Int[2]`, the indexed type is `Int`. + /// If the type is `Int`, the indexed type is `None`. + /// + /// This is useful for determining the type of an array element. + #[allow(clippy::too_many_lines)] + #[must_use] + pub fn get_indexed_type(&self) -> Option { + let ty = match self { + Type::BitArray(dims, is_const) => indexed_type_builder( + || Type::Bit(*is_const), + |d| Type::BitArray(d, *is_const), + dims, + ), + Type::QubitArray(dims) => indexed_type_builder(|| Type::Qubit, Type::QubitArray, dims), + Type::BoolArray(dims) => { + indexed_type_builder(|| Type::Bool(false), Type::BoolArray, dims) + } + Type::AngleArray(size, dims) => indexed_type_builder( + || Type::Angle(*size, false), + |d| Type::AngleArray(*size, d), + dims, + ), + Type::ComplexArray(size, dims) => indexed_type_builder( + || Type::Complex(*size, false), + |d| Type::ComplexArray(*size, d), + dims, + ), + Type::DurationArray(dims) => { + indexed_type_builder(|| Type::Duration(false), Type::DurationArray, dims) + } + Type::FloatArray(size, dims) => indexed_type_builder( + || Type::Float(*size, false), + |d| Type::FloatArray(*size, d), + dims, + ), + Type::IntArray(size, dims) => indexed_type_builder( + || Type::Int(*size, false), + |d| Type::IntArray(*size, d), + dims, + ), + Type::UIntArray(size, dims) => indexed_type_builder( + || Type::UInt(*size, false), + |d| Type::UIntArray(*size, d), + dims, + ), + _ => return None, + }; + Some(ty) + } + + pub(crate) fn as_const(&self) -> Type { + match self { + Type::Bit(_) => Self::Bit(true), + Type::Bool(_) => Self::Bool(true), + Type::Duration(_) => Self::Duration(true), + Type::Stretch(_) => Self::Stretch(true), + Type::Angle(w, _) => Self::Angle(*w, true), + Type::Complex(w, _) => Self::Complex(*w, true), + Type::Float(w, _) => Self::Float(*w, true), + Type::Int(w, _) => Self::Int(*w, true), + Type::UInt(w, _) => Self::UInt(*w, true), + Type::BitArray(dims, _) => Self::BitArray(dims.clone(), true), + _ => self.clone(), + } + } + + pub(crate) fn as_non_const(&self) -> Type { + match self { + Type::Bit(_) => Self::Bit(false), + Type::Bool(_) => Self::Bool(false), + Type::Duration(_) => Self::Duration(false), + Type::Stretch(_) => Self::Stretch(false), + Type::Angle(w, _) => Self::Angle(*w, false), + Type::Complex(w, _) => Self::Complex(*w, false), + Type::Float(w, _) => Self::Float(*w, false), + Type::Int(w, _) => Self::Int(*w, false), + Type::UInt(w, _) => Self::UInt(*w, false), + Type::BitArray(dims, _) => Self::BitArray(dims.clone(), false), + _ => self.clone(), + } + } + + pub(crate) fn is_quantum(&self) -> bool { + matches!( + self, + Type::HardwareQubit | Type::Qubit | Type::QubitArray(_) + ) + } +} + +fn indexed_type_builder( + ty: impl Fn() -> Type, + ty_array: impl Fn(ArrayDimensions) -> Type, + dims: &ArrayDimensions, +) -> Type { + match dims.clone() { + ArrayDimensions::One(_) => ty(), + ArrayDimensions::Two(d1, _) => ty_array(ArrayDimensions::One(d1)), + ArrayDimensions::Three(d1, d2, _) => ty_array(ArrayDimensions::Two(d1, d2)), + ArrayDimensions::Four(d1, d2, d3, _) => ty_array(ArrayDimensions::Three(d1, d2, d3)), + ArrayDimensions::Five(d1, d2, d3, d4, _) => ty_array(ArrayDimensions::Four(d1, d2, d3, d4)), + ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { + ty_array(ArrayDimensions::Five(d1, d2, d3, d4, d5)) + } + ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { + ty_array(ArrayDimensions::Six(d1, d2, d3, d4, d5, d6)) + } + ArrayDimensions::Err => Type::Err, + } +} + +#[derive(Debug, Clone, Default, Eq, Hash, PartialEq)] +pub enum ArrayDimensions { + One(u32), + Two(u32, u32), + Three(u32, u32, u32), + Four(u32, u32, u32, u32), + Five(u32, u32, u32, u32, u32), + Six(u32, u32, u32, u32, u32, u32), + Seven(u32, u32, u32, u32, u32, u32, u32), + #[default] + Err, +} + +impl From for ArrayDimensions { + fn from(value: u32) -> Self { + Self::One(value) + } +} + +impl Display for ArrayDimensions { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + ArrayDimensions::One(..) => write!(f, "[]"), + ArrayDimensions::Two(..) => write!(f, "[][]"), + ArrayDimensions::Three(..) => write!(f, "[][][]"), + ArrayDimensions::Four(..) => write!(f, "[][][][]"), + ArrayDimensions::Five(..) => write!(f, "[][][][][]"), + ArrayDimensions::Six(..) => write!(f, "[][][][][][]"), + ArrayDimensions::Seven(..) => write!(f, "[][][][][][][]"), + ArrayDimensions::Err => write!(f, "Invalid array dimensions"), + } + } +} + +impl ArrayDimensions { + #[must_use] + pub fn num_dims(&self) -> usize { + match self { + ArrayDimensions::One(_) => 1, + ArrayDimensions::Two(_, _) => 2, + ArrayDimensions::Three(_, _, _) => 3, + ArrayDimensions::Four(_, _, _, _) => 4, + ArrayDimensions::Five(_, _, _, _, _) => 5, + ArrayDimensions::Six(_, _, _, _, _, _) => 6, + ArrayDimensions::Seven(_, _, _, _, _, _, _) => 7, + ArrayDimensions::Err => 0, + } + } +} + +/// When two types are combined, the result is a type that can represent both. +/// For constness, the result is const iff both types are const. +#[must_use] +pub fn relax_constness(lhs_ty: &Type, rhs_ty: &Type) -> bool { + lhs_ty.is_const() && rhs_ty.is_const() +} + +/// Having no width means that the type is not a fixed-width type +/// and can hold any explicit width. If both types have a width, +/// the result is the maximum of the two. Otherwise, the result +/// is a type without a width. +#[must_use] +pub fn promote_width(lhs_ty: &Type, rhs_ty: &Type) -> Option { + match (lhs_ty.width(), rhs_ty.width()) { + (Some(w1), Some(w2)) => Some(max(w1, w2)), + (Some(_) | None, None) | (None, Some(_)) => None, + } +} + +fn get_effective_width(lhs_ty: &Type, rhs_ty: &Type) -> Option { + match (lhs_ty.width(), rhs_ty.width()) { + (Some(w1), Some(w2)) => Some(max(w1, w2)), + (Some(w), None) | (None, Some(w)) => Some(w), + (None, None) => None, + } +} + +/// If both can be promoted to a common type, the result is that type. +/// If the types are not compatible, the result is `Type::Void`. +#[must_use] +pub fn promote_types(lhs_ty: &Type, rhs_ty: &Type) -> Type { + if types_equal_except_const(lhs_ty, rhs_ty) { + return lhs_ty.clone(); + } + let ty = promote_types_symmetric(lhs_ty, rhs_ty); + if ty != Type::Void { + return ty; + } + let ty = promote_types_asymmetric(lhs_ty, rhs_ty); + if ty == Type::Void { + return promote_types_asymmetric(rhs_ty, lhs_ty); + } + ty +} + +pub(crate) fn promote_to_uint_ty( + lhs_ty: &Type, + rhs_ty: &Type, +) -> (Option, Option, Option) { + let is_const = relax_constness(lhs_ty, rhs_ty); + let lhs_ty = get_uint_ty(lhs_ty); + let rhs_ty = get_uint_ty(rhs_ty); + match (lhs_ty, rhs_ty) { + (Some(lhs_ty), Some(rhs_ty)) => { + let width = get_effective_width(&lhs_ty, &rhs_ty); + ( + Some(Type::UInt(width, is_const)), + Some(lhs_ty), + Some(rhs_ty), + ) + } + (Some(lhs_ty), None) => (None, Some(lhs_ty), None), + (None, Some(rhs_ty)) => (None, None, Some(rhs_ty)), + (None, None) => (None, None, None), + } +} + +fn get_uint_ty(ty: &Type) -> Option { + if matches!(ty, Type::Int(..) | Type::UInt(..) | Type::Angle(..)) { + Some(Type::UInt(ty.width(), ty.is_const())) + } else if matches!(ty, Type::Bool(..) | Type::Bit(..)) { + Some(Type::UInt(Some(1), ty.is_const())) + } else if let Type::BitArray(dims, _) = ty { + match dims { + ArrayDimensions::One(d) => Some(Type::UInt(Some(*d), ty.is_const())), + _ => None, + } + } else { + None + } +} + +/// Promotes two types if they share a common base type with +/// their constness relaxed, and their width promoted. +/// If the types are not compatible, the result is `Type::Void`. +fn promote_types_symmetric(lhs_ty: &Type, rhs_ty: &Type) -> Type { + let is_const = relax_constness(lhs_ty, rhs_ty); + match (lhs_ty, rhs_ty) { + (Type::Bit(..), Type::Bit(..)) => Type::Bit(is_const), + (Type::Bool(..), Type::Bool(..)) => Type::Bool(is_const), + (Type::Int(..), Type::Int(..)) => Type::Int(promote_width(lhs_ty, rhs_ty), is_const), + (Type::UInt(..), Type::UInt(..)) => Type::UInt(promote_width(lhs_ty, rhs_ty), is_const), + (Type::Angle(..), Type::Angle(..)) => Type::Angle(promote_width(lhs_ty, rhs_ty), is_const), + (Type::Float(..), Type::Float(..)) => Type::Float(promote_width(lhs_ty, rhs_ty), is_const), + (Type::Complex(..), Type::Complex(..)) => { + Type::Complex(promote_width(lhs_ty, rhs_ty), is_const) + } + _ => Type::Void, + } +} + +/// Promotion follows casting rules. We only match one way, as the +/// both combinations are covered by calling this function twice +/// with the arguments swapped. +/// +/// If the types are not compatible, the result is `Type::Void`. +/// +/// The left-hand side is the type to promote from, and the right-hand +/// side is the type to promote to. So any promotion goes from lesser +/// type to greater type. +/// +/// This is more complicated as we have C99 promotion for simple types, +/// but complex types like `Complex`, and `Angle` don't follow those rules. +fn promote_types_asymmetric(lhs_ty: &Type, rhs_ty: &Type) -> Type { + let is_const = relax_constness(lhs_ty, rhs_ty); + #[allow(clippy::match_same_arms)] + match (lhs_ty, rhs_ty) { + (Type::Bit(..), Type::Bool(..)) => Type::Bool(is_const), + (Type::Bit(..), Type::Int(w, _)) => Type::Int(*w, is_const), + (Type::Bit(..), Type::UInt(w, _)) => Type::UInt(*w, is_const), + + (Type::Bit(..), Type::Angle(w, _)) => Type::Angle(*w, is_const), + + (Type::Bool(..), Type::Int(w, _)) => Type::Int(*w, is_const), + (Type::Bool(..), Type::UInt(w, _)) => Type::UInt(*w, is_const), + (Type::Bool(..), Type::Float(w, _)) => Type::Float(*w, is_const), + (Type::Bool(..), Type::Complex(w, _)) => Type::Complex(*w, is_const), + + (Type::UInt(..), Type::Int(..)) => Type::Int(promote_width(lhs_ty, rhs_ty), is_const), + (Type::UInt(..), Type::Float(..)) => Type::Float(promote_width(lhs_ty, rhs_ty), is_const), + (Type::UInt(..), Type::Complex(..)) => { + Type::Complex(promote_width(lhs_ty, rhs_ty), is_const) + } + + (Type::Int(..), Type::Float(..)) => Type::Float(promote_width(lhs_ty, rhs_ty), is_const), + (Type::Int(..), Type::Complex(..)) => { + Type::Complex(promote_width(lhs_ty, rhs_ty), is_const) + } + (Type::Angle(..), Type::Float(..)) => Type::Float(promote_width(lhs_ty, rhs_ty), is_const), + (Type::Float(..), Type::Complex(..)) => { + Type::Complex(promote_width(lhs_ty, rhs_ty), is_const) + } + _ => Type::Void, + } +} + +/// Compares two types for equality, ignoring constness. +pub(crate) fn types_equal_except_const(lhs: &Type, rhs: &Type) -> bool { + match (lhs, rhs) { + (Type::Bit(_), Type::Bit(_)) + | (Type::Qubit, Type::Qubit) + | (Type::HardwareQubit, Type::HardwareQubit) + | (Type::Bool(_), Type::Bool(_)) + | (Type::Duration(_), Type::Duration(_)) + | (Type::Stretch(_), Type::Stretch(_)) + | (Type::Range, Type::Range) + | (Type::Set, Type::Set) + | (Type::Void, Type::Void) + | (Type::Err, Type::Err) => true, + (Type::Int(lhs_width, _), Type::Int(rhs_width, _)) + | (Type::UInt(lhs_width, _), Type::UInt(rhs_width, _)) + | (Type::Float(lhs_width, _), Type::Float(rhs_width, _)) + | (Type::Angle(lhs_width, _), Type::Angle(rhs_width, _)) + | (Type::Complex(lhs_width, _), Type::Complex(rhs_width, _)) => lhs_width == rhs_width, + (Type::BitArray(lhs_dims, _), Type::BitArray(rhs_dims, _)) + | (Type::BoolArray(lhs_dims), Type::BoolArray(rhs_dims)) + | (Type::QubitArray(lhs_dims), Type::QubitArray(rhs_dims)) => lhs_dims == rhs_dims, + (Type::IntArray(lhs_width, lhs_dims), Type::IntArray(rhs_width, rhs_dims)) + | (Type::UIntArray(lhs_width, lhs_dims), Type::UIntArray(rhs_width, rhs_dims)) + | (Type::FloatArray(lhs_width, lhs_dims), Type::FloatArray(rhs_width, rhs_dims)) + | (Type::AngleArray(lhs_width, lhs_dims), Type::AngleArray(rhs_width, rhs_dims)) + | (Type::ComplexArray(lhs_width, lhs_dims), Type::ComplexArray(rhs_width, rhs_dims)) => { + lhs_width == rhs_width && lhs_dims == rhs_dims + } + (Type::Gate(lhs_cargs, lhs_qargs), Type::Gate(rhs_cargs, rhs_qargs)) => { + lhs_cargs == rhs_cargs && lhs_qargs == rhs_qargs + } + _ => false, + } +} + +/// Compares two types for equality, ignoring constness and width. +/// arrays are equal if their dimensions are equal. +pub(crate) fn base_types_equal(lhs: &Type, rhs: &Type) -> bool { + match (lhs, rhs) { + (Type::Bit(_), Type::Bit(_)) + | (Type::Qubit, Type::Qubit) + | (Type::HardwareQubit, Type::HardwareQubit) + | (Type::Bool(_), Type::Bool(_)) + | (Type::Duration(_), Type::Duration(_)) + | (Type::Stretch(_), Type::Stretch(_)) + | (Type::Range, Type::Range) + | (Type::Set, Type::Set) + | (Type::Void, Type::Void) + | (Type::Err, Type::Err) + | (Type::Int(_, _), Type::Int(_, _)) + | (Type::UInt(_, _), Type::UInt(_, _)) + | (Type::Float(_, _), Type::Float(_, _)) + | (Type::Angle(_, _), Type::Angle(_, _)) + | (Type::Complex(_, _), Type::Complex(_, _)) + | (Type::Gate(_, _), Type::Gate(_, _)) => true, + (Type::BitArray(lhs_dims, _), Type::BitArray(rhs_dims, _)) + | (Type::BoolArray(lhs_dims), Type::BoolArray(rhs_dims)) + | (Type::QubitArray(lhs_dims), Type::QubitArray(rhs_dims)) => lhs_dims == rhs_dims, + (Type::IntArray(_, lhs_dims), Type::IntArray(_, rhs_dims)) + | (Type::UIntArray(_, lhs_dims), Type::UIntArray(_, rhs_dims)) + | (Type::FloatArray(_, lhs_dims), Type::FloatArray(_, rhs_dims)) + | (Type::AngleArray(_, lhs_dims), Type::AngleArray(_, rhs_dims)) + | (Type::ComplexArray(_, lhs_dims), Type::ComplexArray(_, rhs_dims)) => { + lhs_dims == rhs_dims + } + _ => false, + } +} + +#[must_use] +pub fn can_cast_literal(lhs_ty: &Type, ty_lit: &Type) -> bool { + // todo: not sure if this top case is still needed after parser changes + if matches!(lhs_ty, Type::Int(..)) && matches!(ty_lit, Type::UInt(..)) { + return true; + } + // todo: not sure if this case is still needed after parser changes + if matches!(lhs_ty, Type::UInt(..)) { + return matches!(ty_lit, Type::Complex(..)); + } + + base_types_equal(lhs_ty, ty_lit) + || matches!( + (lhs_ty, ty_lit), + (Type::Angle(_, _), Type::Float(_, _) | Type::Bit(..)) + ) + || matches!((lhs_ty, ty_lit), (Type::Bit(..), Type::Angle(..))) + || matches!( + (lhs_ty, ty_lit), + ( + Type::Float(_, _) | Type::Complex(_, _), + Type::Int(_, _) | Type::UInt(_, _) + ) | (Type::Complex(_, _), Type::Float(_, _)) + ) + || { + matches!(lhs_ty, Type::Bit(..) | Type::Bool(..)) + && matches!(ty_lit, Type::Bit(..) | Type::Bool(..)) + } + || { + match lhs_ty { + Type::BitArray(dims, _) => { + matches!(dims, ArrayDimensions::One(_)) + && matches!(ty_lit, Type::Int(_, _) | Type::UInt(_, _)) + } + _ => false, + } + } +} + +/// some literals can be cast to a specific type if the value is known +/// This is useful to avoid generating a cast expression in the AST +pub(crate) fn can_cast_literal_with_value_knowledge(lhs_ty: &Type, kind: &LiteralKind) -> bool { + if matches!(lhs_ty, &Type::Bit(_)) { + if let LiteralKind::Int(value) = kind { + return *value == 0 || *value == 1; + } + } + if matches!(lhs_ty, &Type::UInt(..)) { + if let LiteralKind::Int(value) = kind { + return *value >= 0; + } + } + false +} + +// https://openqasm.com/language/classical.html +pub(crate) fn unary_op_can_be_applied_to_type(op: syntax::UnaryOp, ty: &Type) -> bool { + match op { + syntax::UnaryOp::NotB => match ty { + Type::Bit(_) | Type::UInt(_, _) | Type::Angle(_, _) => true, + Type::BitArray(dims, _) | Type::UIntArray(_, dims) | Type::AngleArray(_, dims) => { + // the spe says "registers of the same size" which is a bit ambiguous + // but we can assume that it means that the array is a single dim + matches!(dims, ArrayDimensions::One(_)) + } + _ => false, + }, + syntax::UnaryOp::NotL => matches!(ty, Type::Bool(_)), + syntax::UnaryOp::Neg => { + matches!(ty, Type::Int(_, _) | Type::Float(_, _) | Type::Angle(_, _)) + } + } +} + +pub(crate) fn binop_requires_asymmetric_angle_op( + op: syntax::BinOp, + lhs: &Type, + rhs: &Type, +) -> bool { + match op { + syntax::BinOp::Div => { + matches!( + (lhs, rhs), + ( + Type::Angle(_, _), + Type::Int(_, _) | Type::UInt(_, _) | Type::Angle(_, _) + ) + ) + } + syntax::BinOp::Mul => { + matches!( + (lhs, rhs), + (Type::Angle(_, _), Type::Int(_, _) | Type::UInt(_, _)) + ) || matches!( + (lhs, rhs), + (Type::Int(_, _) | Type::UInt(_, _), Type::Angle(_, _)) + ) + } + _ => false, + } +} + +/// Bit arrays can be compared, but need to be converted to int first +pub(crate) fn binop_requires_int_conversion_for_type( + op: syntax::BinOp, + lhs: &Type, + rhs: &Type, +) -> bool { + match op { + syntax::BinOp::Eq + | syntax::BinOp::Gt + | syntax::BinOp::Gte + | syntax::BinOp::Lt + | syntax::BinOp::Lte + | syntax::BinOp::Neq => match (lhs, rhs) { + (Type::BitArray(lhs_dims, _), Type::BitArray(rhs_dims, _)) => { + match (lhs_dims, rhs_dims) { + (ArrayDimensions::One(lhs_size), ArrayDimensions::One(rhs_size)) => { + lhs_size == rhs_size + } + _ => false, + } + } + _ => false, + }, + _ => false, + } +} + +/// Symmetric arithmetic conversions are applied to: +/// binary arithmetic *, /, %, +, - +/// relational operators <, >, <=, >=, ==, != +/// binary bitwise arithmetic &, ^, |, +pub(crate) fn requires_symmetric_conversion(op: syntax::BinOp) -> bool { + match op { + syntax::BinOp::Add + | syntax::BinOp::AndB + | syntax::BinOp::AndL + | syntax::BinOp::Div + | syntax::BinOp::Eq + | syntax::BinOp::Exp + | syntax::BinOp::Gt + | syntax::BinOp::Gte + | syntax::BinOp::Lt + | syntax::BinOp::Lte + | syntax::BinOp::Mod + | syntax::BinOp::Mul + | syntax::BinOp::Neq + | syntax::BinOp::OrB + | syntax::BinOp::OrL + | syntax::BinOp::Sub + | syntax::BinOp::XorB => true, + syntax::BinOp::Shl | syntax::BinOp::Shr => false, + } +} + +pub(crate) fn try_promote_with_casting(left_type: &Type, right_type: &Type) -> Type { + let promoted_type = promote_types(left_type, right_type); + + if promoted_type != Type::Void { + return promoted_type; + } + if let Some(value) = try_promote_bitarray_to_int(left_type, right_type) { + return value; + } + // simple promotion failed, try a lossless cast + // each side to double + let promoted_rhs = promote_types(&Type::Float(None, right_type.is_const()), right_type); + let promoted_lhs = promote_types(left_type, &Type::Float(None, left_type.is_const())); + + match (promoted_lhs, promoted_rhs) { + (Type::Void, Type::Void) => Type::Float(None, false), + (Type::Void, promoted_rhs) => promoted_rhs, + (promoted_lhs, Type::Void) => promoted_lhs, + (promoted_lhs, promoted_rhs) => { + // return the greater of the two promoted types + if matches!(promoted_lhs, Type::Complex(..)) { + promoted_lhs + } else if matches!(promoted_rhs, Type::Complex(..)) { + promoted_rhs + } else if matches!(promoted_lhs, Type::Float(..)) { + promoted_lhs + } else if matches!(promoted_rhs, Type::Float(..)) { + promoted_rhs + } else { + Type::Float(None, false) + } + } + } +} + +fn try_promote_bitarray_to_int(left_type: &Type, right_type: &Type) -> Option { + if matches!( + (left_type, right_type), + (Type::Int(..) | Type::UInt(..), Type::BitArray(..)) + ) { + let Type::BitArray(ArrayDimensions::One(size), _) = right_type else { + return None; + }; + + if left_type.width().is_some() && left_type.width() != Some(*size) { + return None; + } + + return Some(left_type.clone()); + } + + if matches!( + (left_type, right_type), + (Type::BitArray(..), Type::Int(..) | Type::UInt(..)) + ) { + let Type::BitArray(ArrayDimensions::One(size), _) = left_type else { + return None; + }; + + if right_type.width().is_some() && right_type.width() != Some(*size) { + return None; + } + + return Some(right_type.clone()); + } + None +} + +// integer promotions are applied only to both operands of +// the shift operators << and >> +pub(crate) fn binop_requires_symmetric_uint_conversion(op: syntax::BinOp) -> bool { + matches!(op, syntax::BinOp::Shl | syntax::BinOp::Shr) +} + +pub(crate) fn is_complex_binop_supported(op: syntax::BinOp) -> bool { + matches!( + op, + syntax::BinOp::Add + | syntax::BinOp::Sub + | syntax::BinOp::Mul + | syntax::BinOp::Div + | syntax::BinOp::Exp + ) +} diff --git a/compiler/qsc_qasm/src/stdlib.rs b/compiler/qsc_qasm/src/stdlib.rs new file mode 100644 index 0000000000..ff466a50e6 --- /dev/null +++ b/compiler/qsc_qasm/src/stdlib.rs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +pub(crate) mod angle; +pub(crate) mod compile; + +pub use compile::package_store_with_qasm; diff --git a/compiler/qsc_qasm/src/stdlib/QasmStd/qsharp.json b/compiler/qsc_qasm/src/stdlib/QasmStd/qsharp.json new file mode 100644 index 0000000000..a29a2e746f --- /dev/null +++ b/compiler/qsc_qasm/src/stdlib/QasmStd/qsharp.json @@ -0,0 +1,9 @@ +{ + "author": "Microsoft", + "license": "MIT", + "files": [ + "src/QasmStd/Angle.qs", + "src/QasmStd/Convert.qs", + "src/QasmStd/Intrinsic.qs" + ] +} diff --git a/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Angle.qs b/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Angle.qs new file mode 100644 index 0000000000..d3926410ce --- /dev/null +++ b/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Angle.qs @@ -0,0 +1,273 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import Std.Arrays.Reversed; +import Std.Convert.BigIntAsBoolArray; +import Std.Convert.BoolArrayAsInt; +import Std.Convert.IntAsBigInt; +import Std.Convert.IntAsBoolArray; +import Std.Convert.IntAsDouble; +import Std.Diagnostics.Fact; +import Convert.__IntAsResultArrayBE__; +import Convert.__ResultAsInt__; + +export __Angle__, __AngleAsBoolArrayBE__, __AngleAsResultArray__, __AngleAsDouble__, __AngleAsBool__, __AngleAsResult__, __IntAsAngle__, __DoubleAsAngle__, __ConvertAngleToWidth__, __ConvertAngleToWidthNoTrunc__, __AngleShl__, __AngleShr__, __AngleNotB__, __AngleAndB__, __AngleOrB__, __AngleXorB__, __AngleEq__, __AngleNeq__, __AngleGt__, __AngleGte__, __AngleLt__, __AngleLte__, __AddAngles__, __SubtractAngles__, __MultiplyAngleByInt__, __MultiplyAngleByBigInt__, __DivideAngleByInt__, __DivideAngleByAngle__, __NegAngle__, __ResultAsAngle__; + + +struct __Angle__ { + Value : Int, + Size : Int +} + +function __AngleAsBoolArrayBE__(angle : __Angle__) : Bool[] { + Reversed(IntAsBoolArray(angle.Value, angle.Size)) +} + +function __AngleAsResultArray__(angle : __Angle__) : Result[] { + let (number, bits) = angle!; + __IntAsResultArrayBE__(number, bits) +} + +function __AngleAsDouble__(angle : __Angle__) : Double { + let F64_MANTISSA_DIGITS = 53; + let (value, size) = if angle.Size > F64_MANTISSA_DIGITS { + __ConvertAngleToWidth__(angle, F64_MANTISSA_DIGITS, false)! + } else { + angle! + }; + let denom = IntAsDouble(1 <<< size); + let value = IntAsDouble(value); + let factor = (2.0 * Std.Math.PI()) / denom; + value * factor +} + +function __AngleAsBool__(angle : __Angle__) : Bool { + return angle.Value != 0; +} + +function __ResultAsAngle__(result: Result) : __Angle__ { + new __Angle__ { Value = __ResultAsInt__(result), Size = 1 } +} + +function __AngleAsResult__(angle : __Angle__) : Result { + Microsoft.Quantum.Convert.BoolAsResult(angle.Value != 0) +} + +function __IntAsAngle__(value : Int, size : Int) : __Angle__ { + Fact(value >= 0, "Value must be >= 0"); + Fact(size > 0, "Size must be > 0"); + new __Angle__ { Value = value, Size = size } +} + +function __DoubleAsAngle__(value : Double, size : Int) : __Angle__ { + let tau : Double = 2. * Std.Math.PI(); + + mutable value = value % tau; + if value < 0. { + value = value + tau; + } + + Fact(value >= 0., "Value must be >= 0."); + Fact(value < tau, "Value must be < tau."); + Fact(size > 0, "Size must be > 0"); + + + let factor = tau / Std.Convert.IntAsDouble(1 <<< size); + let value = RoundHalfAwayFromZero(value / factor); + new __Angle__ { Value = value, Size = size } +} + +function __ConvertAngleToWidthNoTrunc__(angle : __Angle__, new_size : Int) : __Angle__ { + __ConvertAngleToWidth__(angle, new_size, false) +} + +function __ConvertAngleToWidth__(angle : __Angle__, new_size : Int, truncate : Bool) : __Angle__ { + let (value, size) = angle!; + if new_size < size { + let value = if truncate { + let shift_amount = size - new_size; + value >>> shift_amount + } else { + // Rounding + let shift_amount = size - new_size; + let half = 1 <<< (shift_amount - 1); + let mask = (1 <<< shift_amount) - 1; + let lower_bits = value &&& mask; + let upper_bits = value >>> shift_amount; + if lower_bits > half or (lower_bits == half and (upper_bits &&& 1) == 1) { + upper_bits + 1 + } else { + upper_bits + } + }; + new __Angle__ { Value = value, Size = size } + } elif new_size == size { + // Same size, no change + angle + } else { + // Padding with zeros + let value = value <<< (new_size - size); + new __Angle__ { Value = value, Size = size } + } +} + +// Bit shift + +function __AngleShl__(lhs : __Angle__, rhs : Int) : __Angle__ { + let (lhs_value, lhs_size) = lhs!; + let mask = (1 <<< lhs_size) - 1; + let value = (lhs_value <<< rhs) &&& mask; + new __Angle__ { Value = value, Size = lhs_size } +} + +function __AngleShr__(lhs : __Angle__, rhs : Int) : __Angle__ { + let (lhs_value, lhs_size) = lhs!; + let value = (lhs_value >>> rhs); + new __Angle__ { Value = value, Size = lhs_size } +} + +// Bitwise + +function __AngleNotB__(angle : __Angle__) : __Angle__ { + let (value, size) = angle!; + let mask = (1 <<< size) - 1; + let value = (~~~value) &&& mask; + new __Angle__ { Value = value, Size = size } +} + +function __AngleAndB__(lhs : __Angle__, rhs : __Angle__) : __Angle__ { + let (lhs_value, lhs_size) = lhs!; + let (rhs_value, rhs_size) = rhs!; + Fact(lhs_size == rhs_size, "Angle sizes must be the same"); + let value = lhs_value &&& rhs_value; + new __Angle__ { Value = value, Size = lhs_size } +} + +function __AngleOrB__(lhs : __Angle__, rhs : __Angle__) : __Angle__ { + let (lhs_value, lhs_size) = lhs!; + let (rhs_value, rhs_size) = rhs!; + Fact(lhs_size == rhs_size, "Angle sizes must be the same"); + let value = lhs_value ||| rhs_value; + new __Angle__ { Value = value, Size = lhs_size } +} + +function __AngleXorB__(lhs : __Angle__, rhs : __Angle__) : __Angle__ { + let (lhs_value, lhs_size) = lhs!; + let (rhs_value, rhs_size) = rhs!; + Fact(lhs_size == rhs_size, "Angle sizes must be the same"); + let value = lhs_value ^^^ rhs_value; + new __Angle__ { Value = value, Size = lhs_size } +} + +// Comparison + +function __AngleEq__(lhs: __Angle__, rhs: __Angle__) : Bool { + let (lhs_value, lhs_size) = lhs!; + let (rhs_value, rhs_size) = rhs!; + Fact(lhs_size == rhs_size, "Angle sizes must be the same"); + lhs_value == rhs_value +} + +function __AngleNeq__(lhs: __Angle__, rhs: __Angle__) : Bool { + let (lhs_value, lhs_size) = lhs!; + let (rhs_value, rhs_size) = rhs!; + Fact(lhs_size == rhs_size, "Angle sizes must be the same"); + lhs_value != rhs_value +} + +function __AngleGt__(lhs: __Angle__, rhs: __Angle__) : Bool { + let (lhs_value, lhs_size) = lhs!; + let (rhs_value, rhs_size) = rhs!; + Fact(lhs_size == rhs_size, "Angle sizes must be the same"); + lhs_value > rhs_value +} + +function __AngleGte__(lhs: __Angle__, rhs: __Angle__) : Bool { + let (lhs_value, lhs_size) = lhs!; + let (rhs_value, rhs_size) = rhs!; + Fact(lhs_size == rhs_size, "Angle sizes must be the same"); + lhs_value >= rhs_value +} + +function __AngleLt__(lhs: __Angle__, rhs: __Angle__) : Bool { + let (lhs_value, lhs_size) = lhs!; + let (rhs_value, rhs_size) = rhs!; + Fact(lhs_size == rhs_size, "Angle sizes must be the same"); + lhs_value < rhs_value +} + +function __AngleLte__(lhs: __Angle__, rhs: __Angle__) : Bool { + let (lhs_value, lhs_size) = lhs!; + let (rhs_value, rhs_size) = rhs!; + Fact(lhs_size == rhs_size, "Angle sizes must be the same"); + lhs_value <= rhs_value +} + +// Arithmetic + +function __AddAngles__(lhs : __Angle__, rhs : __Angle__) : __Angle__ { + let (lhs_value, lhs_size) = lhs!; + let (rhs_value, rhs_size) = rhs!; + Fact(lhs_size == rhs_size, "Angle sizes must be the same"); + let value = (lhs_value + rhs_value) % (1 <<< lhs_size); + new __Angle__ { Value = value, Size = lhs_size } +} + +function __SubtractAngles__(lhs : __Angle__, rhs : __Angle__) : __Angle__ { + let (lhs_value, lhs_size) = lhs!; + let (rhs_value, rhs_size) = rhs!; + Fact(lhs_size == rhs_size, "Angle sizes must be the same"); + let value = (lhs_value + ((1 <<< lhs_size) - rhs_value)) % (1 <<< lhs_size); + new __Angle__ { Value = value, Size = lhs_size } +} + +function __MultiplyAngleByInt__(angle : __Angle__, factor : Int) : __Angle__ { + let (value, size) = angle!; + let value = (value * factor) % (1 <<< size); + new __Angle__ { Value = value, Size = size } +} + +function __MultiplyAngleByBigInt__(angle : __Angle__, factor : BigInt) : __Angle__ { + let (value, size) = angle!; + let value : BigInt = Std.Convert.IntAsBigInt(value); + let value = (value * factor) % Std.Convert.IntAsBigInt(1 <<< size); + let value = Std.Convert.BoolArrayAsInt(Std.Convert.BigIntAsBoolArray(value, size)); + new __Angle__ { Value = value, Size = size } +} + +function __DivideAngleByAngle__(lhs : __Angle__, rhs : __Angle__) : Int { + let (lhs_value, lhs_size) = lhs!; + let (rhs_value, rhs_size) = rhs!; + Fact(lhs_size == rhs_size, "Angle sizes must be the same"); + let value = lhs_value / rhs_value; + value +} + +function __DivideAngleByInt__(angle : __Angle__, divisor : Int) : __Angle__ { + let (value, size) = angle!; + let value = value / divisor; + new __Angle__ { Value = value, Size = size } +} + +function __NegAngle__(angle : __Angle__) : __Angle__ { + let (value, size) = angle!; + let value = (1 <<< size) - value; + new __Angle__ { Value = value, Size = size } +} + +// not exported +function RoundHalfAwayFromZero(value : Double) : Int { + let roundedValue = Microsoft.Quantum.Math.Round(value); + let EPSILON = 2.2204460492503131e-16; + let diff = Std.Math.AbsD(value - Std.Convert.IntAsDouble(roundedValue)); + if (Std.Math.AbsD(diff - 0.5) < EPSILON) { + if (value > 0.0) { + return roundedValue + 1; + } else { + return roundedValue - 1; + } + } else { + return roundedValue; + } +} + diff --git a/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Convert.qs b/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Convert.qs new file mode 100644 index 0000000000..22dfb0c015 --- /dev/null +++ b/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Convert.qs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import Std.Math.AbsI; + +/// The POW function is used to implement the `pow` modifier in QASM for integers. +operation __Pow__<'T>(N: Int, op: ('T => Unit is Adj + Ctl), target : 'T) : Unit { + let op = if N > 0 { () => op(target) } else { () => Adjoint op(target) }; + for _ in 1..AbsI(N) { + op() + } +} + +/// The ``BARRIER`` function is used to implement the `barrier` statement in QASM. +/// The `@SimulatableIntrinsic` attribute is used to mark the operation for QIR +/// generation. +/// Q# doesn't support barriers, so this is a no-op. We need to figure out what +/// barriers mean in the context of QIR in the future for better support. +@SimulatableIntrinsic() +operation __quantum__qis__barrier__body() : Unit {} + + +/// The ``BOOL_AS_RESULT`` function is used to implement the cast expr in QASM for bool to bit. +/// This already exists in the Q# library, but is defined as a marker for casts from QASM. +function __BoolAsResult__(input: Bool) : Result { + Microsoft.Quantum.Convert.BoolAsResult(input) +} + +/// The ``BOOL_AS_INT`` function is used to implement the cast expr in QASM for bool to int. +function __BoolAsInt__(value: Bool) : Int { + if value { + 1 + } else { + 0 + } +} + +/// The ``BOOL_AS_BIGINT`` function is used to implement the cast expr in QASM for bool to big int. + +function __BoolAsBigInt__(value: Bool) : BigInt { + if value { + 1L + } else { + 0L + } +} + +/// The ``BOOL_AS_DOUBLE`` function is used to implement the cast expr in QASM for bool to int. + +function __BoolAsDouble__(value: Bool) : Double { + if value { + 1. + } else { + 0. + } +} + +/// The ``RESULT_AS_BOOL`` function is used to implement the cast expr in QASM for bit to bool. +/// This already exists in the Q# library, but is defined as a marker for casts from QASM. +function __ResultAsBool__(input: Result) : Bool { + Microsoft.Quantum.Convert.ResultAsBool(input) +} + +/// The ``RESULT_AS_INT`` function is used to implement the cast expr in QASM for bit to bool. +function __ResultAsInt__(input: Result) : Int { + if Microsoft.Quantum.Convert.ResultAsBool(input) { + 1 + } else { + 0 + } +} + +/// The ``RESULT_AS_BIGINT`` function is used to implement the cast expr in QASM for bit to bool. +function __ResultAsBigInt__(input: Result) : BigInt { + if Microsoft.Quantum.Convert.ResultAsBool(input) { + 1L + } else { + 0L + } +} + +/// The ``INT_AS_RESULT_ARRAY_BE`` function is used to implement the cast expr in QASM for int to bit[]. +/// with big-endian order. This is needed for round-trip conversion for bin ops. +function __IntAsResultArrayBE__(number : Int, bits : Int) : Result[] { + mutable runningValue = number; + mutable result = []; + for _ in 1..bits { + set result += [__BoolAsResult__((runningValue &&& 1) != 0)]; + set runningValue >>>= 1; + } + Microsoft.Quantum.Arrays.Reversed(result) +} + +/// The ``RESULT_ARRAY_AS_INT_BE`` function is used to implement the cast expr in QASM for bit[] to uint. +/// with big-endian order. This is needed for round-trip conversion for bin ops. +function __ResultArrayAsIntBE__(results : Result[]) : Int { + Microsoft.Quantum.Convert.ResultArrayAsInt(Microsoft.Quantum.Arrays.Reversed(results)) +} + +export __Pow__, __quantum__qis__barrier__body, __BoolAsResult__, __BoolAsInt__, __BoolAsBigInt__, __BoolAsDouble__, __ResultAsBool__, __ResultAsInt__, __ResultAsBigInt__, __IntAsResultArrayBE__, __ResultArrayAsIntBE__, ; diff --git a/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Intrinsic.qs b/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Intrinsic.qs new file mode 100644 index 0000000000..54a3977419 --- /dev/null +++ b/compiler/qsc_qasm/src/stdlib/QasmStd/src/QasmStd/Intrinsic.qs @@ -0,0 +1,570 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// stdgates.inc, gates with implied modifiers are omitted as they are mapped +// to the base gate with modifiers in the lowerer. +export gphase, U, p, x, y, z, h, s, sdg, t, tdg, sx, rx, ry, rz, cx, cp, swap, ccx, cu, phase, id, u1, u2, u3; + +// gates that qiskit won't emit qasm defs for that are NOT part of stgates.inc +// but are have the _standard_gate property in Qiskit: + +// QIR intrinsics missing from qasm std library, that Qiskit won't emit qasm defs for +export rxx, ryy, rzz; + +// Remaining gates that are not in the qasm std library, but are standard gates in Qiskit +// that Qiskit wont emit correctly. +export dcx, ecr, r, rzx, cs, csdg, sxdg, csx, cu1, cu3, rccx, c3sqrtx, c3x, rc3x, xx_minus_yy, xx_plus_yy, ccz; + +import Angle.*; + +import Std.Intrinsic.*; + +function ZERO_ANGLE() : __Angle__ { + return __DoubleAsAngle__(0., 1); +} + +function PI_OVER_2() : __Angle__ { + return __DoubleAsAngle__(Std.Math.PI() / 2., 53); +} + +function PI_OVER_4() : __Angle__ { + return __DoubleAsAngle__(Std.Math.PI() / 4., 53); +} + +function PI_OVER_8() : __Angle__ { + return __DoubleAsAngle__(Std.Math.PI() / 8., 53); +} + +function PI_ANGLE() : __Angle__ { + return __DoubleAsAngle__(Std.Math.PI(), 53); +} + +function NEG_PI_OVER_2() : __Angle__ { + return __DoubleAsAngle__(-Std.Math.PI() / 2., 53); +} + +function NEG_PI_OVER_4() : __Angle__ { + return __DoubleAsAngle__(-Std.Math.PI() / 4., 53); +} + +function NEG_PI_OVER_8() : __Angle__ { + return __DoubleAsAngle__(-Std.Math.PI() / 8., 53); +} + +operation gphase(theta : __Angle__) : Unit is Adj + Ctl { + body ... { + Exp([], __AngleAsDouble__(theta), []) + } + adjoint auto; + controlled auto; + controlled adjoint auto; +} + +operation U(theta : __Angle__, phi : __Angle__, lambda : __Angle__, qubit : Qubit) : Unit is Adj + Ctl { + body ... { + let theta = __AngleAsDouble__(theta); + let phi = __AngleAsDouble__(phi); + let lambda = __AngleAsDouble__(lambda); + + Rz(lambda, qubit); + Ry(theta, qubit); + Rz(phi, qubit); + R(PauliI, -lambda - phi - theta, qubit); + } + adjoint auto; + controlled auto; + controlled adjoint auto; +} + +operation p(lambda : __Angle__, qubit : Qubit) : Unit is Adj + Ctl { + Controlled gphase([qubit], lambda); +} + +operation x(qubit : Qubit) : Unit is Adj + Ctl { + X(qubit); +} + +operation y(qubit : Qubit) : Unit is Adj + Ctl { + Y(qubit); +} + +operation z(qubit : Qubit) : Unit is Adj + Ctl { + Z(qubit); +} + +operation h(qubit : Qubit) : Unit is Adj + Ctl { + H(qubit); +} + +operation s(qubit : Qubit) : Unit is Adj + Ctl { + S(qubit); +} + +operation sdg(qubit : Qubit) : Unit is Adj + Ctl { + Adjoint S(qubit); +} + +operation t(qubit : Qubit) : Unit is Adj + Ctl { + T(qubit); +} + +operation tdg(qubit : Qubit) : Unit is Adj + Ctl { + Adjoint T(qubit); +} + +operation sx(qubit : Qubit) : Unit is Adj + Ctl { + Rx(Std.Math.PI() / 2., qubit); + Adjoint R(PauliI, Std.Math.PI() / 2., qubit); +} + +operation rx(theta : __Angle__, qubit : Qubit) : Unit is Adj + Ctl { + let theta = __AngleAsDouble__(theta); + Rx(theta, qubit); +} + +operation ry(theta : __Angle__, qubit : Qubit) : Unit is Adj + Ctl { + let theta = __AngleAsDouble__(theta); + Ry(theta, qubit); +} + +operation rz(theta : __Angle__, qubit : Qubit) : Unit is Adj + Ctl { + let theta = __AngleAsDouble__(theta); + Rz(theta, qubit); +} + +operation cx(ctrl : Qubit, qubit : Qubit) : Unit is Adj + Ctl { + CNOT(ctrl, qubit); +} + +operation cp(lambda : __Angle__, ctrl : Qubit, qubit : Qubit) : Unit is Adj + Ctl { + Controlled p([ctrl], (lambda, qubit)); +} + +operation swap(qubit1 : Qubit, qubit2 : Qubit) : Unit is Adj + Ctl { + SWAP(qubit1, qubit2); +} + +operation ccx(ctrl1 : Qubit, ctrl2 : Qubit, target : Qubit) : Unit is Adj + Ctl { + CCNOT(ctrl1, ctrl2, target); +} + +operation cu(theta : __Angle__, phi : __Angle__, lambda : __Angle__, gamma : __Angle__, qubit1 : Qubit, qubit2 : Qubit) : Unit is Adj + Ctl { + p(__SubtractAngles__(gamma, __DivideAngleByInt__(theta, 2)), qubit1); + Controlled U([qubit2], (theta, phi, lambda, qubit1)); +} + +// Gates for OpenQASM 2 backwards compatibility +operation phase(lambda : __Angle__, qubit : Qubit) : Unit is Adj + Ctl { + U(ZERO_ANGLE(), ZERO_ANGLE(), lambda, qubit); +} + +operation id(qubit : Qubit) : Unit is Adj + Ctl { + I(qubit) +} + +operation u1(lambda : __Angle__, qubit : Qubit) : Unit is Adj + Ctl { + U(ZERO_ANGLE(), ZERO_ANGLE(), lambda, qubit); +} + +operation u2(phi : __Angle__, lambda : __Angle__, qubit : Qubit) : Unit is Adj + Ctl { + gphase(__NegAngle__(__DivideAngleByInt__(__AddAngles__( + phi, + __AddAngles__( + lambda, + PI_OVER_2() + ) + ), 2))); + + U(PI_OVER_2(), phi, lambda, qubit); +} + +operation u3(theta : __Angle__, phi : __Angle__, lambda : __Angle__, qubit : Qubit) : Unit is Adj + Ctl { + gphase(__NegAngle__(__DivideAngleByInt__(__AddAngles__( + phi, + __AddAngles__( + lambda, + theta + ) + ), 2))); + + U(theta, phi, lambda, qubit); +} + + +/// rxx: gate rxx(theta) a, b { h a; h b; cx a, b; rz(theta) b; cx a, b; h b; h a; } +operation rxx(theta : __Angle__, qubit0 : Qubit, qubit1 : Qubit) : Unit is Adj + Ctl { + Rxx(__AngleAsDouble__(theta), qubit0, qubit1); +} + +/// ryy: gate ryy(theta) a, b { rx(pi/2) a; rx(pi/2) b; cx a, b; rz(theta) b; cx a, b; rx(-pi/2) a; rx(-pi/2) b; } +operation ryy(theta : __Angle__, qubit0 : Qubit, qubit1 : Qubit) : Unit is Adj + Ctl { + Ryy(__AngleAsDouble__(theta), qubit0, qubit1); +} + +/// rzz: gate rzz(theta) a, b { cx a, b; u1(theta) b; cx a, b; } +operation rzz(theta : __Angle__, qubit0 : Qubit, qubit1 : Qubit) : Unit is Adj + Ctl { + Rzz(__AngleAsDouble__(theta), qubit0, qubit1); +} + +/// Double-CNOT gate. +/// ``` +/// gate dcx a, b { +/// cx a, b; +/// cx b, a; +/// } +/// ``` +operation dcx(qubit0 : Qubit, qubit1 : Qubit) : Unit is Adj + Ctl { + cx(qubit0, qubit1); + cx(qubit1, qubit0); +} + +/// An echoed cross-resonance gate. +/// `gate ecr a, b { rzx(pi/4) a, b; x a; rzx(-pi/4) a, b; }` +operation ecr(qubit0 : Qubit, qubit1 : Qubit) : Unit is Adj + Ctl { + rzx(PI_OVER_4(), qubit0, qubit1); + x(qubit0); + rzx(NEG_PI_OVER_4(), qubit0, qubit1); +} + +/// Rotation θ around the cos(φ)x + sin(φ)y axis. +/// `gate r(θ, φ) a {u3(θ, φ - π/2, -φ + π/2) a;}` +operation r(theta : __Angle__, phi : __Angle__, qubit : Qubit) : Unit is Adj + Ctl { + u3(theta, PI_OVER_4(), __SubtractAngles__( + phi, + NEG_PI_OVER_2() + ), qubit); +} + +/// A parametric 2-qubit `Z ⊗ X` interaction (rotation about ZX). +/// `gate rzx(theta) a, b { h b; cx a, b; u1(theta) b; cx a, b; h b; }` +operation rzx(theta : __Angle__, qubit0 : Qubit, qubit1 : Qubit) : Unit is Adj + Ctl { + h(qubit1); + cx(qubit0, qubit1); + u1(theta, qubit1); + cx(qubit0, qubit1); + h(qubit1); +} + + +/// Controlled-S gate. +/// `gate cs a,b { h b; cp(pi/2) a,b; h b; }` +operation cs(qubit0 : Qubit, qubit1 : Qubit) : Unit is Adj + Ctl { + Controlled s([qubit1], qubit0); +} + +/// Controlled-S† gate. +/// csdg: gate csdg a,b { h b; cp(-pi/2) a,b; h b; } +operation csdg(qubit0 : Qubit, qubit1 : Qubit) : Unit is Adj + Ctl { + Controlled Adjoint S([qubit1], qubit0); +} + +/// The inverse single-qubit Sqrt(X) gate. +/// `gate sxdg a { rz(pi/2) a; h a; rz(pi/2); }` +operation sxdg(qubit : Qubit) : Unit is Adj + Ctl { + Adjoint sx(qubit); +} + +// Controlled-√X gate. +/// `gate csx a,b { h b; cu1(pi/2) a,b; h b; }` +operation csx(qubit0 : Qubit, qubit1 : Qubit) : Unit is Adj + Ctl { + Controlled sx([qubit1], qubit0); +} + +/// Controlled-U1 gate. +/// `ctrl @ u1(lambda) a, b` or: +/// ``` +/// gate cu1(lambda) a,b { +/// u1(lambda/2) a; +/// cx a,b; +/// u1(-lambda/2) b; +/// cx a,b; +/// u1(lambda/2) b; +/// } +/// ``` +operation cu1(lambda : __Angle__, ctrl : Qubit, target : Qubit) : Unit is Adj + Ctl { + Controlled u1([ctrl], (lambda, target)); +} + +/// Controlled-U3 gate (3-parameter two-qubit gate). +/// `ctrl @ u3(theta, phi, lambda) a, b` or: +/// ``` +/// gate cu3(theta,phi,lambda) c, t { +/// u1((lambda+phi)/2) c; +/// u1((lambda-phi)/2) t; +/// cx c,t; +/// u3(-theta/2,0,-(phi+lambda)/2) t; +/// cx c,t; +/// u3(theta/2,phi,0) t; +/// } +/// ``` +operation cu3(theta : __Angle__, phi : __Angle__, lambda : __Angle__, ctrl : Qubit, target : Qubit) : Unit is Adj + Ctl { + Controlled u3([ctrl], (theta, phi, lambda, target)); +} + +/// The simplified Toffoli gate, also referred to as Margolus gate. +/// `gate rccx a,b,c { u2(0,pi) c; u1(pi/4) c; cx b, c; u1(-pi/4) c; cx a, c; u1(pi/4) c; cx b, c; u1(-pi/4) c; u2(0,pi) c; }` +operation rccx(ctrl1 : Qubit, ctrl2 : Qubit, target : Qubit) : Unit is Adj + Ctl { + u2(ZERO_ANGLE(), PI_ANGLE(), target); + u1(PI_OVER_4(), target); + cx(ctrl2, target); + u1(NEG_PI_OVER_4(), target); + cx(ctrl1, target); + u1(PI_OVER_4(), target); + cx(ctrl2, target); + u1(NEG_PI_OVER_4(), target); + u2(ZERO_ANGLE(), PI_ANGLE(), target); +} + +/// c3sx/c3sqrtx: The 3-qubit controlled sqrt-X gate. +/// ``` +/// gate c3sqrtx a,b,c,d { +/// h d; +/// cu1(pi/8) a,d; +/// h d; cx a,b; +/// h d; +/// cu1(-pi/8) b,d; +/// h d; cx a,b; h d; +/// cu1(pi/8) b,d; +/// h d; +/// cx b,c; +/// h d; +/// cu1(-pi/8) c,d; +/// h d; +/// cx a,c; +/// h d; +/// cu1(pi/8) c,d; +/// h d; +/// cx b,c; +/// h d; +/// cu1(-pi/8) c,d; +/// h d; +/// cx a,c; +/// h d; +/// cu1(pi/8) c,d; +/// h d; +/// } +/// ``` +operation c3sqrtx(a : Qubit, b : Qubit, c : Qubit, target : Qubit) : Unit is Adj + Ctl { + h(target); + Controlled u1([a], (PI_OVER_8(), target)); + h(target); + cx(a, b); + h(target); + Controlled u1([b], (NEG_PI_OVER_8(), target)); + h(target); + cx(a, b); + h(target); + Controlled u1([b], (PI_OVER_8(), target)); + h(target); + cx(b, c); + h(target); + Controlled u1([c], (NEG_PI_OVER_8(), target)); + h(target); + cx(a, c); + h(target); + Controlled u1([c], (PI_OVER_8(), target)); + h(target); + cx(b, c); + h(target); + Controlled u1([c], (NEG_PI_OVER_8(), target)); + h(target); + cx(a, c); + h(target); + Controlled u1([c], (PI_OVER_8(), target)); + h(target); +} + +/// The X gate controlled on 3 qubits. +/// ``` +/// gate c3x a,b,c,d +/// { +/// h d; +/// p(pi/8) a; +/// p(pi/8) b; +/// p(pi/8) c; +/// p(pi/8) d; +/// cx a, b; +/// p(-pi/8) b; +/// cx a, b; +/// cx b, c; +/// p(-pi/8) c; +/// cx a, c; +/// p(pi/8) c; +/// cx b, c; +/// p(-pi/8) c; +/// cx a, c; +/// cx c, d; +/// p(-pi/8) d; +/// cx b, d; +/// p(pi/8) d; +/// cx c, d; +/// p(-pi/8) d; +/// cx a, d; +/// p(pi/8) d; +/// cx c, d; +/// p(-pi/8) d; +/// cx b, d; +/// p(pi/8) d; +/// cx c, d; +/// p(-pi/8) d; +/// cx a, d; +/// h d; +/// } +/// ``` +operation c3x(a : Qubit, b : Qubit, c : Qubit, d : Qubit) : Unit is Adj + Ctl { + h(d); + p(PI_OVER_8(), a); + p(PI_OVER_8(), b); + p(PI_OVER_8(), c); + p(PI_OVER_8(), d); + cx(a, b); + p(NEG_PI_OVER_8(), b); + cx(a, b); + cx(b, c); + p(NEG_PI_OVER_8(), c); + cx(a, c); + p(PI_OVER_8(), c); + cx(b, c); + p(NEG_PI_OVER_8(), c); + cx(a, c); + cx(c, d); + p(NEG_PI_OVER_8(), d); + cx(b, d); + p(PI_OVER_8(), d); + cx(c, d); + p(NEG_PI_OVER_8(), d); + cx(a, d); + p(PI_OVER_8(), d); + cx(c, d); + p(NEG_PI_OVER_8(), d); + cx(b, d); + p(PI_OVER_8(), d); + cx(c, d); + p(NEG_PI_OVER_8(), d); + cx(a, d); + h(d); +} + +/// Simplified 3-controlled Toffoli gate. +/// ``` +/// gate rc3x a,b,c,d{ +/// u2(0,pi) d; +/// u1(pi/4) d; +/// cx c,d; +/// u1(-pi/4) d; +/// u2(0,pi) d; +/// cx a,d; +/// u1(pi/4) d; +/// cx b,d; +/// u1(-pi/4) d; +/// cx a,d; +/// u1(pi/4) d; +/// cx b,d; +/// u1(-pi/4) d; +/// u2(0,pi) d; +/// u1(pi/4) d; +/// cx c,d; +/// u1(-pi/4) d; +/// u2(0,pi) d; +/// } +/// ``` +operation rc3x(a : Qubit, b : Qubit, c : Qubit, d : Qubit) : Unit is Adj + Ctl { + u2(ZERO_ANGLE(), PI_ANGLE(), d); + u1(PI_OVER_4(), d); + cx(c, d); + u1(NEG_PI_OVER_4(), d); + u2(ZERO_ANGLE(), PI_ANGLE(), d); + cx(a, d); + u1(PI_OVER_4(), d); + cx(b, d); + u1(NEG_PI_OVER_4(), d); + cx(a, d); + u1(PI_OVER_4(), d); + cx(b, d); + u1(NEG_PI_OVER_4(), d); + u2(ZERO_ANGLE(), PI_ANGLE(), d); + u1(PI_OVER_4(), d); + cx(c, d); + u1(NEG_PI_OVER_4(), d); + u2(ZERO_ANGLE(), PI_ANGLE(), d); +} + +/// XX-YY gate. +/// ``` +/// gate xx_minus_yy(theta, beta) a, b { +/// rz(-beta) b; +/// rz(-pi/2) a; +/// sx a; +/// rz(pi/2) a; +/// s b; +/// cx a, b; +/// ry(theta/2) a; +/// ry(-theta/2) b; +/// cx a, b; +/// sdg b; +/// rz(-pi/2) a; +/// sxdg a; +/// rz(pi/2) a; +/// rz(beta) b; +/// } +/// ``` +operation xx_minus_yy(theta : __Angle__, beta : __Angle__, qubit0 : Qubit, qubit1 : Qubit) : Unit is Adj + Ctl { + rz(__NegAngle__(beta), qubit1); + rz(NEG_PI_OVER_2(), qubit0); + sx(qubit0); + rz(PI_OVER_2(), qubit0); + s(qubit1); + cx(qubit0, qubit1); + ry(__DivideAngleByInt__(theta, 2), qubit0); + ry(__NegAngle__(__DivideAngleByInt__(theta, 2)), qubit1); + cx(qubit0, qubit1); + sdg(qubit1); + rz(NEG_PI_OVER_2(), qubit0); + sxdg(qubit0); + rz(PI_OVER_2(), qubit0); + rz(beta, qubit1); +} + +/// XX+YY gate. +/// ``` +/// gate xx_plus_yy(theta, beta) a, b { +/// rz(beta) b; +/// rz(-pi/2) a; +/// sx a; +/// rz(pi/2) a; +/// s b; +/// cx a, b; +/// ry(theta/2) a; +/// ry(theta/2) b; +/// cx a, b; +/// sdg b; +/// rz(-pi/2) a; +/// sxdg a; +/// rz(pi/2) a; +/// rz(-beta) b; +/// } +/// ``` +operation xx_plus_yy(theta : __Angle__, beta : __Angle__, qubit0 : Qubit, qubit1 : Qubit) : Unit is Adj + Ctl { + rz(beta, qubit1); + rz(NEG_PI_OVER_2(), qubit0); + sx(qubit0); + rz(PI_OVER_2(), qubit0); + s(qubit1); + cx(qubit0, qubit1); + ry(__DivideAngleByInt__(theta, 2), qubit0); + ry(__DivideAngleByInt__(theta, 2), qubit1); + cx(qubit0, qubit1); + sdg(qubit1); + rz(NEG_PI_OVER_2(), qubit0); + sxdg(qubit0); + rz(PI_OVER_2(), qubit0); + rz(__NegAngle__(beta), qubit1); +} + +/// CCZ gate. +/// `gate ccz a,b,c { h c; ccx a,b,c; h c; }` +operation ccz(ctrl1 : Qubit, ctrl2 : Qubit, target : Qubit) : Unit is Adj + Ctl { + h(target); + ccx(ctrl1, ctrl2, target); + h(target); +} diff --git a/compiler/qsc_qasm3/src/angle.rs b/compiler/qsc_qasm/src/stdlib/angle.rs similarity index 56% rename from compiler/qsc_qasm3/src/angle.rs rename to compiler/qsc_qasm/src/stdlib/angle.rs index 44de3cf37a..43c8f60a99 100644 --- a/compiler/qsc_qasm3/src/angle.rs +++ b/compiler/qsc_qasm/src/stdlib/angle.rs @@ -6,38 +6,76 @@ pub(crate) mod tests; use num_bigint::BigInt; -use crate::oqasm_helpers::safe_u64_to_f64; +use crate::convert::safe_u64_to_f64; +use core::f64; use std::convert::TryInto; -use std::f64::consts::PI; +use std::f64::consts::TAU; use std::fmt; -use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use std::ops::{ + Add, AddAssign, BitAnd, BitOr, BitXor, Div, DivAssign, Mul, MulAssign, Neg, Not, Shl, Shr, Sub, + SubAssign, +}; /// A fixed-point angle type with a specified number of bits. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -struct Angle { - value: u64, - size: u32, +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Angle { + pub value: u64, + pub size: u32, } -#[allow(dead_code)] impl Angle { - fn new(value: u64, size: u32) -> Self { + pub fn new(value: u64, size: u32) -> Self { Angle { value, size } } - fn from_f64(val: f64, size: u32) -> Self { + pub fn from_f64_maybe_sized(val: f64, size: Option) -> Angle { + Self::from_f64_sized(val, size.unwrap_or(f64::MANTISSA_DIGITS)) + } + + /// Takes an `f64` representing angle and: + /// 1. Wraps it around so that it is in the range [0, TAU). + /// 2. Encodes it as a binary number between 0 and (1 << size) - 1. + pub fn from_f64_sized(mut val: f64, size: u32) -> Angle { + // First, we need to convert the angle to the `[0, TAU)` range. + val %= TAU; + + // The modulus operator leaves negative numbers as negative. + // So, in this case we need to add an extra `TAU`. + if val < 0. { + val += TAU; + } + + // If the size is > f64::MANTISSA_DIGITS, the cast to f64 + // on the next lines will loose precission. + if size > f64::MANTISSA_DIGITS { + return Self::from_f64_sized_edge_case(val, size); + } + #[allow(clippy::cast_precision_loss)] - let factor = (2.0 * PI) / (1u64 << size) as f64; + let factor = TAU / (1u64 << size) as f64; #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] let value = (val / factor).round() as u64; - Angle { value, size } + Angle::new(value, size) } + /// This function handles the edge case when size > `f64::MANTISSA_DIGITS`. + fn from_f64_sized_edge_case(val: f64, size: u32) -> Angle { + let angle = Self::from_f64_sized(val, f64::MANTISSA_DIGITS); + angle.cast(size, false) + } + + #[cfg(test)] fn to_bitstring(self) -> String { format!("{:0width$b}", self.value, width = self.size as usize) } + pub fn cast_to_maybe_sized(self, new_size: Option) -> Angle { + match new_size { + Some(size) => self.cast(size, false), + None => self, + } + } fn cast(&self, new_size: u32, truncate: bool) -> Self { match new_size.cmp(&self.size) { std::cmp::Ordering::Less => { @@ -79,6 +117,91 @@ impl Angle { } } +impl Default for Angle { + fn default() -> Self { + Self { + value: 0, + size: f64::MANTISSA_DIGITS, + } + } +} + +// Bit shift +impl Shl for Angle { + type Output = Self; + + fn shl(self, rhs: i64) -> Self::Output { + let mask = (1 << self.size) - 1; + Self { + value: (self.value << rhs) & mask, + size: self.size, + } + } +} + +impl Shr for Angle { + type Output = Self; + + fn shr(self, rhs: i64) -> Self::Output { + Self { + value: self.value >> rhs, + size: self.size, + } + } +} + +// Bitwise + +impl Not for Angle { + type Output = Self; + + fn not(self) -> Self::Output { + let mask = (1 << self.size) - 1; + Self { + value: !self.value & mask, + size: self.size, + } + } +} + +impl BitAnd for Angle { + type Output = Self; + + fn bitand(self, rhs: Self) -> Self::Output { + assert_eq!(self.size, rhs.size); + Self { + value: self.value & rhs.value, + size: self.size, + } + } +} + +impl BitOr for Angle { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + assert_eq!(self.size, rhs.size); + Self { + value: self.value | rhs.value, + size: self.size, + } + } +} + +impl BitXor for Angle { + type Output = Self; + + fn bitxor(self, rhs: Self) -> Self::Output { + assert_eq!(self.size, rhs.size); + Self { + value: self.value ^ rhs.value, + size: self.size, + } + } +} + +// Arithmetic + impl Add for Angle { type Output = Self; @@ -185,17 +308,26 @@ impl DivAssign for Angle { impl TryInto for Angle { type Error = &'static str; + /// Angle to float cast is not allowed in QASM3. + /// This function is only meant to be used in unit tests. fn try_into(self) -> Result { if self.size > 64 { return Err("Size exceeds 64 bits"); } + + // Edge case handling. + if self.size > f64::MANTISSA_DIGITS { + let angle = self.cast(f64::MANTISSA_DIGITS, false); + return angle.try_into(); + } + let Some(denom) = safe_u64_to_f64(1u64 << self.size) else { return Err("Denominator is too large"); }; let Some(value) = safe_u64_to_f64(self.value) else { return Err("Value is too large"); }; - let factor = (2.0 * PI) / denom; + let factor = TAU / denom; Ok(value * factor) } } diff --git a/compiler/qsc_qasm3/src/angle/tests.rs b/compiler/qsc_qasm/src/stdlib/angle/tests.rs similarity index 59% rename from compiler/qsc_qasm3/src/angle/tests.rs rename to compiler/qsc_qasm/src/stdlib/angle/tests.rs index 783d8dc3e2..9c1142b8e0 100644 --- a/compiler/qsc_qasm3/src/angle/tests.rs +++ b/compiler/qsc_qasm/src/stdlib/angle/tests.rs @@ -5,15 +5,46 @@ #![allow(clippy::unreadable_literal)] use std::convert::TryInto; -use std::f64::consts::PI; +use std::f64::consts::{PI, TAU}; use super::Angle; +#[test] +fn test_angle_domain() { + let angle_0000 = Angle::from_f64_sized(0.0, 4); + let angle_0001 = Angle::from_f64_sized(PI / 8.0, 4); + let angle_0010 = Angle::from_f64_sized(PI / 4.0, 4); + let angle_0100 = Angle::from_f64_sized(PI / 2.0, 4); + let angle_1000 = Angle::from_f64_sized(PI, 4); + + assert_eq!(angle_0000.to_bitstring(), "0000"); + assert_eq!(angle_0001.to_bitstring(), "0001"); + assert_eq!(angle_0010.to_bitstring(), "0010"); + assert_eq!(angle_0100.to_bitstring(), "0100"); + assert_eq!(angle_1000.to_bitstring(), "1000"); +} + +#[test] +fn tau_wraps_around() { + let angle_0 = Angle::from_f64_sized(0.0, 4); + let angle_tau = Angle::from_f64_sized(TAU, 4); + assert_eq!(angle_0.to_bitstring(), "0000"); + assert_eq!(angle_tau.to_bitstring(), "0000"); +} + +#[test] +fn angle_float_invariant() { + let angle = Angle::from_f64_sized(PI, 4); + assert_eq!(angle.to_bitstring(), "1000"); + let pi: f64 = angle.try_into().unwrap(); + assert!(dbg!((pi - PI).abs()) <= f64::EPSILON); +} + #[test] fn test_angle() { - let angle1 = Angle::from_f64(PI, 4); - let angle2 = Angle::from_f64(PI / 2.0, 6); - let angle3 = Angle::from_f64(7.0 * (PI / 8.0), 8); + let angle1 = Angle::from_f64_sized(PI, 4); + let angle2 = Angle::from_f64_sized(PI / 2.0, 6); + let angle3 = Angle::from_f64_sized(7.0 * (PI / 8.0), 8); assert_eq!(angle1.to_bitstring(), "1000"); assert_eq!(angle2.to_bitstring(), "010000"); @@ -22,132 +53,132 @@ fn test_angle() { #[test] fn test_angle_creation() { - let angle = Angle::from_f64(PI, 4); + let angle = Angle::from_f64_sized(PI, 4); assert_eq!(angle.value, 8); assert_eq!(angle.size, 4); } #[test] fn test_angle_addition() { - let angle1 = Angle::from_f64(PI / 2.0, 4); - let angle2 = Angle::from_f64(PI / 2.0, 4); + let angle1 = Angle::from_f64_sized(PI / 2.0, 4); + let angle2 = Angle::from_f64_sized(PI / 2.0, 4); let result = angle1 + angle2; assert_eq!(result.value, 8); let angle: f64 = result.try_into().unwrap(); - assert!((angle - PI).abs() < f64::EPSILON); + assert!((angle - PI).abs() <= f64::EPSILON); } #[test] fn test_angle_multiplication() { - let angle = Angle::from_f64(PI / 4.0, 4); + let angle = Angle::from_f64_sized(PI / 4.0, 4); let result: Angle = angle * 2u64; assert_eq!(result.value, 4); let angle: f64 = result.try_into().unwrap(); - assert!((angle - PI / 2.0).abs() < f64::EPSILON); + assert!((angle - PI / 2.0).abs() <= f64::EPSILON); } #[test] fn test_angle_multiplication_bigint() { - let angle = Angle::from_f64(PI / 4.0, 4); + let angle = Angle::from_f64_sized(PI / 4.0, 4); let result: Angle = angle * 18446744073709551616u128; assert_eq!(result.value, 0); let angle: f64 = result.try_into().unwrap(); - assert!((angle - 0.0).abs() < f64::EPSILON); + assert!((angle - 0.0).abs() <= f64::EPSILON); } #[test] fn test_angle_multiplication_bigint2() { - let angle = Angle::from_f64(PI / 4.0, 4); + let angle = Angle::from_f64_sized(PI / 4.0, 4); let result: Angle = angle * 9223372036854775806u128; assert_eq!(result.value, 12); let angle: f64 = result.try_into().unwrap(); - assert!((angle - ((3. * PI) / 2.)).abs() < f64::EPSILON); + assert!((angle - ((3. * PI) / 2.)).abs() <= f64::EPSILON); } #[test] fn test_angle_division_int() { - let angle = Angle::from_f64(PI / 2.0, 4); + let angle = Angle::from_f64_sized(PI / 2.0, 4); let result = angle / 2; assert_eq!(result.value, 2); let angle: f64 = result.try_into().unwrap(); - assert!((angle - PI / 4.0).abs() < f64::EPSILON); + assert!((angle - PI / 4.0).abs() <= f64::EPSILON); } #[test] fn test_angle_division_by_angle() { - let angle1 = Angle::from_f64(PI, 4); - let angle2 = Angle::from_f64(PI / 4.0, 4); + let angle1 = Angle::from_f64_sized(PI, 4); + let angle2 = Angle::from_f64_sized(PI / 4.0, 4); let result = angle1 / angle2; assert_eq!(result, 4); } #[test] fn test_angle_unary_negation() { - let angle = Angle::from_f64(PI / 4.0, 4); + let angle = Angle::from_f64_sized(PI / 4.0, 4); let result = -angle; // "0010" assert_eq!(result.value, 14); // 7*(pi/4) │ "1110" } #[test] fn test_angle_compound_addition() { - let mut angle1 = Angle::from_f64(PI / 2.0, 4); - let angle2 = Angle::from_f64(PI / 2.0, 4); + let mut angle1 = Angle::from_f64_sized(PI / 2.0, 4); + let angle2 = Angle::from_f64_sized(PI / 2.0, 4); angle1 += angle2; assert_eq!(angle1.value, 8); let angle: f64 = angle1.try_into().unwrap(); - assert!((angle - PI).abs() < f64::EPSILON); + assert!((angle - PI).abs() <= f64::EPSILON); } #[test] fn test_angle_compound_subtraction() { - let mut angle1 = Angle::from_f64(PI, 4); - let angle2 = Angle::from_f64(PI / 2.0, 4); + let mut angle1 = Angle::from_f64_sized(PI, 4); + let angle2 = Angle::from_f64_sized(PI / 2.0, 4); angle1 -= angle2; assert_eq!(angle1.value, 4); let angle: f64 = angle1.try_into().unwrap(); - assert!((angle - PI / 2.0).abs() < f64::EPSILON); + assert!((angle - PI / 2.0).abs() <= f64::EPSILON); } #[test] fn test_angle_compound_multiplication() { - let mut angle = Angle::from_f64(PI / 4.0, 4); + let mut angle = Angle::from_f64_sized(PI / 4.0, 4); angle *= 2; assert_eq!(angle.value, 4); let angle: f64 = angle.try_into().unwrap(); - assert!((angle - PI / 2.0).abs() < f64::EPSILON); + assert!((angle - PI / 2.0).abs() <= f64::EPSILON); } #[test] fn test_angle_compound_division() { - let mut angle = Angle::from_f64(PI / 2.0, 4); + let mut angle = Angle::from_f64_sized(PI / 2.0, 4); angle /= 2; assert_eq!(angle.value, 2); let angle: f64 = angle.try_into().unwrap(); - assert!((angle - PI / 4.0).abs() < f64::EPSILON); + assert!((angle - PI / 4.0).abs() <= f64::EPSILON); } #[test] fn test_angle_bitstring() { - let angle = Angle::from_f64(PI, 4); + let angle = Angle::from_f64_sized(PI, 4); assert_eq!(angle.to_bitstring(), "1000"); } #[test] fn test_angle_try_into_f64() { - let angle: Angle = Angle::from_f64(PI, 4); + let angle: Angle = Angle::from_f64_sized(PI, 4); let angle_f64: f64 = angle.try_into().unwrap(); - assert!((angle_f64 - PI).abs() < f64::EPSILON); + assert!((angle_f64 - PI).abs() <= f64::EPSILON); } #[test] fn test_angle_display() { - let angle = Angle::from_f64(PI, 4); + let angle = Angle::from_f64_sized(PI, 4); assert_eq!(format!("{angle}"), format!("{PI}")); } #[test] fn from_f64_round_to_the_nearest_ties_to_even() { - let angle = Angle::from_f64(2.0 * PI * (127. / 512.), 8); + let angle = Angle::from_f64_sized(2.0 * PI * (127. / 512.), 8); // 00111111 is equally close, but even rounds to 01000000 assert_eq!(angle.to_bitstring(), "01000000"); } @@ -178,7 +209,7 @@ fn test_angle_cast_round_padding() { assert!( (TryInto::::try_into(angle).unwrap() - TryInto::::try_into(new_angle).unwrap()) .abs() - < f64::EPSILON + <= f64::EPSILON ); } diff --git a/compiler/qsc_qasm/src/stdlib/compile.rs b/compiler/qsc_qasm/src/stdlib/compile.rs new file mode 100644 index 0000000000..f0de74ec24 --- /dev/null +++ b/compiler/qsc_qasm/src/stdlib/compile.rs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use miette::Report; + +use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags}; +use qsc_frontend::{ + compile::{compile, CompileUnit, PackageStore, SourceContents, SourceMap, SourceName}, + error::WithSource, +}; +use qsc_hir::hir::PackageId; +use qsc_passes::{run_core_passes, run_default_passes, PackageType}; + +#[test] +fn compiles_with_base_profile() { + let _ = package_store_with_qasm(TargetCapabilityFlags::empty()); +} + +#[must_use] +pub fn package_store_with_qasm( + capabilities: TargetCapabilityFlags, +) -> ( + qsc_hir::hir::PackageId, + qsc_hir::hir::PackageId, + PackageStore, +) { + let (std_package_id, mut store) = package_store_with_stdlib(capabilities); + let mut unit = compile_qasm_std(&store, std_package_id, capabilities); + + let pass_errors = run_default_passes(store.core(), &mut unit, PackageType::Lib); + if pass_errors.is_empty() { + //unit.expose(); + let package_id = store.insert(unit); + (std_package_id, package_id, store) + } else { + for error in pass_errors { + let report = Report::new(WithSource::from_map(&unit.sources, error)); + eprintln!("{report:?}"); + } + + panic!("could not compile qasm standard library") + } +} + +pub const STD_LIB: &[(&str, &str)] = &[ + ( + "openqasm-library-source:QasmStd/Angle.qs", + include_str!("QasmStd/src/QasmStd/Angle.qs"), + ), + ( + "openqasm-library-source:QasmStd/Convert.qs", + include_str!("QasmStd/src/QasmStd/Convert.qs"), + ), + ( + "openqasm-library-source:QasmStd/Intrinsic.qs", + include_str!("QasmStd/src/QasmStd/Intrinsic.qs"), + ), +]; + +/// Compiles the standard library. +/// +/// # Panics +/// +/// Panics if the standard library does not compile without errors. +#[must_use] +pub fn compile_qasm_std( + store: &PackageStore, + std_id: PackageId, + capabilities: TargetCapabilityFlags, +) -> CompileUnit { + let std: Vec<(SourceName, SourceContents)> = STD_LIB + .iter() + .map(|(name, contents)| ((*name).into(), (*contents).into())) + .collect(); + let sources = SourceMap::new(std, None); + + let mut unit = compile( + store, + &[(PackageId::CORE, None), (std_id, None)], + sources, + capabilities, + LanguageFeatures::default(), + ); + assert_no_errors(&unit.sources, &mut unit.errors); + unit +} + +fn assert_no_errors(sources: &SourceMap, errors: &mut Vec) { + if !errors.is_empty() { + for error in errors.drain(..) { + eprintln!("{:?}", Report::new(WithSource::from_map(sources, error))); + } + + panic!("could not compile package"); + } +} + +#[must_use] +pub fn package_store_with_stdlib( + capabilities: TargetCapabilityFlags, +) -> (qsc_hir::hir::PackageId, PackageStore) { + let mut store = PackageStore::new(core()); + let std_id = store.insert(std(&store, capabilities)); + (std_id, store) +} + +/// Compiles the core library. +/// +/// # Panics +/// +/// Panics if the core library compiles with errors. +#[must_use] +fn core() -> CompileUnit { + let mut unit = qsc_frontend::compile::core(); + let pass_errors = run_core_passes(&mut unit); + if pass_errors.is_empty() { + unit + } else { + for error in pass_errors { + let report = Report::new(WithSource::from_map(&unit.sources, error)); + eprintln!("{report:?}"); + } + + panic!("could not compile core library") + } +} + +/// Compiles the standard library. +/// +/// # Panics +/// +/// Panics if the standard library does not compile without errors. +#[must_use] +fn std(store: &PackageStore, capabilities: TargetCapabilityFlags) -> CompileUnit { + let mut unit = qsc_frontend::compile::std(store, capabilities); + let pass_errors = run_default_passes(store.core(), &mut unit, PackageType::Lib); + if pass_errors.is_empty() { + unit + } else { + for error in pass_errors { + let report = Report::new(WithSource::from_map(&unit.sources, error)); + eprintln!("{report:?}"); + } + + panic!("could not compile standard library") + } +} diff --git a/compiler/qsc_qasm/src/tests.rs b/compiler/qsc_qasm/src/tests.rs new file mode 100644 index 0000000000..04baf99fb3 --- /dev/null +++ b/compiler/qsc_qasm/src/tests.rs @@ -0,0 +1,489 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::io::{InMemorySourceResolver, SourceResolver}; +use crate::semantic::{parse_source, QasmSemanticParseResult}; +use crate::stdlib::compile::package_store_with_qasm; +use crate::{ + compile_to_qsharp_ast_with_config, CompilerConfig, OutputSemantics, ProgramType, + QasmCompileUnit, QubitSemantics, +}; +use miette::Report; +use qsc::compile::compile_ast; +use qsc::interpret::Error; +use qsc::target::Profile; +use qsc::{ + ast::{mut_visit::MutVisitor, Package, Stmt, TopLevelNode}, + SourceMap, Span, +}; +use qsc_hir::hir::PackageId; +use qsc_passes::PackageType; +use std::{path::Path, sync::Arc}; + +pub(crate) mod assignment; +pub(crate) mod declaration; +pub(crate) mod expression; +pub(crate) mod fuzz; +pub(crate) mod output; +pub(crate) mod sample_circuits; +pub(crate) mod scopes; +pub(crate) mod statement; + +pub(crate) fn fail_on_compilation_errors(unit: &QasmCompileUnit) { + if unit.has_errors() { + print_compilation_errors(unit); + panic!("Errors found in compilation"); + } +} + +pub(crate) fn print_compilation_errors(unit: &QasmCompileUnit) { + if unit.has_errors() { + for e in unit.errors() { + println!("{:?}", Report::new(e.clone())); + } + } +} + +pub(crate) fn gen_qsharp(package: &Package) -> String { + qsc::codegen::qsharp::write_package_string(package) +} + +/// Generates QIR from an AST package. +/// This function is used for testing purposes only. +/// The interactive environment uses a different mechanism to generate QIR. +/// As we need an entry expression to generate QIR in those cases. +/// +/// This function assumes that the AST package was designed as an entry point. +pub(crate) fn generate_qir_from_ast( + ast_package: Package, + source_map: SourceMap, + profile: Profile, +) -> Result> { + let capabilities = profile.into(); + let (stdid, qasmid, mut store) = package_store_with_qasm(capabilities); + let dependencies = vec![ + (PackageId::CORE, None), + (stdid, None), + (qasmid, Some("QasmStd".into())), + ]; + qsc::codegen::qir::get_qir_from_ast( + &mut store, + &dependencies, + ast_package, + source_map, + capabilities, + ) +} + +fn compile(source: S) -> miette::Result> +where + S: AsRef, +{ + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + compile_with_config(source, config) +} + +fn compile_with_config( + source: S, + config: CompilerConfig, +) -> miette::Result> +where + S: AsRef, +{ + let res = parse(source)?; + if res.has_syntax_errors() { + for e in res.sytax_errors() { + println!("{:?}", Report::new(e.clone())); + } + } + assert!(!res.has_syntax_errors()); + let program = res.program; + + let compiler = crate::compiler::QasmCompiler { + source_map: res.source_map, + config, + stmts: vec![], + symbols: res.symbols, + errors: res.errors, + }; + + let unit = compiler.compile(&program); + Ok(unit) +} + +#[allow(dead_code)] +pub fn compile_all

( + path: P, + sources: impl IntoIterator, Arc)>, +) -> miette::Result> +where + P: AsRef, +{ + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + compile_all_with_config(path, sources, config) +} + +#[allow(dead_code)] +pub fn compile_all_fragments

( + path: P, + sources: impl IntoIterator, Arc)>, +) -> miette::Result> +where + P: AsRef, +{ + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::Fragments, + None, + None, + ); + compile_all_with_config(path, sources, config) +} + +fn compile_fragments(source: S) -> miette::Result> +where + S: AsRef, +{ + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::Fragments, + None, + None, + ); + compile_with_config(source, config) +} + +pub fn compile_all_with_config

( + path: P, + sources: impl IntoIterator, Arc)>, + config: CompilerConfig, +) -> miette::Result> +where + P: AsRef, +{ + let res = parse_all(path, sources)?; + assert!(!res.has_syntax_errors()); + let program = res.program; + + let compiler = crate::compiler::QasmCompiler { + source_map: res.source_map, + config, + stmts: vec![], + symbols: res.symbols, + errors: res.errors, + }; + + let unit = compiler.compile(&program); + Ok(unit) +} + +fn compile_qasm_to_qir(source: &str, profile: Profile) -> Result> { + let unit = compile(source)?; + fail_on_compilation_errors(&unit); + let package = unit.package.expect("no package found"); + let qir = generate_qir_from_ast(package, unit.source_map, profile).map_err(|errors| { + errors + .iter() + .map(|e| Report::new(e.clone())) + .collect::>() + })?; + Ok(qir) +} + +/// used to do full compilation with best effort of the input. +/// This is useful for fuzz testing. +fn compile_qasm_best_effort(source: &str, profile: Profile) { + let (stdid, qasmid, store) = package_store_with_qasm(profile.into()); + + let mut resolver = InMemorySourceResolver::from_iter([]); + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::File, + Some("Fuzz".into()), + None, + ); + + let unit = + compile_to_qsharp_ast_with_config(source, "source.qasm", Some(&mut resolver), config); + let (sources, _, package, _) = unit.into_tuple(); + + let dependencies = vec![ + (PackageId::CORE, None), + (stdid, None), + (qasmid, Some("QasmStd".into())), + ]; + + let (mut _unit, _errors) = compile_ast( + &store, + &dependencies, + package.expect("package must exist"), + sources, + PackageType::Lib, + profile.into(), + ); +} + +pub(crate) fn gen_qsharp_stmt(stmt: &Stmt) -> String { + qsc::codegen::qsharp::write_stmt_string(stmt) +} + +#[allow(dead_code)] +pub(crate) fn compare_compilation_to_qsharp(unit: &QasmCompileUnit, expected: &str) { + let package = unit.package.as_ref().expect("package must exist"); + let despanned_ast = AstDespanner.despan(package); + let qsharp = gen_qsharp(&despanned_ast); + difference::assert_diff!(&qsharp, expected, "\n", 0); +} + +pub(crate) fn parse(source: S) -> miette::Result> +where + S: AsRef, +{ + let mut resolver = + InMemorySourceResolver::from_iter([("Test.qasm".into(), source.as_ref().into())]); + let res = parse_source(source, "Test.qasm", &mut resolver); + if res.source.has_errors() { + let errors = res + .errors() + .into_iter() + .map(|e| Report::new(e.clone())) + .collect(); + return Err(errors); + } + Ok(res) +} + +pub(crate) fn parse_all

( + path: P, + sources: impl IntoIterator, Arc)>, +) -> miette::Result> +where + P: AsRef, +{ + let mut resolver = InMemorySourceResolver::from_iter(sources); + let source = resolver + .resolve(path.as_ref()) + .map_err(|e| vec![Report::new(e)])? + .1; + let res = parse_source(source, path, &mut resolver); + if res.source.has_errors() { + let errors = res + .errors() + .into_iter() + .map(|e| Report::new(e.clone())) + .collect(); + Err(errors) + } else { + Ok(res) + } +} + +pub fn compile_qasm_to_qsharp_file(source: &str) -> miette::Result> { + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::File, + Some("Test".into()), + None, + ); + let unit = compile_with_config(source, config)?; + if unit.has_errors() { + let errors = unit.errors.into_iter().map(Report::new).collect(); + return Err(errors); + } + let Some(package) = unit.package else { + panic!("Expected package, got None"); + }; + let qsharp = gen_qsharp(&package); + Ok(qsharp) +} + +pub fn compile_qasm_to_qsharp_operation(source: &str) -> miette::Result> { + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::Operation, + Some("Test".into()), + None, + ); + let unit = compile_with_config(source, config)?; + if unit.has_errors() { + let errors = unit.errors.into_iter().map(Report::new).collect(); + return Err(errors); + } + let Some(package) = unit.package else { + panic!("Expected package, got None"); + }; + let qsharp = gen_qsharp(&package); + Ok(qsharp) +} + +pub fn compile_qasm_to_qsharp(source: &str) -> miette::Result> { + compile_qasm_to_qsharp_with_semantics(source, QubitSemantics::Qiskit) +} + +pub fn compile_qasm_to_qsharp_with_semantics( + source: &str, + qubit_semantics: QubitSemantics, +) -> miette::Result> { + let config = CompilerConfig::new( + qubit_semantics, + OutputSemantics::Qiskit, + ProgramType::Fragments, + None, + None, + ); + let unit = compile_with_config(source, config)?; + qsharp_from_qasm_compilation(unit) +} + +pub fn qsharp_from_qasm_compilation(unit: QasmCompileUnit) -> miette::Result> { + if unit.has_errors() { + let errors = unit.errors.into_iter().map(Report::new).collect(); + return Err(errors); + } + let Some(package) = unit.package else { + panic!("Expected package, got None"); + }; + let qsharp = gen_qsharp(&package); + Ok(qsharp) +} + +pub fn compile_qasm_stmt_to_qsharp(source: &str) -> miette::Result> { + compile_qasm_stmt_to_qsharp_with_semantics(source, QubitSemantics::Qiskit) +} + +pub fn compile_qasm_stmt_to_qsharp_with_semantics( + source: &str, + qubit_semantics: QubitSemantics, +) -> miette::Result> { + let config = CompilerConfig::new( + qubit_semantics, + OutputSemantics::Qiskit, + ProgramType::Fragments, + None, + None, + ); + let unit = compile_with_config(source, config)?; + if unit.has_errors() { + let errors = unit.errors.into_iter().map(Report::new).collect(); + return Err(errors); + } + let Some(package) = unit.package else { + panic!("Expected package, got None"); + }; + let qsharp = get_last_statement_as_qsharp(&package); + Ok(qsharp) +} + +fn get_last_statement_as_qsharp(package: &Package) -> String { + let qsharp = match package.nodes.iter().last() { + Some(i) => match i { + TopLevelNode::Namespace(_) => panic!("Expected Stmt, got Namespace"), + TopLevelNode::Stmt(stmt) => gen_qsharp_stmt(stmt.as_ref()), + }, + None => panic!("Expected Stmt, got None"), + }; + qsharp +} + +pub struct AstDespanner; +impl AstDespanner { + #[allow(dead_code)] // false positive lint + pub fn despan(&mut self, package: &Package) -> Package { + let mut p = package.clone(); + self.visit_package(&mut p); + p + } +} + +impl MutVisitor for AstDespanner { + fn visit_span(&mut self, span: &mut Span) { + span.hi = 0; + span.lo = 0; + } +} + +#[allow(dead_code)] +struct HirDespanner; +impl HirDespanner { + #[allow(dead_code)] + fn despan(&mut self, package: &qsc::hir::Package) -> qsc::hir::Package { + let mut p = package.clone(); + qsc::hir::mut_visit::MutVisitor::visit_package(self, &mut p); + p + } +} + +impl qsc::hir::mut_visit::MutVisitor for HirDespanner { + fn visit_span(&mut self, span: &mut Span) { + span.hi = 0; + span.lo = 0; + } +} + +mod qsharp { + use qsc_ast::ast::Package; + use qsc_data_structures::language_features::LanguageFeatures; + use qsc_frontend::compile::{parse_all, SourceMap}; + + pub(super) fn parse_package(sources: Option) -> Package { + let (ast_package, _) = parse_all(&sources.unwrap_or_default(), LanguageFeatures::empty()); + ast_package + } + + #[must_use] + pub(super) fn parse_program(program: &str) -> Package { + let sources = SourceMap::new([("test".into(), program.into())], None); + parse_package(Some(sources)) + } +} + +#[allow(dead_code)] +pub(crate) fn compare_qasm_and_qasharp_asts(source: &str) { + // 1. Generate a despaned QASM package. + let config = crate::CompilerConfig::new( + crate::QubitSemantics::Qiskit, + crate::OutputSemantics::Qiskit, + crate::ProgramType::File, + None, + None, + ); + let mut resolver = crate::io::InMemorySourceResolver::from_iter([]); + let unit = crate::compile_to_qsharp_ast_with_config( + source, + "source.qasm", + Some(&mut resolver), + config, + ); + let qasm_package = unit.package.as_ref().expect("package must exist"); + let despanned_qasm_ast = AstDespanner.despan(qasm_package); + + // 2. Generate Q# source from the QASM ast. + let qsharp_src = gen_qsharp(&despanned_qasm_ast); + + // 3. Generate a despaned Q# package using the Q# compiler. + let qsharp_package = qsharp::parse_program(&qsharp_src); + let despanned_qsharp_ast = AstDespanner.despan(&qsharp_package); + + // 4. Compare diffs between the ASTs generated by QASM and by Q#. + let despanned_qasm_ast = despanned_qasm_ast.to_string(); + let despanned_qsharp_ast = despanned_qsharp_ast.to_string(); + + difference::assert_diff!(&despanned_qasm_ast, &despanned_qsharp_ast, "\n", 0); +} diff --git a/compiler/qsc_qasm3/src/tests/assignment.rs b/compiler/qsc_qasm/src/tests/assignment.rs similarity index 70% rename from compiler/qsc_qasm3/src/tests/assignment.rs rename to compiler/qsc_qasm/src/tests/assignment.rs index cf454274f6..472deb327d 100644 --- a/compiler/qsc_qasm3/src/tests/assignment.rs +++ b/compiler/qsc_qasm/src/tests/assignment.rs @@ -3,7 +3,7 @@ mod alias; -use crate::tests::{fail_on_compilation_errors, parse, qasm_to_program_fragments}; +use crate::tests::{compile_fragments, fail_on_compilation_errors}; use miette::Report; #[test] @@ -22,9 +22,7 @@ fn classical() -> miette::Result<(), Vec> { b = a == 0; "; - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program_fragments(res.source, res.source_map); + let unit = compile_fragments(source)?; fail_on_compilation_errors(&unit); Ok(()) } @@ -45,15 +43,13 @@ fn quantum() -> miette::Result<(), Vec> { b = a == 0; "; - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program_fragments(res.source, res.source_map); + let unit = compile_fragments(source)?; fail_on_compilation_errors(&unit); Ok(()) } #[test] -#[ignore = "qasm3 parser does not support old-style decls yet"] +#[ignore = "qasm parser does not support old-style decls yet"] fn classical_old_style_decls() -> miette::Result<(), Vec> { let source = " bit[2] a; @@ -68,9 +64,7 @@ fn classical_old_style_decls() -> miette::Result<(), Vec> { b = a == 0; "; - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program_fragments(res.source, res.source_map); + let unit = compile_fragments(source)?; fail_on_compilation_errors(&unit); Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/assignment/alias.rs b/compiler/qsc_qasm/src/tests/assignment/alias.rs similarity index 63% rename from compiler/qsc_qasm3/src/tests/assignment/alias.rs rename to compiler/qsc_qasm/src/tests/assignment/alias.rs index 830646fc2b..4cfc5a79dc 100644 --- a/compiler/qsc_qasm3/src/tests/assignment/alias.rs +++ b/compiler/qsc_qasm/src/tests/assignment/alias.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::tests::{fail_on_compilation_errors, parse, qasm_to_program_fragments}; +use crate::tests::{compile_fragments, fail_on_compilation_errors}; use miette::Report; #[test] @@ -13,9 +13,7 @@ fn classical() -> miette::Result<(), Vec> { let c = a[{0,1}] ++ b[1:2]; "; - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program_fragments(res.source, res.source_map); + let unit = compile_fragments(source)?; fail_on_compilation_errors(&unit); Ok(()) } @@ -34,15 +32,13 @@ fn quantum() -> miette::Result<(), Vec> { let e = d[1]; "; - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program_fragments(res.source, res.source_map); + let unit = compile_fragments(source)?; fail_on_compilation_errors(&unit); Ok(()) } #[test] -#[ignore = "qasm3 parser does not support old-style decls yet"] +#[ignore = "qasm parser does not support old-style decls yet"] fn classical_old_style_decls() -> miette::Result<(), Vec> { let source = " creg a[2]; @@ -50,15 +46,13 @@ fn classical_old_style_decls() -> miette::Result<(), Vec> { let c = a[{0,1}] ++ b[1:2]; "; - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program_fragments(res.source, res.source_map); + let unit = compile_fragments(source)?; fail_on_compilation_errors(&unit); Ok(()) } #[test] -#[ignore = "qasm3 parser does not support old-style decls yet"] +#[ignore = "qasm parser does not support old-style decls yet"] fn quantum_old_style_decls() -> miette::Result<(), Vec> { let source = " qreg q1[5]; @@ -71,9 +65,7 @@ fn quantum_old_style_decls() -> miette::Result<(), Vec> { let e = d[1]; "; - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program_fragments(res.source, res.source_map); + let unit = compile_fragments(source)?; fail_on_compilation_errors(&unit); Ok(()) } diff --git a/compiler/qsc_qasm/src/tests/declaration.rs b/compiler/qsc_qasm/src/tests/declaration.rs new file mode 100644 index 0000000000..7d6f7e6434 --- /dev/null +++ b/compiler/qsc_qasm/src/tests/declaration.rs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +mod array; +mod bit; +mod bool; +mod complex; +mod def; +mod float; +mod gate; +mod integer; +mod io; +mod qubit; +mod unsigned_integer; + +use crate::{ + tests::{compile_fragments, compile_with_config, fail_on_compilation_errors}, + CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, +}; +use miette::Report; +use qsc::target::Profile; + +use super::compile_qasm_best_effort; + +#[test] +fn classical() -> miette::Result<(), Vec> { + let source = r#" + int[10] a; + int[10] b; + uint[32] c = 0xFa_1F; + uint[32] d = 0XFa_1F; + uint[16] e = 0o12_34; + uint[16] f = 0b1001_1001; + uint[16] g = 0B1001_1001; + uint h; + qubit[6] q1; + qubit q2; + bit[4] b1 = "0100"; + bit[8] b2 = "1001_0100"; + bool i = true; + bool j = false; + const float[64] k = 5.5e3; + const float[64] l = 5; + float[32] m = .1e+3; + "#; + + let unit = compile_fragments(source)?; + fail_on_compilation_errors(&unit); + Ok(()) +} + +#[test] +fn duration_literal() -> miette::Result<(), Vec> { + let source = " + duration dur0; + duration dur1 = 1000dt; + duration dur2 = 10 ms; + duration dur3 = 8 us; + duration dur4 = 1s; + "; + + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::Fragments, + None, + None, + ); + let unit = compile_with_config(source, config).expect("parse failed"); + for error in &unit.errors { + println!("{error}"); + } + assert_eq!(unit.errors.len(), 10); + for error in &unit.errors { + assert!([ + "duration type values are not supported", + "timing literals are not supported", + ] + .contains(&error.to_string().as_str())); + } + + Ok(()) +} + +#[test] +fn stretch() { + let source = " + stretch s; + "; + + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::Fragments, + None, + None, + ); + let unit = compile_with_config(source, config).expect("parse failed"); + assert!(unit.has_errors()); + for error in &unit.errors { + println!("{error}"); + } + assert!(unit.errors.len() == 2); + assert!(unit.errors[0] + .to_string() + .contains("stretch type values are not supported"),); + assert!(unit.errors[1] + .to_string() + .contains("stretch default values are not supported"),); +} + +#[test] +fn gate_decl_with_missing_seq_item_doesnt_panic() { + let source = r#"gate g1 x,,y {}"#; + compile_qasm_best_effort(source, Profile::Unrestricted); +} diff --git a/compiler/qsc_qasm3/src/tests/declaration/array.rs b/compiler/qsc_qasm/src/tests/declaration/array.rs similarity index 79% rename from compiler/qsc_qasm3/src/tests/declaration/array.rs rename to compiler/qsc_qasm/src/tests/declaration/array.rs index a6e2a874e5..344d89eb6b 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/array.rs +++ b/compiler/qsc_qasm/src/tests/declaration/array.rs @@ -4,7 +4,7 @@ mod bit; mod qubit; -use crate::tests::{fail_on_compilation_errors, parse, qasm_to_program_fragments}; +use crate::tests::{compile_fragments, fail_on_compilation_errors}; use miette::Report; #[test] @@ -28,9 +28,7 @@ fn arrays() -> miette::Result<(), Vec> { array[uint[32], 2, 2] x = y; "; - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program_fragments(res.source, res.source_map); + let unit = compile_fragments(source)?; fail_on_compilation_errors(&unit); Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/declaration/array/bit.rs b/compiler/qsc_qasm/src/tests/declaration/array/bit.rs similarity index 100% rename from compiler/qsc_qasm3/src/tests/declaration/array/bit.rs rename to compiler/qsc_qasm/src/tests/declaration/array/bit.rs diff --git a/compiler/qsc_qasm3/src/tests/declaration/array/qubit.rs b/compiler/qsc_qasm/src/tests/declaration/array/qubit.rs similarity index 100% rename from compiler/qsc_qasm3/src/tests/declaration/array/qubit.rs rename to compiler/qsc_qasm/src/tests/declaration/array/qubit.rs diff --git a/compiler/qsc_qasm3/src/tests/declaration/bit.rs b/compiler/qsc_qasm/src/tests/declaration/bit.rs similarity index 100% rename from compiler/qsc_qasm3/src/tests/declaration/bit.rs rename to compiler/qsc_qasm/src/tests/declaration/bit.rs diff --git a/compiler/qsc_qasm3/src/tests/declaration/bool.rs b/compiler/qsc_qasm/src/tests/declaration/bool.rs similarity index 84% rename from compiler/qsc_qasm3/src/tests/declaration/bool.rs rename to compiler/qsc_qasm/src/tests/declaration/bool.rs index a3bca38d69..18f7c24ce5 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/bool.rs +++ b/compiler/qsc_qasm/src/tests/declaration/bool.rs @@ -22,22 +22,6 @@ fn bool_default_decl() -> miette::Result<(), Vec> { Ok(()) } -#[test] -fn const_bool_default_decl() -> miette::Result<(), Vec> { - let source = " - const bool x; - "; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let x = false; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - #[test] fn bool_true_decl() -> miette::Result<(), Vec> { let source = " diff --git a/compiler/qsc_qasm3/src/tests/declaration/complex.rs b/compiler/qsc_qasm/src/tests/declaration/complex.rs similarity index 91% rename from compiler/qsc_qasm3/src/tests/declaration/complex.rs rename to compiler/qsc_qasm/src/tests/declaration/complex.rs index 88567b1478..039b17c667 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/complex.rs +++ b/compiler/qsc_qasm/src/tests/declaration/complex.rs @@ -22,22 +22,6 @@ fn implicit_bitness_default_decl() -> miette::Result<(), Vec> { Ok(()) } -#[test] -fn const_implicit_bitness_default_decl() -> miette::Result<(), Vec> { - let source = " - const complex[float] x; - "; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let x = Microsoft.Quantum.Math.Complex(0., 0.); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - #[test] fn explicit_bitness_default_decl() -> miette::Result<(), Vec> { let source = " @@ -54,22 +38,6 @@ fn explicit_bitness_default_decl() -> miette::Result<(), Vec> { Ok(()) } -#[test] -fn const_explicit_bitness_default_decl() -> miette::Result<(), Vec> { - let source = " - const complex[float[42]] x; - "; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let x = Microsoft.Quantum.Math.Complex(0., 0.); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - #[test] fn const_implicit_bitness_double_img_only_decl() -> miette::Result<(), Vec> { let source = " diff --git a/compiler/qsc_qasm/src/tests/declaration/def.rs b/compiler/qsc_qasm/src/tests/declaration/def.rs new file mode 100644 index 0000000000..96028ca038 --- /dev/null +++ b/compiler/qsc_qasm/src/tests/declaration/def.rs @@ -0,0 +1,258 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::tests::compile_qasm_stmt_to_qsharp; +use expect_test::expect; +use miette::Report; + +#[test] +fn no_parameters_no_return() -> miette::Result<(), Vec> { + let source = "def empty() {}"; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + function empty() : Unit {} + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn single_parameter() -> miette::Result<(), Vec> { + let source = r#" + def square(int x) -> int { + return x * x; + } + "#; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + function square(x : Int) : Int { + return x * x; + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn qubit_parameter() -> miette::Result<(), Vec> { + let source = r#" + def square(qubit q) -> uint { + return 1; + } + "#; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + operation square(q : Qubit) : Int { + return 1; + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn qubit_array_parameter() -> miette::Result<(), Vec> { + let source = r#" + def square(qubit[3] qs) -> uint { + return 1; + } + "#; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + operation square(qs : Qubit[]) : Int { + return 1; + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn implicit_cast_to_function_return_type() -> miette::Result<(), Vec> { + let source = r#" + def square(int a) -> bit { + return a; + } + "#; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + function square(a : Int) : Result { + return if a == 0 { + One + } else { + Zero + }; + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn return_from_void_function() -> miette::Result<(), Vec> { + let source = r#" + def square(int a) { + return; + } + "#; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + function square(a : Int) : Unit { + return (); + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn return_expr_on_void_function_fails() { + let source = r#" + def square(int val) { + return val; + } + "#; + + let Err(errors) = compile_qasm_stmt_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Lowerer.ReturningExpressionFromVoidSubroutine + + x cannot return an expression from a void subroutine + ,-[Test.qasm:3:20] + 2 | def square(int val) { + 3 | return val; + : ^^^ + 4 | } + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} + +#[test] +fn missing_return_expr_on_non_void_function_fails() { + let source = r#" + def square(int a) -> bit { + return; + } + "#; + + let Err(errors) = compile_qasm_stmt_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Lowerer.MissingTargetExpressionInReturnStmt + + x return statements on a non-void subroutine should have a target expression + ,-[Test.qasm:3:13] + 2 | def square(int a) -> bit { + 3 | return; + : ^^^^^^^ + 4 | } + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} + +#[test] +fn capturing_external_variables_const_evaluate_them() -> miette::Result<(), Vec> { + let source = r#" + const int a = 2; + const int b = 3; + const int c = a * b; + def f() -> int { + return c; + } + "#; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + function f() : Int { + return 6; + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn capturing_non_const_external_variable_fails() { + let source = r#" + int a = 2 << (-3); + def f() -> int { + return a; + } + "#; + + let Err(errors) = compile_qasm_stmt_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Lowerer.UndefinedSymbol + + x undefined symbol: a + ,-[Test.qasm:4:20] + 3 | def f() -> int { + 4 | return a; + : ^ + 5 | } + `---- + , Qasm.Lowerer.CannotCast + + x cannot cast expression of type Err to type Int(None, false) + ,-[Test.qasm:4:20] + 3 | def f() -> int { + 4 | return a; + : ^ + 5 | } + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} + +#[test] +fn capturing_non_const_evaluatable_external_variable_fails() { + let source = r#" + const int a = 2 << (-3); + def f() -> int { + return a; + } + "#; + + let Err(errors) = compile_qasm_stmt_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Compiler.NegativeUIntValue + + x uint expression must evaluate to a non-negative value, but it evaluated + | to -3 + ,-[Test.qasm:2:28] + 1 | + 2 | const int a = 2 << (-3); + : ^^^^ + 3 | def f() -> int { + `---- + , Qasm.Lowerer.ExprMustBeConst + + x a captured variable must be a const expression + ,-[Test.qasm:4:20] + 3 | def f() -> int { + 4 | return a; + : ^ + 5 | } + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} diff --git a/compiler/qsc_qasm3/src/tests/declaration/float.rs b/compiler/qsc_qasm/src/tests/declaration/float.rs similarity index 93% rename from compiler/qsc_qasm3/src/tests/declaration/float.rs rename to compiler/qsc_qasm/src/tests/declaration/float.rs index 5b4eced981..2f7ca8a970 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/float.rs +++ b/compiler/qsc_qasm/src/tests/declaration/float.rs @@ -22,22 +22,6 @@ fn implicit_bitness_default_decl() -> miette::Result<(), Vec> { Ok(()) } -#[test] -fn const_default_decl() -> miette::Result<(), Vec> { - let source = " - const float x; - "; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let x = 0.; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - #[test] fn lit_decl() -> miette::Result<(), Vec> { let source = " @@ -103,7 +87,6 @@ fn const_explicit_width_lit_decl() -> miette::Result<(), Vec> { } #[test] -#[ignore = "oq3 parser bug, can't read float with leading dot"] fn lit_decl_leading_dot() -> miette::Result<(), Vec> { let source = " float x = .421; @@ -120,7 +103,6 @@ fn lit_decl_leading_dot() -> miette::Result<(), Vec> { } #[test] -#[ignore = "oq3 parser bug, can't read float with leading dot"] fn const_lit_decl_leading_dot() -> miette::Result<(), Vec> { let source = " const float x = .421; @@ -137,7 +119,6 @@ fn const_lit_decl_leading_dot() -> miette::Result<(), Vec> { } #[test] -#[ignore = "oq3 parser bug, can't read float with leading dot"] fn const_lit_decl_leading_dot_scientific() -> miette::Result<(), Vec> { let source = " const float x = .421e2; @@ -338,7 +319,23 @@ fn const_lit_decl_signed_int_lit_cast_neg() -> miette::Result<(), Vec> { let qsharp = compile_qasm_stmt_to_qsharp(source)?; expect![ r#" - let x = -7.; + let x = Microsoft.Quantum.Convert.IntAsDouble(-7); + "# + ] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn init_float_with_int_value_less_than_safely_representable_values_is_runtime_conversion( +) -> miette::Result<(), Vec> { + let min_exact_int = -(2i64.pow(f64::MANTISSA_DIGITS)); + let next = min_exact_int - 1; + let source = &format!("float a = {next};"); + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![ + r#" + mutable a = Microsoft.Quantum.Convert.IntAsDouble(-9007199254740993); "# ] .assert_eq(&qsharp); diff --git a/compiler/qsc_qasm/src/tests/declaration/gate.rs b/compiler/qsc_qasm/src/tests/declaration/gate.rs new file mode 100644 index 0000000000..a38043758c --- /dev/null +++ b/compiler/qsc_qasm/src/tests/declaration/gate.rs @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::tests::compile_qasm_stmt_to_qsharp; +use expect_test::expect; +use miette::Report; + +#[test] +fn single_qubit() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + gate my_h q { + h q; + } + "#; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + operation my_h(q : Qubit) : Unit is Adj + Ctl { + h(q); + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn two_qubits() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + gate my_h q, q2 { + h q2; + h q; + } + "#; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + operation my_h(q : Qubit, q2 : Qubit) : Unit is Adj + Ctl { + h(q2); + h(q); + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn single_angle_single_qubit() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + gate my_h(θ) q { + rx(θ) q; + } + "#; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + operation my_h(θ : __Angle__, q : Qubit) : Unit is Adj + Ctl { + rx(θ, q); + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn two_angles_two_qubits() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + gate my_h(θ, φ) q, q2 { + rx(θ) q2; + ry(φ) q; + } + "#; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + operation my_h(θ : __Angle__, φ : __Angle__, q : Qubit, q2 : Qubit) : Unit is Adj + Ctl { + rx(θ, q2); + ry(φ, q); + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn capturing_external_variables_const_evaluate_them() -> miette::Result<(), Vec> { + let source = r#" + const int a = 2; + const int b = 3; + const int c = a * b; + gate my_gate q { + int x = c; + } + "#; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + operation my_gate(q : Qubit) : Unit is Adj + Ctl { + mutable x = 6; + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn capturing_non_const_external_variable_fails() { + let source = r#" + int a = 2 << (-3); + gate my_gate q { + int x = a; + } + "#; + + let Err(errors) = compile_qasm_stmt_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Lowerer.UndefinedSymbol + + x undefined symbol: a + ,-[Test.qasm:4:21] + 3 | gate my_gate q { + 4 | int x = a; + : ^ + 5 | } + `---- + , Qasm.Lowerer.CannotCast + + x cannot cast expression of type Err to type Int(None, false) + ,-[Test.qasm:4:21] + 3 | gate my_gate q { + 4 | int x = a; + : ^ + 5 | } + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} + +#[test] +fn capturing_non_const_evaluatable_external_variable_fails() { + let source = r#" + const int a = 2 << (-3); + gate my_gate q { + int x = a; + } + "#; + + let Err(errors) = compile_qasm_stmt_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Compiler.NegativeUIntValue + + x uint expression must evaluate to a non-negative value, but it evaluated + | to -3 + ,-[Test.qasm:2:28] + 1 | + 2 | const int a = 2 << (-3); + : ^^^^ + 3 | gate my_gate q { + `---- + , Qasm.Lowerer.ExprMustBeConst + + x a captured variable must be a const expression + ,-[Test.qasm:4:21] + 3 | gate my_gate q { + 4 | int x = a; + : ^ + 5 | } + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} diff --git a/compiler/qsc_qasm3/src/tests/declaration/integer.rs b/compiler/qsc_qasm/src/tests/declaration/integer.rs similarity index 70% rename from compiler/qsc_qasm3/src/tests/declaration/integer.rs rename to compiler/qsc_qasm/src/tests/declaration/integer.rs index e4d9e1f9a0..09ba4498b6 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/integer.rs +++ b/compiler/qsc_qasm/src/tests/declaration/integer.rs @@ -54,22 +54,6 @@ fn implicit_bitness_int_default_decl() -> miette::Result<(), Vec> { Ok(()) } -#[test] -fn const_implicit_bitness_int_default_decl() -> miette::Result<(), Vec> { - let source = " - const int x; - "; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let x = 0; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - #[test] fn const_implicit_bitness_int_lit_decl() -> miette::Result<(), Vec> { let source = " @@ -87,7 +71,6 @@ fn const_implicit_bitness_int_lit_decl() -> miette::Result<(), Vec> { } #[test] -#[ignore = "oq3 parser bug, capital X is not recognized as hex"] fn implicit_bitness_int_hex_cap_decl() -> miette::Result<(), Vec> { let source = " int x = 0XFa_1F; @@ -120,10 +103,9 @@ fn const_implicit_bitness_int_hex_low_decl() -> miette::Result<(), Vec> } #[test] -#[ignore = "oq3 parser bug, capital X is not recognized as hex"] fn const_implicit_bitness_int_hex_cap_decl() -> miette::Result<(), Vec> { let source = " - const int y = 0XFa_1F; + const int x = 0XFa_1F; "; let qsharp = compile_qasm_stmt_to_qsharp(source)?; @@ -185,7 +167,6 @@ fn implicit_bitness_int_binary_low_decl() -> miette::Result<(), Vec> { } #[test] -#[ignore = "oq3 parser bug, capital B is not recognized as binary"] fn implicit_bitness_int_binary_cap_decl() -> miette::Result<(), Vec> { let source = " int x = 0B1010; @@ -218,7 +199,6 @@ fn const_implicit_bitness_int_binary_low_decl() -> miette::Result<(), Vec miette::Result<(), Vec> { let source = " const int x = 0B1010; @@ -282,22 +262,6 @@ fn explicit_bitness_int_default_decl() -> miette::Result<(), Vec> { Ok(()) } -#[test] -fn const_explicit_bitness_int_default_decl() -> miette::Result<(), Vec> { - let source = " - const int[10] x; - "; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let x = 0; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - #[test] fn explicit_bitness_int_decl() -> miette::Result<(), Vec> { let source = " @@ -331,61 +295,18 @@ fn const_explicit_bitness_int_decl() -> miette::Result<(), Vec> { } #[test] -fn assigning_uint_to_negative_lit_results_in_semantic_error() { - let source = " - const uint[10] x = -42; - "; - - let Err(errors) = compile_qasm_stmt_to_qsharp(source) else { - panic!("Expected error"); - }; - expect![ - r#"Cannot assign a value of Negative Int type to a classical variable of UInt(Some(10), True) type."# - ] - .assert_eq(&errors[0].to_string()); -} - -#[test] -fn implicit_bitness_uint_const_negative_decl_raises_semantic_error() { - let source = " - const uint x = -42; - "; - - let Err(errors) = compile_qasm_stmt_to_qsharp(source) else { - panic!("Expected error"); - }; - expect![ - r#"Cannot assign a value of Negative Int type to a classical variable of UInt(None, True) type."# - ] - .assert_eq(&errors[0].to_string()); -} - -#[test] -fn explicit_bitness_uint_const_negative_decl_raises_semantic_error() { - let source = " - const uint[32] x = -42; - "; - - let Err(errors) = compile_qasm_stmt_to_qsharp(source) else { - panic!("Expected error"); - }; - expect![ - r#"Cannot assign a value of Negative Int type to a classical variable of UInt(Some(32), True) type."# - ] - .assert_eq(&errors[0].to_string()); -} - -#[test] -fn implicit_bitness_int_negative_float_decl_causes_semantic_error() { +fn implicit_bitness_int_negative_float_decl_creates_truncation_call( +) -> miette::Result<(), Vec> { let source = " int x = -42.; "; - let Err(errors) = compile_qasm_stmt_to_qsharp(source) else { - panic!("Expected error"); - }; + let qsharp = compile_qasm_stmt_to_qsharp(source)?; expect![ - r#"Cannot assign a value of Float(None, True) type to a classical variable of Int(None, False) type."# + r#" + mutable x = Microsoft.Quantum.Math.Truncate(-42.); + "# ] - .assert_eq(&errors[0].to_string()); + .assert_eq(&qsharp); + Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/declaration/io.rs b/compiler/qsc_qasm/src/tests/declaration/io.rs similarity index 100% rename from compiler/qsc_qasm3/src/tests/declaration/io.rs rename to compiler/qsc_qasm/src/tests/declaration/io.rs diff --git a/compiler/qsc_qasm3/src/tests/declaration/io/explicit_input.rs b/compiler/qsc_qasm/src/tests/declaration/io/explicit_input.rs similarity index 52% rename from compiler/qsc_qasm3/src/tests/declaration/io/explicit_input.rs rename to compiler/qsc_qasm/src/tests/declaration/io/explicit_input.rs index f479416272..afd9795fbe 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/io/explicit_input.rs +++ b/compiler/qsc_qasm/src/tests/declaration/io/explicit_input.rs @@ -13,11 +13,13 @@ input bit[2] c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -operation Test(c : Result[]) : Unit {} -"# - ] + expect![[r#" + operation Test(c : Result[]) : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -30,11 +32,13 @@ input bit c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -operation Test(c : Result) : Unit {} -"# - ] + expect![[r#" + operation Test(c : Result) : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -46,11 +50,13 @@ input bool c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -operation Test(c : Bool) : Unit {} -"# - ] + expect![[r#" + operation Test(c : Bool) : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -62,11 +68,13 @@ input complex[float] c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -operation Test(c : Microsoft.Quantum.Math.Complex) : Unit {} -"# - ] + expect![[r#" + operation Test(c : Microsoft.Quantum.Math.Complex) : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -78,11 +86,13 @@ input float f; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -operation Test(f : Double) : Unit {} -"# - ] + expect![[r#" + operation Test(f : Double) : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -94,11 +104,13 @@ input float[60] f; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -operation Test(f : Double) : Unit {} -"# - ] + expect![[r#" + operation Test(f : Double) : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -110,11 +122,13 @@ input int i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -operation Test(i : Int) : Unit {} -"# - ] + expect![[r#" + operation Test(i : Int) : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -126,11 +140,13 @@ input int[60] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -operation Test(i : Int) : Unit {} -"# - ] + expect![[r#" + operation Test(i : Int) : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -142,11 +158,13 @@ input uint i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -operation Test(i : Int) : Unit {} -"# - ] + expect![[r#" + operation Test(i : Int) : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -158,11 +176,13 @@ input uint[60] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -operation Test(i : Int) : Unit {} -"# - ] + expect![[r#" + operation Test(i : Int) : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -174,11 +194,13 @@ input int[65] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -operation Test(i : BigInt) : Unit {} -"# - ] + expect![[r#" + operation Test(i : BigInt) : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -195,7 +217,23 @@ input qubit q; assert!(error[0] .to_string() - .contains("QASM3 Parse Error: Quantum type found in input/output declaration.")); + .contains("expected scalar or array type, found keyword `qubit`")); +} + +#[test] +fn lifting_angle_raises_compile_error() { + let source = r#" +input angle a; +"#; + + let Err(error) = compile_qasm_to_qsharp_operation(source) else { + panic!("Expected error") + }; + + assert_eq!( + error[0].to_string(), + "use `float` types for passing input, using `angle` types are not supported" + ); } #[test] @@ -213,11 +251,13 @@ input bit[2] b2; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -operation Test(bi : BigInt, i : Int, ui : Int, u : Int, f : Double, b : Bool, c : Result, cf : Microsoft.Quantum.Math.Complex, b2 : Result[]) : Unit {} -"# - ] + expect![[r#" + operation Test(bi : BigInt, i : Int, ui : Int, u : Int, f : Double, b : Bool, c : Result, cf : Microsoft.Quantum.Math.Complex, b2 : Result[]) : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + } + "#]] .assert_eq(&qsharp); Ok(()) } diff --git a/compiler/qsc_qasm/src/tests/declaration/io/explicit_output.rs b/compiler/qsc_qasm/src/tests/declaration/io/explicit_output.rs new file mode 100644 index 0000000000..ea8ac75890 --- /dev/null +++ b/compiler/qsc_qasm/src/tests/declaration/io/explicit_output.rs @@ -0,0 +1,300 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::tests::compile_qasm_to_qsharp_operation; +use expect_test::expect; +use miette::Report; + +#[test] +fn bit_array_is_returned() -> miette::Result<(), Vec> { + let source = r#" +output bit[2] c; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Result[] { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = [Zero, Zero]; + c + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn bit_is_returned() -> miette::Result<(), Vec> { + let source = r#" +output bit c; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Result { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = Zero; + c + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn bool_is_returned() -> miette::Result<(), Vec> { + let source = r#" +output bool c; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Bool { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = false; + c + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn complex_is_returned() -> miette::Result<(), Vec> { + let source = r#" +output complex[float] c; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Microsoft.Quantum.Math.Complex { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = Microsoft.Quantum.Math.Complex(0., 0.); + c + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn float_implicit_width_is_returned() -> miette::Result<(), Vec> { + let source = r#" +output float f; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable f = 0.; + f + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn float_explicit_width_is_returned() -> miette::Result<(), Vec> { + let source = r#" +output float[42] f; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable f = 0.; + f + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn int_explicit_width_is_returned() -> miette::Result<(), Vec> { + let source = r#" +output int[42] i; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn int_implicit_width_is_returned() -> miette::Result<(), Vec> { + let source = r#" +output int i; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn uint_implicit_width_is_returned() -> miette::Result<(), Vec> { + let source = r#" +output uint i; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn uint_explicit_width_is_returned() -> miette::Result<(), Vec> { + let source = r#" +output uint[42] i; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn bigint_explicit_width_is_returned() -> miette::Result<(), Vec> { + let source = r#" +output int[65] i; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : BigInt { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn qubit_explicit_output_raises_parse_error() { + let source = r#" +output qubit q; +"#; + + let Err(error) = compile_qasm_to_qsharp_operation(source) else { + panic!("Expected error") + }; + + assert!(error[0] + .to_string() + .contains("expected scalar or array type, found keyword `qubit")); +} + +#[test] +fn order_is_preserved_with_multiple_inputs() -> miette::Result<(), Vec> { + let source = r#" +output int[65] bi; +output int[6] i; +output uint[60] ui; +output uint u; +output float f; +output bool b; +output bit c; +output complex[float] cf; +output bit[2] b2; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : (BigInt, Int, Int, Int, Double, Bool, Result, Microsoft.Quantum.Math.Complex, Result[]) { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable bi = 0; + mutable i = 0; + mutable ui = 0; + mutable u = 0; + mutable f = 0.; + mutable b = false; + mutable c = Zero; + mutable cf = Microsoft.Quantum.Math.Complex(0., 0.); + mutable b2 = [Zero, Zero]; + (bi, i, ui, u, f, b, c, cf, b2) + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn angle_explicit_returned_as_double() -> miette::Result<(), Vec> { + let source = r#" +output angle c; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = new __Angle__ { + Value = 0, + Size = 53 + }; + __AngleAsDouble__(c) + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} diff --git a/compiler/qsc_qasm/src/tests/declaration/io/implicit_output.rs b/compiler/qsc_qasm/src/tests/declaration/io/implicit_output.rs new file mode 100644 index 0000000000..79b7291ec8 --- /dev/null +++ b/compiler/qsc_qasm/src/tests/declaration/io/implicit_output.rs @@ -0,0 +1,285 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::tests::compile_qasm_to_qsharp_operation; +use expect_test::expect; +use miette::Report; + +#[test] +fn bit_array_is_inferred_and_returned() -> miette::Result<(), Vec> { + let source = r#" +bit[2] c; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Result[] { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = [Zero, Zero]; + c + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn bit_is_inferred_and_returned() -> miette::Result<(), Vec> { + let source = r#" +bit c; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Result { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = Zero; + c + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn bool_is_inferred_and_returned() -> miette::Result<(), Vec> { + let source = r#" +bool c; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Bool { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = false; + c + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn complex_is_inferred_and_returned() -> miette::Result<(), Vec> { + let source = r#" +complex[float] c; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Microsoft.Quantum.Math.Complex { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = Microsoft.Quantum.Math.Complex(0., 0.); + c + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn float_implicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { + let source = r#" +float f; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable f = 0.; + f + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn float_explicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { + let source = r#" +float[42] f; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable f = 0.; + f + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn int_explicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { + let source = r#" +int[42] i; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn int_implicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { + let source = r#" +int i; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn uint_implicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { + let source = r#" +uint i; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn uint_explicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { + let source = r#" +uint[42] i; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn bigint_explicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { + let source = r#" +int[65] i; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : BigInt { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn order_is_preserved_with_multiple_inputs() -> miette::Result<(), Vec> { + let source = r#" +int[65] bi; +int[6] i; +uint[60] ui; +uint u; +float f; +bool b; +bit c; +complex[float] cf; +bit[2] b2; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : (BigInt, Int, Int, Int, Double, Bool, Result, Microsoft.Quantum.Math.Complex, Result[]) { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable bi = 0; + mutable i = 0; + mutable ui = 0; + mutable u = 0; + mutable f = 0.; + mutable b = false; + mutable c = Zero; + mutable cf = Microsoft.Quantum.Math.Complex(0., 0.); + mutable b2 = [Zero, Zero]; + (bi, i, ui, u, f, b, c, cf, b2) + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn angle_is_inferred_and_returned_as_double() -> miette::Result<(), Vec> { + let source = r#" +angle c; +"#; + + let qsharp = compile_qasm_to_qsharp_operation(source)?; + expect![[r#" + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = new __Angle__ { + Value = 0, + Size = 53 + }; + __AngleAsDouble__(c) + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} diff --git a/compiler/qsc_qasm3/src/tests/declaration/qubit.rs b/compiler/qsc_qasm/src/tests/declaration/qubit.rs similarity index 83% rename from compiler/qsc_qasm3/src/tests/declaration/qubit.rs rename to compiler/qsc_qasm/src/tests/declaration/qubit.rs index de647e60c7..15ec73d002 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/qubit.rs +++ b/compiler/qsc_qasm/src/tests/declaration/qubit.rs @@ -4,7 +4,7 @@ use expect_test::expect; use miette::Report; -use crate::tests::{fail_on_compilation_errors, parse, qasm_to_program_fragments}; +use crate::tests::{compile_fragments, fail_on_compilation_errors}; use crate::{ tests::{compile_qasm_stmt_to_qsharp, compile_qasm_stmt_to_qsharp_with_semantics}, QubitSemantics, @@ -17,9 +17,7 @@ fn quantum() -> miette::Result<(), Vec> { qubit q2; "; - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program_fragments(res.source, res.source_map); + let unit = compile_fragments(source)?; fail_on_compilation_errors(&unit); Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs b/compiler/qsc_qasm/src/tests/declaration/unsigned_integer.rs similarity index 83% rename from compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs rename to compiler/qsc_qasm/src/tests/declaration/unsigned_integer.rs index f5e1695c35..e35fe8a539 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs +++ b/compiler/qsc_qasm/src/tests/declaration/unsigned_integer.rs @@ -22,22 +22,6 @@ fn implicit_bitness_int_default_decl() -> miette::Result<(), Vec> { Ok(()) } -#[test] -fn const_implicit_bitness_int_default_decl() -> miette::Result<(), Vec> { - let source = " - const uint x; - "; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let x = 0; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - #[test] fn const_implicit_bitness_int_lit_decl() -> miette::Result<(), Vec> { let source = " @@ -55,7 +39,6 @@ fn const_implicit_bitness_int_lit_decl() -> miette::Result<(), Vec> { } #[test] -#[ignore = "oq3 parser bug, capital X is not recognized as hex"] fn implicit_bitness_int_hex_cap_decl() -> miette::Result<(), Vec> { let source = " uint x = 0XFa_1F; @@ -88,10 +71,9 @@ fn const_implicit_bitness_int_hex_low_decl() -> miette::Result<(), Vec> } #[test] -#[ignore = "oq3 parser bug, capital X is not recognized as hex"] fn const_implicit_bitness_int_hex_cap_decl() -> miette::Result<(), Vec> { let source = " - const uint y = 0XFa_1F; + const uint x = 0XFa_1F; "; let qsharp = compile_qasm_stmt_to_qsharp(source)?; @@ -153,7 +135,6 @@ fn implicit_bitness_int_binary_low_decl() -> miette::Result<(), Vec> { } #[test] -#[ignore = "oq3 parser bug, capital B is not recognized as binary"] fn implicit_bitness_int_binary_cap_decl() -> miette::Result<(), Vec> { let source = " uint x = 0B1010; @@ -186,7 +167,6 @@ fn const_implicit_bitness_int_binary_low_decl() -> miette::Result<(), Vec miette::Result<(), Vec> { let source = " const uint x = 0B1010; @@ -251,17 +231,17 @@ fn explicit_bitness_int_decl() -> miette::Result<(), Vec> { } #[test] -fn const_explicit_bitness_int_decl() -> miette::Result<(), Vec> { +#[ignore = "not implemented"] +fn assigning_uint_to_negative_lit_results_in_semantic_error() { let source = " - const uint[10] x; + const uint[10] x = -42; "; - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let x = 0; - "# - ] - .assert_eq(&qsharp); - Ok(()) + let Err(errors) = compile_qasm_stmt_to_qsharp(source) else { + panic!("Expected error"); + }; + expect![[ + r#"Cannot assign a value of Negative Int type to a classical variable of UInt(Some(10), True) type."# + ]] + .assert_eq(&errors[0].to_string()); } diff --git a/compiler/qsc_qasm3/src/tests/expression.rs b/compiler/qsc_qasm/src/tests/expression.rs similarity index 94% rename from compiler/qsc_qasm3/src/tests/expression.rs rename to compiler/qsc_qasm/src/tests/expression.rs index 8ee361df0c..b8803d23e9 100644 --- a/compiler/qsc_qasm3/src/tests/expression.rs +++ b/compiler/qsc_qasm/src/tests/expression.rs @@ -3,6 +3,7 @@ mod binary; mod bits; +mod function_call; mod ident; mod implicit_cast_from_bit; mod implicit_cast_from_bitarray; diff --git a/compiler/qsc_qasm3/src/tests/expression/binary.rs b/compiler/qsc_qasm/src/tests/expression/binary.rs similarity index 80% rename from compiler/qsc_qasm3/src/tests/expression/binary.rs rename to compiler/qsc_qasm/src/tests/expression/binary.rs index f15be6380e..dc676059b5 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary.rs +++ b/compiler/qsc_qasm/src/tests/expression/binary.rs @@ -5,6 +5,7 @@ use expect_test::expect; use crate::tests::compile_qasm_to_qsharp; +mod angle; mod arithmetic_conversions; mod comparison; mod complex; @@ -33,8 +34,7 @@ fn binary_expr_fail_parse_missing_lhs() { panic!("Expected error"); }; - expect![r#"QASM3 Parse Error: atom_expr: expected expression"#] - .assert_eq(&errors[0].to_string()); + expect![r#"expected EOF, found `<`"#].assert_eq(&errors[0].to_string()); } #[test] @@ -48,5 +48,5 @@ fn binary_expr_fail_parse_missing_rhs() { panic!("Expected error"); }; - expect![r#"QASM3 Parse Error: expr_bp: expected expression"#].assert_eq(&errors[0].to_string()); + expect![r#"expected expression, found `;`"#].assert_eq(&errors[0].to_string()); } diff --git a/compiler/qsc_qasm/src/tests/expression/binary/angle.rs b/compiler/qsc_qasm/src/tests/expression/binary/angle.rs new file mode 100644 index 0000000000..42a38fd02f --- /dev/null +++ b/compiler/qsc_qasm/src/tests/expression/binary/angle.rs @@ -0,0 +1,270 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::tests::compile_qasm_stmt_to_qsharp; + +use expect_test::expect; +use miette::Report; + +// Bit shift +#[test] +fn shl() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + uint b = 2; + angle x = a << b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __AngleShl__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn shr() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + uint b = 2; + angle x = a >> b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __AngleShr__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// Bitwise + +#[test] +fn andb() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + angle[32] b = 2.0; + angle x = a & b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __AngleAndB__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn orb() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + angle[32] b = 2.0; + angle x = a | b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __AngleOrB__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn xorb() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + angle[32] b = 2.0; + angle x = a ^ b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __AngleXorB__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// Comparison + +#[test] +fn eq() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + angle[32] b = 2.0; + bool x = a == b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __AngleEq__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn neq() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + angle[32] b = 2.0; + bool x = a != b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __AngleNeq__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn gt() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + angle[32] b = 2.0; + bool x = a > b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __AngleGt__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn gte() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + angle[32] b = 2.0; + bool x = a >= b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __AngleGte__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn lt() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + angle[32] b = 2.0; + bool x = a < b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __AngleLt__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn lte() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + angle[32] b = 2.0; + bool x = a <= b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __AngleLte__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// Arithmetic + +#[test] +fn addition() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + angle[32] b = 2.0; + angle x = a + b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __AddAngles__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn subtraction() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + angle[32] b = 2.0; + angle x = a - b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __SubtractAngles__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn multiplication_by_uint() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + uint[32] b = 2; + angle[32] x = a * b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __MultiplyAngleByInt__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn division_by_uint() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + uint[32] b = 2; + angle x = a / b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __DivideAngleByInt__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn division_by_angle() -> miette::Result<(), Vec> { + let source = " + angle[32] a = 1.0; + angle[32] b = 2.0; + uint x = a / b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = __DivideAngleByAngle__(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} diff --git a/compiler/qsc_qasm3/src/tests/expression/binary/arithmetic_conversions.rs b/compiler/qsc_qasm/src/tests/expression/binary/arithmetic_conversions.rs similarity index 75% rename from compiler/qsc_qasm3/src/tests/expression/binary/arithmetic_conversions.rs rename to compiler/qsc_qasm/src/tests/expression/binary/arithmetic_conversions.rs index a570787372..eddae61f83 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary/arithmetic_conversions.rs +++ b/compiler/qsc_qasm/src/tests/expression/binary/arithmetic_conversions.rs @@ -15,13 +15,14 @@ fn int_idents_without_width_can_be_multiplied() -> miette::Result<(), Vec miette::Result<(), Vec miette::Result<(), Vec "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 5; mutable y = 3; x * y; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -76,13 +79,14 @@ fn multiplying_int_idents_with_different_width_result_in_higher_width_result( "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 5; mutable y = 3; mutable z = x * y; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -97,13 +101,14 @@ fn multiplying_int_idents_with_different_width_result_in_no_width_result( "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 5; mutable y = 3; mutable z = x * y; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -118,13 +123,14 @@ fn multiplying_int_idents_with_width_greater_than_64_result_in_bigint_result( "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 5; mutable y = 3; mutable z = Microsoft.Quantum.Convert.IntAsBigInt(x * y); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/expression/binary/comparison.rs b/compiler/qsc_qasm/src/tests/expression/binary/comparison.rs similarity index 85% rename from compiler/qsc_qasm3/src/tests/expression/binary/comparison.rs rename to compiler/qsc_qasm/src/tests/expression/binary/comparison.rs index 5bbb6ba08a..d656a0c381 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary/comparison.rs +++ b/compiler/qsc_qasm/src/tests/expression/binary/comparison.rs @@ -23,9 +23,11 @@ fn int_var_comparisons_can_be_translated() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp_file(source)?; - expect![ - r#" - namespace qasm3_import { + expect![[r#" + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; @EntryPoint() operation Test() : (Int, Int, Bool, Bool, Bool, Bool, Bool, Bool) { mutable x = 5; @@ -38,8 +40,7 @@ fn int_var_comparisons_can_be_translated() -> miette::Result<(), Vec> { mutable d = (x != y); (x, y, f, e, a, c, b, d) } - }"# - ] + }"#]] .assert_eq(&qsharp); Ok(()) } @@ -58,9 +59,11 @@ fn uint_var_comparisons_can_be_translated() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp_file(source)?; - expect![ - r#" - namespace qasm3_import { + expect![[r#" + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; @EntryPoint() operation Test() : (Int, Int, Bool, Bool, Bool, Bool, Bool, Bool) { mutable x = 5; @@ -73,8 +76,7 @@ fn uint_var_comparisons_can_be_translated() -> miette::Result<(), Vec> { mutable d = (x != y); (x, y, f, e, a, c, b, d) } - }"# - ] + }"#]] .assert_eq(&qsharp); Ok(()) } @@ -93,9 +95,11 @@ fn bit_var_comparisons_can_be_translated() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp_file(source)?; - expect![ - r#" - namespace qasm3_import { + expect![[r#" + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; @EntryPoint() operation Test() : (Result, Result, Bool, Bool, Bool, Bool, Bool, Bool) { mutable x = One; @@ -108,8 +112,7 @@ fn bit_var_comparisons_can_be_translated() -> miette::Result<(), Vec> { mutable d = (x != y); (x, y, f, e, a, c, b, d) } - }"# - ] + }"#]] .assert_eq(&qsharp); Ok(()) } @@ -128,14 +131,13 @@ fn bitarray_var_comparisons_can_be_translated() -> miette::Result<(), Vec __ResultArrayAsIntBE__(y)); @@ -146,8 +148,7 @@ fn bitarray_var_comparisons_can_be_translated() -> miette::Result<(), Vec miette::Result<(), Vec< "#; let qsharp = compile_qasm_to_qsharp_file(source)?; - expect![ - r#" - namespace qasm3_import { + expect![[r#" + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; operation Test(y : Int) : (Result[], Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool) { - function __ResultArrayAsIntBE__(results : Result[]) : Int { - Microsoft.Quantum.Convert.ResultArrayAsInt(Microsoft.Quantum.Arrays.Reversed(results)) - } mutable x = [One]; mutable a = (__ResultArrayAsIntBE__(x) > y); mutable b = (__ResultArrayAsIntBE__(x) >= y); @@ -194,8 +194,7 @@ fn bitarray_var_comparison_to_int_can_be_translated() -> miette::Result<(), Vec< mutable l = (y != __ResultArrayAsIntBE__(x)); (x, a, b, c, d, e, f, g, h, i, j, k, l) } - }"# - ] + }"#]] .assert_eq(&qsharp); Ok(()) } @@ -214,9 +213,11 @@ fn float_var_comparisons_can_be_translated() -> miette::Result<(), Vec> "; let qsharp = compile_qasm_to_qsharp_file(source)?; - expect![ - r#" - namespace qasm3_import { + expect![[r#" + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; @EntryPoint() operation Test() : (Double, Double, Bool, Bool, Bool, Bool, Bool, Bool) { mutable x = 5.; @@ -229,8 +230,7 @@ fn float_var_comparisons_can_be_translated() -> miette::Result<(), Vec> mutable d = (x != y); (x, y, f, e, a, c, b, d) } - }"# - ] + }"#]] .assert_eq(&qsharp); Ok(()) } @@ -251,9 +251,11 @@ fn bool_var_comparisons_can_be_translated() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp_file(source)?; - expect![ - r#" - namespace qasm3_import { + expect![[r#" + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; @EntryPoint() operation Test() : (Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool) { mutable x = true; @@ -268,8 +270,7 @@ fn bool_var_comparisons_can_be_translated() -> miette::Result<(), Vec> { mutable h = (x or not y); (x, y, a, b, c, d, e, f, g, h) } - }"# - ] + }"#]] .assert_eq(&qsharp); Ok(()) } diff --git a/compiler/qsc_qasm/src/tests/expression/binary/complex.rs b/compiler/qsc_qasm/src/tests/expression/binary/complex.rs new file mode 100644 index 0000000000..00366c153a --- /dev/null +++ b/compiler/qsc_qasm/src/tests/expression/binary/complex.rs @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::tests::compile_qasm_stmt_to_qsharp; +use expect_test::expect; +use miette::Report; + +#[test] +fn addition() -> miette::Result<(), Vec> { + let source = " + input complex[float] a; + input complex[float] b; + complex x = a + b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = Microsoft.Quantum.Math.PlusC(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn addition_assign_op() -> miette::Result<(), Vec> { + let source = " + input complex[float] a; + complex x = 0.0; + x += a; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + set x = Microsoft.Quantum.Math.PlusC(x, a); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn subtraction() -> miette::Result<(), Vec> { + let source = " + input complex[float] a; + input complex[float] b; + complex x = a - b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = Microsoft.Quantum.Math.MinusC(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn subtraction_assign_op() -> miette::Result<(), Vec> { + let source = " + input complex[float] a; + complex x = 0.0; + x -= a; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + set x = Microsoft.Quantum.Math.MinusC(x, a); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn multiplication() -> miette::Result<(), Vec> { + let source = " + input complex[float] a; + input complex[float] b; + complex x = a * b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = Microsoft.Quantum.Math.TimesC(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn multiplication_assign_op() -> miette::Result<(), Vec> { + let source = " + input complex[float] a; + complex x = 0.0; + x *= a; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + set x = Microsoft.Quantum.Math.TimesC(x, a); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn division() -> miette::Result<(), Vec> { + let source = " + input complex[float] a; + input complex[float] b; + complex x = a / b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = Microsoft.Quantum.Math.DividedByC(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn division_assign_op() -> miette::Result<(), Vec> { + let source = " + input complex[float] a; + complex x = 0.0; + x /= a; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + set x = Microsoft.Quantum.Math.DividedByC(x, a); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn power() -> miette::Result<(), Vec> { + let source = " + input complex[float] a; + input complex[float] b; + complex x = a ** b; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = Microsoft.Quantum.Math.PowC(a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn power_assign_op() -> miette::Result<(), Vec> { + let source = " + input complex[float] a; + complex x = 0.0; + x **= a; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + set x = Microsoft.Quantum.Math.PowC(x, a); + "#]] + .assert_eq(&qsharp); + Ok(()) +} diff --git a/compiler/qsc_qasm3/src/tests/expression/binary/ident.rs b/compiler/qsc_qasm/src/tests/expression/binary/ident.rs similarity index 75% rename from compiler/qsc_qasm3/src/tests/expression/binary/ident.rs rename to compiler/qsc_qasm/src/tests/expression/binary/ident.rs index 5e6dee5f9c..a6224743b5 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary/ident.rs +++ b/compiler/qsc_qasm/src/tests/expression/binary/ident.rs @@ -15,13 +15,14 @@ fn mutable_int_idents_without_width_can_be_multiplied() -> miette::Result<(), Ve "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 5; mutable y = 3; x * y; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -35,13 +36,14 @@ fn const_int_idents_without_width_can_be_multiplied() -> miette::Result<(), Vec< "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let x = 5; let y = 3; x * y; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -56,13 +58,14 @@ fn const_int_idents_widthless_lhs_can_be_multiplied_by_explicit_width_int( "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let x = 5; let y = 3; x * y; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/expression/binary/literal.rs b/compiler/qsc_qasm/src/tests/expression/binary/literal.rs similarity index 100% rename from compiler/qsc_qasm3/src/tests/expression/binary/literal.rs rename to compiler/qsc_qasm/src/tests/expression/binary/literal.rs diff --git a/compiler/qsc_qasm3/src/tests/expression/binary/literal/multiplication.rs b/compiler/qsc_qasm/src/tests/expression/binary/literal/multiplication.rs similarity index 87% rename from compiler/qsc_qasm3/src/tests/expression/binary/literal/multiplication.rs rename to compiler/qsc_qasm/src/tests/expression/binary/literal/multiplication.rs index 4fb67ca1f0..7e112df8e5 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary/literal/multiplication.rs +++ b/compiler/qsc_qasm/src/tests/expression/binary/literal/multiplication.rs @@ -4,7 +4,7 @@ use expect_test::expect; use miette::Report; -use crate::tests::{compile_qasm_stmt_to_qsharp, compile_qasm_to_qsharp}; +use crate::tests::compile_qasm_stmt_to_qsharp; #[test] fn int_float_lhs_promoted_to_float() -> miette::Result<(), Vec> { @@ -12,12 +12,10 @@ fn int_float_lhs_promoted_to_float() -> miette::Result<(), Vec> { 5 * 0.3; "; - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" 5. * 0.3; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/expression/bits.rs b/compiler/qsc_qasm/src/tests/expression/bits.rs similarity index 63% rename from compiler/qsc_qasm3/src/tests/expression/bits.rs rename to compiler/qsc_qasm/src/tests/expression/bits.rs index c3c9cddf7a..04890be817 100644 --- a/compiler/qsc_qasm3/src/tests/expression/bits.rs +++ b/compiler/qsc_qasm/src/tests/expression/bits.rs @@ -25,26 +25,13 @@ fn bit_array_bits_and_register_ops() -> miette::Result<(), Vec> { rs_a_1 = (a >> 1); // Bit shift right produces "01000111" "#; let qsharp = compile_qasm_to_qsharp_file(source)?; - expect![ - r#" - namespace qasm3_import { + expect![[r#" + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; @EntryPoint() operation Test() : (Result[], Result[], Result[], Result[], Result[]) { - function __BoolAsResult__(input : Bool) : Result { - Microsoft.Quantum.Convert.BoolAsResult(input) - } - function __IntAsResultArrayBE__(number : Int, bits : Int) : Result[] { - mutable runningValue = number; - mutable result = []; - for _ in 1..bits { - set result += [__BoolAsResult__((runningValue &&& 1) != 0)]; - set runningValue >>>= 1; - } - Microsoft.Quantum.Arrays.Reversed(result) - } - function __ResultArrayAsIntBE__(results : Result[]) : Int { - Microsoft.Quantum.Convert.ResultArrayAsInt(Microsoft.Quantum.Arrays.Reversed(results)) - } mutable a = [One, Zero, Zero, Zero, One, One, One, One]; mutable b = [Zero, One, One, One, Zero, Zero, Zero, Zero]; mutable ls_a_1 = [Zero, Zero, Zero, Zero, Zero, Zero, Zero, Zero]; @@ -59,8 +46,7 @@ fn bit_array_bits_and_register_ops() -> miette::Result<(), Vec> { set rs_a_1 = (__IntAsResultArrayBE__(__ResultArrayAsIntBE__(a) >>> 1, 8)); (ls_a_1, a_or_b, a_and_b, a_xor_b, rs_a_1) } - }"# - ] + }"#]] .assert_eq(&qsharp); Ok(()) } @@ -74,28 +60,14 @@ fn bit_array_left_shift() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __BoolAsResult__(input : Bool) : Result { - Microsoft.Quantum.Convert.BoolAsResult(input) - } - function __IntAsResultArrayBE__(number : Int, bits : Int) : Result[] { - mutable runningValue = number; - mutable result = []; - for _ in 1..bits { - set result += [__BoolAsResult__((runningValue &&& 1) != 0)]; - set runningValue >>>= 1; - } - Microsoft.Quantum.Arrays.Reversed(result) - } - function __ResultArrayAsIntBE__(results : Result[]) : Int { - Microsoft.Quantum.Convert.ResultArrayAsInt(Microsoft.Quantum.Arrays.Reversed(results)) - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable a = [One, Zero, Zero, Zero, One, One, One, One]; mutable ls_a_1 = [Zero, Zero, Zero, Zero, Zero, Zero, Zero, Zero]; set ls_a_1 = (__IntAsResultArrayBE__(__ResultArrayAsIntBE__(a) <<< 1, 8)); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } diff --git a/compiler/qsc_qasm/src/tests/expression/function_call.rs b/compiler/qsc_qasm/src/tests/expression/function_call.rs new file mode 100644 index 0000000000..8290f62538 --- /dev/null +++ b/compiler/qsc_qasm/src/tests/expression/function_call.rs @@ -0,0 +1,369 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::tests::{compile_qasm_to_qir, compile_qasm_to_qsharp}; +use expect_test::expect; +use miette::Report; +use qsc::target::Profile; + +#[test] +fn funcall_with_no_arguments_generates_correct_qsharp() -> miette::Result<(), Vec> { + let source = r#" + def empty() {} + empty(); + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + function empty() : Unit {} + empty(); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn void_function_with_one_argument_generates_correct_qsharp() -> miette::Result<(), Vec> { + let source = r#" + def f(int x) {} + f(2); + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + function f(x : Int) : Unit {} + f(2); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn funcall_with_one_argument_generates_correct_qsharp() -> miette::Result<(), Vec> { + let source = r#" + def square(int x) -> int { + return x * x; + } + + square(2); + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + function square(x : Int) : Int { + return x * x; + } + square(2); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn funcall_with_two_arguments_generates_correct_qsharp() -> miette::Result<(), Vec> { + let source = r#" + def sum(int x, int y) -> int { + return x + y; + } + + sum(2, 3); + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + function sum(x : Int, y : Int) : Int { + return x + y; + } + sum(2, 3); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn funcall_with_qubit_argument() -> miette::Result<(), Vec> { + let source = r#" + def parity(qubit[2] qs) -> bit { + bit a = measure qs[0]; + bit b = measure qs[1]; + return a ^ b; + } + + qubit[2] qs; + bit p = parity(qs); + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + operation parity(qs : Qubit[]) : Result { + mutable a = QIR.Intrinsic.__quantum__qis__m__body(qs[0]); + mutable b = QIR.Intrinsic.__quantum__qis__m__body(qs[1]); + return if __ResultAsInt__(a) ^^^ __ResultAsInt__(b) == 0 { + One + } else { + Zero + }; + } + let qs = QIR.Runtime.AllocateQubitArray(2); + mutable p = parity(qs); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn funcall_with_too_few_arguments_generates_error() { + let source = r#" + def square(int x) -> int { + return x * x; + } + + square(); + "#; + + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Lowerer.InvalidNumberOfClassicalArgs + + x gate expects 1 classical arguments, but 0 were provided + ,-[Test.qasm:6:9] + 5 | + 6 | square(); + : ^^^^^^^^ + 7 | + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} + +#[test] +fn funcall_with_too_many_arguments_generates_error() { + let source = r#" + def square(int x) -> int { + return x * x; + } + + square(2, 3); + "#; + + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Lowerer.InvalidNumberOfClassicalArgs + + x gate expects 1 classical arguments, but 2 were provided + ,-[Test.qasm:6:9] + 5 | + 6 | square(2, 3); + : ^^^^^^^^^^^^ + 7 | + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} + +#[test] +fn funcall_accepts_qubit_argument() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + def h_wrapper(qubit q) { + h q; + } + + qubit q; + h_wrapper(q); + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + operation h_wrapper(q : Qubit) : Unit { + h(q); + } + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + h_wrapper(q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn classical_decl_initialized_with_funcall() -> miette::Result<(), Vec> { + let source = r#" + def square(int x) -> int { + return x * x; + } + + int a = square(2); + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + function square(x : Int) : Int { + return x * x; + } + mutable a = square(2); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn classical_decl_initialized_with_incompatible_funcall_errors() { + let source = r#" + def square(float x) -> float { + return x * x; + } + + bit a = square(2.0); + "#; + + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Float(None, false) to type Bit(false) + ,-[Test.qasm:6:17] + 5 | + 6 | bit a = square(2.0); + : ^^^^^^^^^^^ + 7 | + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} + +#[test] +fn funcall_implicit_arg_cast_uint_to_bitarray() -> miette::Result<(), Vec> { + let source = r#" + def parity(bit[2] arr) -> bit { + return 1; + } + + bit p = parity(2); + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + function parity(arr : Result[]) : Result { + return if 1 == 0 { + One + } else { + Zero + }; + } + mutable p = parity(__IntAsResultArrayBE__(2, 2)); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn funcall_implicit_arg_cast_uint_to_qubit_errors() { + let source = r#" + def parity(qubit[2] arr) -> bit { + return 1; + } + + bit p = parity(2); + "#; + + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Lowerer.CannotCast + + x cannot cast expression of type Int(None, true) to type QubitArray(One(2)) + ,-[Test.qasm:6:24] + 5 | + 6 | bit p = parity(2); + : ^ + 7 | + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} + +#[test] +fn simulatable_intrinsic_on_def_stmt_generates_correct_qir() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + + @SimulatableIntrinsic + def my_gate(qubit q) { + x q; + } + + qubit q; + my_gate(q); + bit result = measure q; + "#; + + let qsharp = compile_qasm_to_qir(source, Profile::AdaptiveRI)?; + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + define void @ENTRYPOINT__main() #0 { + block_0: + call void @my_gate(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__tuple_record_output(i64 0, i8* null) + ret void + } + + declare void @my_gate(%Qubit*) + + declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + + declare void @__quantum__rt__tuple_record_output(i64, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 1, !"int_computations", !"i64"} + "#]] + .assert_eq(&qsharp); + Ok(()) +} diff --git a/compiler/qsc_qasm3/src/tests/expression/ident.rs b/compiler/qsc_qasm/src/tests/expression/ident.rs similarity index 75% rename from compiler/qsc_qasm3/src/tests/expression/ident.rs rename to compiler/qsc_qasm/src/tests/expression/ident.rs index 86d689015b..52e7da2382 100644 --- a/compiler/qsc_qasm3/src/tests/expression/ident.rs +++ b/compiler/qsc_qasm/src/tests/expression/ident.rs @@ -12,11 +12,33 @@ fn unresolved_idenfiers_raise_symbol_error() { float x = t; "; - let Err(errors) = compile_qasm_stmt_to_qsharp(source) else { - panic!("Expected an error"); + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("should have generated an error"); }; - assert_eq!(1, errors.len(), "Expected one error"); - expect![r#"Undefined symbol: t."#].assert_eq(&errors[0].to_string()); + let errs: Vec<_> = errors.iter().map(|e| format!("{e:?}")).collect(); + let errs_string = errs.join("\n"); + expect![[r#" + Qasm.Lowerer.UndefinedSymbol + + x undefined symbol: t + ,-[Test.qasm:2:19] + 1 | + 2 | float x = t; + : ^ + 3 | + `---- + + Qasm.Lowerer.CannotCast + + x cannot cast expression of type Err to type Float(None, false) + ,-[Test.qasm:2:19] + 1 | + 2 | float x = t; + : ^ + 3 | + `---- + "#]] + .assert_eq(&errs_string); } // this test verifies QASM behavior that would normally be allowed @@ -32,7 +54,7 @@ fn redefining_symbols_in_same_scope_raise_symbol_error() { panic!("Expected an error"); }; assert_eq!(1, errors.len(), "Expected one error"); - expect![r#"Redefined symbol: x."#].assert_eq(&errors[0].to_string()); + expect![["redefined symbol: x"]].assert_eq(&errors[0].to_string()); } #[test] @@ -45,6 +67,9 @@ fn resolved_idenfiers_are_compiled_as_refs() -> miette::Result<(), Vec> let qsharp = compile_qasm_to_qsharp(source)?; expect![ r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable p = Microsoft.Quantum.Math.PI(); mutable x = p; "# diff --git a/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_bit.rs b/compiler/qsc_qasm/src/tests/expression/implicit_cast_from_bit.rs similarity index 61% rename from compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_bit.rs rename to compiler/qsc_qasm/src/tests/expression/implicit_cast_from_bit.rs index 10dd05ef3a..7079da8755 100644 --- a/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_bit.rs +++ b/compiler/qsc_qasm/src/tests/expression/implicit_cast_from_bit.rs @@ -21,14 +21,10 @@ fn to_bool_and_back_implicitly() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __BoolAsResult__(input : Bool) : Result { - Microsoft.Quantum.Convert.BoolAsResult(input) - } - function __ResultAsBool__(input : Result) : Bool { - Microsoft.Quantum.Convert.ResultAsBool(input) - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable _bool0 = false; mutable _bool1 = false; set _bool0 = true; @@ -36,8 +32,7 @@ fn to_bool_and_back_implicitly() -> miette::Result<(), Vec> { set _bool0 = _bool1; set _bool0 = _bool1; set a = __BoolAsResult__(_bool1); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -50,15 +45,13 @@ fn to_bool_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __ResultAsBool__(input : Result) : Bool { - Microsoft.Quantum.Convert.ResultAsBool(input) - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = One; mutable y = __ResultAsBool__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -71,19 +64,13 @@ fn to_implicit_int_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __ResultAsInt__(input : Result) : Int { - if Microsoft.Quantum.Convert.ResultAsBool(input) { - 1 - } else { - 0 - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = One; mutable y = __ResultAsInt__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -96,19 +83,13 @@ fn to_explicit_int_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __ResultAsInt__(input : Result) : Int { - if Microsoft.Quantum.Convert.ResultAsBool(input) { - 1 - } else { - 0 - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = One; mutable y = __ResultAsInt__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -121,19 +102,13 @@ fn to_implicit_uint_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __ResultAsInt__(input : Result) : Int { - if Microsoft.Quantum.Convert.ResultAsBool(input) { - 1 - } else { - 0 - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = One; mutable y = __ResultAsInt__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -146,19 +121,13 @@ fn to_explicit_uint_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __ResultAsInt__(input : Result) : Int { - if Microsoft.Quantum.Convert.ResultAsBool(input) { - 1 - } else { - 0 - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = One; mutable y = __ResultAsInt__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -171,19 +140,13 @@ fn to_explicit_bigint_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __ResultAsBigInt__(input : Result) : BigInt { - if Microsoft.Quantum.Convert.ResultAsBool(input) { - 1L - } else { - 0L - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = One; mutable y = __ResultAsBigInt__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -199,6 +162,6 @@ fn to_implicit_float_implicitly_fails() { panic!("Expected error") }; - expect![r#"Cannot cast expression of type Bit(False) to type Float(None, False)"#] + expect!["cannot cast expression of type Bit(false) to type Float(None, false)"] .assert_eq(&error[0].to_string()); } diff --git a/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_bitarray.rs b/compiler/qsc_qasm/src/tests/expression/implicit_cast_from_bitarray.rs similarity index 68% rename from compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_bitarray.rs rename to compiler/qsc_qasm/src/tests/expression/implicit_cast_from_bitarray.rs index 73489272a2..b956faaebe 100644 --- a/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_bitarray.rs +++ b/compiler/qsc_qasm/src/tests/expression/implicit_cast_from_bitarray.rs @@ -14,15 +14,13 @@ fn to_int_decl_implicitly() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __ResultArrayAsIntBE__(results : Result[]) : Int { - Microsoft.Quantum.Convert.ResultArrayAsInt(Microsoft.Quantum.Arrays.Reversed(results)) - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable reg = [Zero, Zero, Zero, Zero, Zero]; mutable b = __ResultArrayAsIntBE__(reg); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -36,16 +34,14 @@ fn to_int_assignment_implicitly() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __ResultArrayAsIntBE__(results : Result[]) : Int { - Microsoft.Quantum.Convert.ResultArrayAsInt(Microsoft.Quantum.Arrays.Reversed(results)) - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable reg = [Zero, Zero, Zero, Zero, Zero]; mutable a = 0; set a = __ResultArrayAsIntBE__(reg); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -59,16 +55,14 @@ fn to_int_with_equal_width_in_assignment_implicitly() -> miette::Result<(), Vec< "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __ResultArrayAsIntBE__(results : Result[]) : Int { - Microsoft.Quantum.Convert.ResultArrayAsInt(Microsoft.Quantum.Arrays.Reversed(results)) - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable reg = [Zero, Zero, Zero, Zero, Zero]; mutable a = 0; set a = __ResultArrayAsIntBE__(reg); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -81,15 +75,13 @@ fn to_int_with_equal_width_in_decl_implicitly() -> miette::Result<(), Vec miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __BoolAsResult__(input : Bool) : Result { - Microsoft.Quantum.Convert.BoolAsResult(input) - } - function __ResultAsBool__(input : Result) : Bool { - Microsoft.Quantum.Convert.ResultAsBool(input) - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable _bit0 = Zero; mutable _bit1 = Zero; set _bit0 = One; @@ -36,8 +32,7 @@ fn to_bit_and_back_implicitly() -> miette::Result<(), Vec> { set _bit0 = _bit1; set _bit0 = _bit1; set a = __ResultAsBool__(_bit1); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -50,15 +45,13 @@ fn to_bit_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __BoolAsResult__(input : Bool) : Result { - Microsoft.Quantum.Convert.BoolAsResult(input) - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = true; mutable y = __BoolAsResult__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -71,19 +64,13 @@ fn to_implicit_int_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __BoolAsInt__(value : Bool) : Int { - if value { - 1 - } else { - 0 - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = true; mutable y = __BoolAsInt__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -96,19 +83,13 @@ fn to_explicit_int_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __BoolAsInt__(value : Bool) : Int { - if value { - 1 - } else { - 0 - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = true; mutable y = __BoolAsInt__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -121,19 +102,13 @@ fn to_implicit_uint_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __BoolAsInt__(value : Bool) : Int { - if value { - 1 - } else { - 0 - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = true; mutable y = __BoolAsInt__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -146,19 +121,13 @@ fn to_explicit_uint_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __BoolAsInt__(value : Bool) : Int { - if value { - 1 - } else { - 0 - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = true; mutable y = __BoolAsInt__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -171,19 +140,13 @@ fn to_explicit_bigint_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __BoolAsBigInt__(value : Bool) : BigInt { - if value { - 1L - } else { - 0L - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = true; mutable y = __BoolAsBigInt__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -196,19 +159,13 @@ fn to_implicit_float_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __BoolAsDouble__(value : Bool) : Double { - if value { - 1. - } else { - 0. - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = true; mutable y = __BoolAsDouble__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -221,19 +178,13 @@ fn to_explicit_float_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __BoolAsDouble__(value : Bool) : Double { - if value { - 1. - } else { - 0. - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = true; mutable y = __BoolAsDouble__(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_complex.rs b/compiler/qsc_qasm/src/tests/expression/implicit_cast_from_complex.rs similarity index 100% rename from compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_complex.rs rename to compiler/qsc_qasm/src/tests/expression/implicit_cast_from_complex.rs diff --git a/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_float.rs b/compiler/qsc_qasm/src/tests/expression/implicit_cast_from_float.rs similarity index 73% rename from compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_float.rs rename to compiler/qsc_qasm/src/tests/expression/implicit_cast_from_float.rs index 99d28f98dc..3b67688e0f 100644 --- a/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_float.rs +++ b/compiler/qsc_qasm/src/tests/expression/implicit_cast_from_float.rs @@ -17,7 +17,7 @@ fn to_bit_implicitly() { panic!("Expected error") }; - expect![r#"Cannot cast expression of type Float(None, False) to type Bit(False)"#] + expect!["cannot cast expression of type Float(None, false) to type Bit(false)"] .assert_eq(&error[0].to_string()); } @@ -32,7 +32,7 @@ fn explicit_width_to_bit_implicitly() { panic!("Expected error") }; - expect![r#"Cannot cast expression of type Float(Some(64), False) to type Bit(False)"#] + expect!["cannot cast expression of type Float(Some(64), false) to type Bit(false)"] .assert_eq(&error[0].to_string()); } @@ -44,16 +44,17 @@ fn to_bool_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42.; mutable y = if Microsoft.Quantum.Math.Truncate(x) == 0 { false } else { true }; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -66,12 +67,13 @@ fn to_implicit_int_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42.; mutable y = Microsoft.Quantum.Math.Truncate(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -84,12 +86,13 @@ fn to_explicit_int_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42.; mutable y = Microsoft.Quantum.Math.Truncate(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -102,12 +105,13 @@ fn to_implicit_uint_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42.; mutable y = Microsoft.Quantum.Math.Truncate(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -120,12 +124,13 @@ fn to_explicit_uint_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42.; mutable y = Microsoft.Quantum.Math.Truncate(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -138,12 +143,13 @@ fn to_explicit_bigint_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42.; mutable y = Microsoft.Quantum.Convert.IntAsBigInt(Microsoft.Quantum.Math.Truncate(x)); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -156,12 +162,13 @@ fn to_implicit_float_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42.; mutable y = x; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -174,12 +181,13 @@ fn to_explicit_float_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42.; mutable y = x; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -192,12 +200,13 @@ fn to_implicit_complex_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42.; mutable y = Microsoft.Quantum.Math.Complex(x, 0.); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -210,12 +219,13 @@ fn to_explicit_complex_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42.; mutable y = Microsoft.Quantum.Math.Complex(x, 0.); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_int.rs b/compiler/qsc_qasm/src/tests/expression/implicit_cast_from_int.rs similarity index 72% rename from compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_int.rs rename to compiler/qsc_qasm/src/tests/expression/implicit_cast_from_int.rs index 862fb74526..fe71800c3c 100644 --- a/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_int.rs +++ b/compiler/qsc_qasm/src/tests/expression/implicit_cast_from_int.rs @@ -14,16 +14,17 @@ fn to_bit_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42; mutable y = if x == 0 { One } else { Zero }; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -36,16 +37,17 @@ fn to_bool_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42; mutable y = if x == 0 { false } else { true }; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -58,12 +60,13 @@ fn to_implicit_int_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42; mutable y = x; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -76,12 +79,13 @@ fn to_explicit_int_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42; mutable y = x; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -94,12 +98,13 @@ fn to_implicit_uint_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42; mutable y = x; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -112,12 +117,13 @@ fn to_explicit_uint_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42; mutable y = x; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -130,12 +136,13 @@ fn to_explicit_bigint_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42; mutable y = Microsoft.Quantum.Convert.IntAsBigInt(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -148,12 +155,13 @@ fn to_implicit_float_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42; mutable y = Microsoft.Quantum.Convert.IntAsDouble(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -166,12 +174,13 @@ fn to_explicit_float_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42; mutable y = Microsoft.Quantum.Convert.IntAsDouble(x); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -184,12 +193,13 @@ fn to_implicit_complex_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42; mutable y = Microsoft.Quantum.Math.Complex(Microsoft.Quantum.Convert.IntAsDouble(x), 0.); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -202,12 +212,13 @@ fn to_explicit_complex_implicitly() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 42; mutable y = Microsoft.Quantum.Math.Complex(Microsoft.Quantum.Convert.IntAsDouble(x), 0.); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/expression/indexed.rs b/compiler/qsc_qasm/src/tests/expression/indexed.rs similarity index 56% rename from compiler/qsc_qasm3/src/tests/expression/indexed.rs rename to compiler/qsc_qasm/src/tests/expression/indexed.rs index 6ca7873af5..e80a73bdaf 100644 --- a/compiler/qsc_qasm3/src/tests/expression/indexed.rs +++ b/compiler/qsc_qasm/src/tests/expression/indexed.rs @@ -19,7 +19,7 @@ fn indexed_bit_cannot_be_implicitly_converted_to_float() { }; assert_eq!(1, errors.len(), "Expected one error"); - expect![r#"Cannot cast expression of type Bit(False) to type Float(None, False)"#] + expect!["cannot cast expression of type Bit(false) to type Float(None, true)"] .assert_eq(&errors[0].to_string()); } @@ -33,21 +33,15 @@ fn indexed_bit_can_implicitly_convert_to_int() -> miette::Result<(), Vec "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __ResultAsInt__(input : Result) : Int { - if Microsoft.Quantum.Convert.ResultAsBool(input) { - 1 - } else { - 0 - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = [Zero, Zero, Zero, Zero, Zero]; if __ResultAsInt__(x[0]) == 1 { set x w/= 1 <- One; }; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -62,17 +56,15 @@ fn indexed_bit_can_implicitly_convert_to_bool() -> miette::Result<(), Vec miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = [Zero, Zero, Zero, Zero, Zero]; mutable y = x[0]; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -99,17 +92,60 @@ fn bit_indexed_ty_is_same_as_element_ty() -> miette::Result<(), Vec> { #[ignore = "Not yet implemented"] fn bool_indexed_ty_is_same_as_element_ty() -> miette::Result<(), Vec> { let source = " - bool[5] x; + array[bool, 5] x; bool y = x[0]; "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" mutable x = [false, false, false, false, false]; mutable y = x[0]; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } + +#[test] +#[ignore = "Not yet implemented"] +fn bitstring_slicing() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + bit[5] ans = "10101"; + qubit qq; + if(ans[0:3] == 4) x qq; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#""#]].assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "Not yet implemented"] +fn bitstring_slicing_with_step() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + bit[5] ans = "10101"; + qubit qq; + if(ans[0:3:2] == 4) x qq; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#""#]].assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "Not yet implemented"] +fn bitstring_index_set() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + bit[5] ans = "10101"; + qubit qq; + if(ans[{1, 3}] == 4) x qq; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#""#]].assert_eq(&qsharp); + Ok(()) +} diff --git a/compiler/qsc_qasm/src/tests/expression/unary.rs b/compiler/qsc_qasm/src/tests/expression/unary.rs new file mode 100644 index 0000000000..5898809044 --- /dev/null +++ b/compiler/qsc_qasm/src/tests/expression/unary.rs @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; +use miette::Report; + +use crate::tests::compile_qasm_to_qsharp; + +#[test] +fn bitwise_not_int_fails() { + let source = " + int x = 5; + int y = ~x; + "; + + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Lowerer.TypeDoesNotSupportedUnaryNegation + + x unary negation is not allowed for instances of Int(None, false) + ,-[Test.qasm:3:18] + 2 | int x = 5; + 3 | int y = ~x; + : ^ + 4 | + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} + +#[test] +fn not_bool() -> miette::Result<(), Vec> { + let source = " + bool x = true; + bool y = !x; + "; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable x = true; + mutable y = not x; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn not_result() -> miette::Result<(), Vec> { + let source = " + bit x = 1; + bit y = !x; + "; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable x = One; + mutable y = __BoolAsResult__(not __ResultAsBool__(x)); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn logical_not_int() -> miette::Result<(), Vec> { + let source = " + int x = 159; + bool y = !x; + "; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable x = 159; + mutable y = not if x == 0 { + false + } else { + true + }; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "negating a Result type is an invalid Q# operation"] +fn bitwise_not_result() -> miette::Result<(), Vec> { + let source = " + bit[1] x; + bool success = ~x[0]; + "; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn logical_not_indexed_bit_array_in_if_cond() -> miette::Result<(), Vec> { + let source = " + bit[10] Classical; + if (!Classical[1]) { + Classical[0] = 1; + } + "; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable Classical = [Zero, Zero, Zero, Zero, Zero, Zero, Zero, Zero, Zero, Zero]; + if not __ResultAsBool__(Classical[1]) { + set Classical w/= 0 <- One; + }; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn neg_angle() -> miette::Result<(), Vec> { + let source = " + angle[4] x = 1.0; + angle[4] y = -x; + "; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable x = new __Angle__ { + Value = 3, + Size = 4 + }; + mutable y = __NegAngle__(x); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn notb_angle() -> miette::Result<(), Vec> { + let source = " + angle[4] x = 1.0; + angle[4] y = ~x; + "; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable x = new __Angle__ { + Value = 3, + Size = 4 + }; + mutable y = __AngleNotB__(x); + "#]] + .assert_eq(&qsharp); + Ok(()) +} diff --git a/compiler/qsc_qasm/src/tests/fuzz.rs b/compiler/qsc_qasm/src/tests/fuzz.rs new file mode 100644 index 0000000000..4f6e43972b --- /dev/null +++ b/compiler/qsc_qasm/src/tests/fuzz.rs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use super::compile_qasm_best_effort; +use qsc::target::Profile; + +/// We also had an issue where +/// 1. naming a gate the same as a qubit parameter of a parent gate, +/// 2. and then referencing the qubit parameter of the inner gate. +/// +/// was causing a panic in the Q# resolver. +#[test] +fn fuzz_2297_referencing_qubit_parameter() { + let source = r#" + gate g q0 { + gate q0 q1 {} + q1; + } + "#; + super::compare_qasm_and_qasharp_asts(source); + compile_qasm_best_effort(source, Profile::Unrestricted); +} + +/// The same panic happened when referencing an angle parameter. +#[test] +fn fuzz_2297_referencing_angle_parameter() { + let source = r#" + gate g q0 { + gate q0(r) q1 {} + r; + } + "#; + super::compare_qasm_and_qasharp_asts(source); + compile_qasm_best_effort(source, Profile::Unrestricted); +} + +/// Subroutines didn't have this problem, even though they are also +/// compiled to operations when they take qubit arguments. +#[test] +fn fuzz_2297_def() { + let source = r#" + def g(qubit q0) { + def q0(qubit q1) {} + q1; + } + "#; + super::compare_qasm_and_qasharp_asts(source); + compile_qasm_best_effort(source, Profile::Unrestricted); +} + +/// We also had an issue where, in the same conditions as `fuzz_2297`, +/// a missing identifier in a comma separated list of formal paremeters +/// would generate an empty string Identifier and forward it to Q#, +/// which yields an invalid Q# AST. +#[test] +fn fuzz_2297_with_trailing_comma() { + let source = r#" + gate g q0 { + gate q0 ,q1 {} + q1; + } + "#; + super::compare_qasm_and_qasharp_asts(source); + compile_qasm_best_effort(source, Profile::Unrestricted); +} + +#[test] +fn fuzz_2298() { + let source = r#"gate y()a{gate a,b{}b"#; + super::compare_qasm_and_qasharp_asts(source); + compile_qasm_best_effort(source, Profile::Unrestricted); +} diff --git a/compiler/qsc_qasm3/src/tests/output.rs b/compiler/qsc_qasm/src/tests/output.rs similarity index 51% rename from compiler/qsc_qasm3/src/tests/output.rs rename to compiler/qsc_qasm/src/tests/output.rs index 711b64e9ad..69fef17b86 100644 --- a/compiler/qsc_qasm3/src/tests/output.rs +++ b/compiler/qsc_qasm/src/tests/output.rs @@ -2,15 +2,14 @@ // Licensed under the MIT License. use crate::{ - qasm_to_program, - tests::{fail_on_compilation_errors, gen_qsharp, parse}, + tests::{fail_on_compilation_errors, gen_qsharp}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; use expect_test::expect; use miette::Report; use qsc::target::Profile; -use super::compile_qasm_to_qir; +use super::{compile_qasm_to_qir, compile_with_config}; #[test] fn using_re_semantics_removes_output() -> miette::Result<(), Vec> { @@ -29,32 +28,29 @@ fn using_re_semantics_removes_output() -> miette::Result<(), Vec> { c[0] = measure q[0]; c[1] = measure q[1]; "#; - - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::ResourceEstimation, - ProgramType::File, - Some("Test".into()), - None, - ), + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::ResourceEstimation, + ProgramType::File, + Some("Test".into()), + None, ); + let unit = compile_with_config(source, config).expect("parse failed"); fail_on_compilation_errors(&unit); let qsharp = gen_qsharp(&unit.package.expect("no package found")); expect![[r#" - namespace qasm3_import { + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; operation Test(theta : Double, beta : Int) : Unit { mutable c = [Zero, Zero]; let q = QIR.Runtime.AllocateQubitArray(2); mutable gamma = 0.; mutable delta = 0.; - Rz(theta, q[0]); - H(q[0]); - CNOT(q[0], q[1]); + rz(__DoubleAsAngle__(theta, 53), q[0]); + h(q[0]); + cx(q[0], q[1]); set c w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[0]); set c w/= 1 <- QIR.Intrinsic.__quantum__qis__m__body(q[1]); } @@ -83,31 +79,29 @@ fn using_qasm_semantics_captures_all_classical_decls_as_output() -> miette::Resu c[1] = measure q[1]; "#; - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::OpenQasm, - ProgramType::File, - Some("Test".into()), - None, - ), + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::File, + Some("Test".into()), + None, ); + let unit = compile_with_config(source, config).expect("parse failed"); fail_on_compilation_errors(&unit); let qsharp = gen_qsharp(&unit.package.expect("no package found")); expect![[r#" - namespace qasm3_import { + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; operation Test(theta : Double, beta : Int) : (Result[], Double, Double) { mutable c = [Zero, Zero]; let q = QIR.Runtime.AllocateQubitArray(2); mutable gamma = 0.; mutable delta = 0.; - Rz(theta, q[0]); - H(q[0]); - CNOT(q[0], q[1]); + rz(__DoubleAsAngle__(theta, 53), q[0]); + h(q[0]); + cx(q[0], q[1]); set c w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[0]); set c w/= 1 <- QIR.Intrinsic.__quantum__qis__m__body(q[1]); (c, gamma, delta) @@ -136,32 +130,29 @@ fn using_qiskit_semantics_only_bit_array_is_captured_and_reversed( c[0] = measure q[0]; c[1] = measure q[1]; "#; - - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::Qiskit, - ProgramType::File, - Some("Test".into()), - None, - ), + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, ); + let unit = compile_with_config(source, config).expect("parse failed"); fail_on_compilation_errors(&unit); let qsharp = gen_qsharp(&unit.package.expect("no package found")); expect![[r#" - namespace qasm3_import { + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; operation Test(theta : Double, beta : Int) : Result[] { mutable c = [Zero, Zero]; let q = QIR.Runtime.AllocateQubitArray(2); mutable gamma = 0.; mutable delta = 0.; - Rz(theta, q[0]); - H(q[0]); - CNOT(q[0], q[1]); + rz(__DoubleAsAngle__(theta, 53), q[0]); + h(q[0]); + cx(q[0], q[1]); set c w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[0]); set c w/= 1 <- QIR.Intrinsic.__quantum__qis__m__body(q[1]); Microsoft.Quantum.Arrays.Reversed(c) @@ -197,37 +188,34 @@ c2[0] = measure q[2]; c2[1] = measure q[3]; c2[2] = measure q[4]; "#; - - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::Qiskit, - ProgramType::File, - Some("Test".into()), - None, - ), + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, ); + let unit = compile_with_config(source, config).expect("parse failed"); fail_on_compilation_errors(&unit); let package = unit.package.expect("no package found"); let qsharp = gen_qsharp(&package.clone()); expect![[r#" - namespace qasm3_import { + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; operation Test(theta : Double, beta : Int) : (Result[], Result[]) { mutable c = [Zero, Zero]; mutable c2 = [Zero, Zero, Zero]; let q = QIR.Runtime.AllocateQubitArray(5); mutable gamma = 0.; mutable delta = 0.; - Rz(theta, q[0]); - H(q[0]); - CNOT(q[0], q[1]); - X(q[2]); - I(q[3]); - X(q[4]); + rz(__DoubleAsAngle__(theta, 53), q[0]); + h(q[0]); + cx(q[0], q[1]); + x(q[2]); + id(q[3]); + x(q[4]); set c w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[0]); set c w/= 1 <- QIR.Intrinsic.__quantum__qis__m__body(q[1]); set c2 w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[2]); @@ -270,64 +258,64 @@ c2[2] = measure q[4]; let qir = compile_qasm_to_qir(source, Profile::AdaptiveRI)?; expect![[r#" -%Result = type opaque -%Qubit = type opaque + %Result = type opaque + %Qubit = type opaque -define void @ENTRYPOINT__main() #0 { -block_0: - call void @__quantum__qis__rz__body(double 0.5, %Qubit* inttoptr (i64 0 to %Qubit*)) - call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) - call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*)) - call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 2 to %Qubit*)) - call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 4 to %Qubit*)) - call void @__quantum__qis__barrier__body() - call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) - call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) - call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) - call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 3 to %Result*)) - call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 4 to %Qubit*), %Result* inttoptr (i64 4 to %Result*)) - call void @__quantum__rt__tuple_record_output(i64 2, i8* null) - call void @__quantum__rt__array_record_output(i64 3, i8* null) - call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 4 to %Result*), i8* null) - call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* null) - call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null) - call void @__quantum__rt__array_record_output(i64 2, i8* null) - call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) - call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) - ret void -} + define void @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__qis__rz__body(double 0.4999999999999997, %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 4 to %Qubit*)) + call void @__quantum__qis__barrier__body() + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 3 to %Result*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 4 to %Qubit*), %Result* inttoptr (i64 4 to %Result*)) + call void @__quantum__rt__tuple_record_output(i64 2, i8* null) + call void @__quantum__rt__array_record_output(i64 3, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 4 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null) + call void @__quantum__rt__array_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + ret void + } -declare void @__quantum__qis__rz__body(double, %Qubit*) + declare void @__quantum__qis__rz__body(double, %Qubit*) -declare void @__quantum__qis__h__body(%Qubit*) + declare void @__quantum__qis__h__body(%Qubit*) -declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*) + declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*) -declare void @__quantum__qis__x__body(%Qubit*) + declare void @__quantum__qis__x__body(%Qubit*) -declare void @__quantum__qis__barrier__body() + declare void @__quantum__qis__barrier__body() -declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 -declare void @__quantum__rt__tuple_record_output(i64, i8*) + declare void @__quantum__rt__tuple_record_output(i64, i8*) -declare void @__quantum__rt__array_record_output(i64, i8*) + declare void @__quantum__rt__array_record_output(i64, i8*) -declare void @__quantum__rt__result_record_output(%Result*, i8*) + declare void @__quantum__rt__result_record_output(%Result*, i8*) -attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="5" "required_num_results"="5" } -attributes #1 = { "irreversible" } + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="5" "required_num_results"="5" } + attributes #1 = { "irreversible" } -; module flags + ; module flags -!llvm.module.flags = !{!0, !1, !2, !3, !4} + !llvm.module.flags = !{!0, !1, !2, !3, !4} -!0 = !{i32 1, !"qir_major_version", i32 1} -!1 = !{i32 7, !"qir_minor_version", i32 0} -!2 = !{i32 1, !"dynamic_qubit_management", i1 false} -!3 = !{i32 1, !"dynamic_result_management", i1 false} -!4 = !{i32 1, !"int_computations", !"i64"} -"#]] + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 1, !"int_computations", !"i64"} + "#]] .assert_eq(&qir); Ok(()) diff --git a/compiler/qsc_qasm3/src/tests/sample_circuits.rs b/compiler/qsc_qasm/src/tests/sample_circuits.rs similarity index 100% rename from compiler/qsc_qasm3/src/tests/sample_circuits.rs rename to compiler/qsc_qasm/src/tests/sample_circuits.rs diff --git a/compiler/qsc_qasm3/src/tests/sample_circuits/bell_pair.rs b/compiler/qsc_qasm/src/tests/sample_circuits/bell_pair.rs similarity index 58% rename from compiler/qsc_qasm3/src/tests/sample_circuits/bell_pair.rs rename to compiler/qsc_qasm/src/tests/sample_circuits/bell_pair.rs index 274bb7ab3f..2726225072 100644 --- a/compiler/qsc_qasm3/src/tests/sample_circuits/bell_pair.rs +++ b/compiler/qsc_qasm/src/tests/sample_circuits/bell_pair.rs @@ -2,8 +2,7 @@ // Licensed under the MIT License. use crate::{ - qasm_to_program, - tests::{gen_qsharp, parse, print_compilation_errors}, + tests::{compile_with_config, gen_qsharp, print_compilation_errors}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; @@ -23,19 +22,15 @@ c[1] = measure q[1]; fn it_compiles() { let source = SOURCE; - let res = parse(source).expect("should parse"); - assert!(!res.has_errors()); - let unit = qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::OpenQasm, - ProgramType::File, - Some("Test".into()), - None, - ), + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::File, + Some("Test".into()), + None, ); + let unit = compile_with_config(source, config).expect("parse failed"); + print_compilation_errors(&unit); assert!(!unit.has_errors()); let Some(package) = &unit.package else { diff --git a/compiler/qsc_qasm3/src/tests/sample_circuits/rgqft_multiplier.rs b/compiler/qsc_qasm/src/tests/sample_circuits/rgqft_multiplier.rs similarity index 82% rename from compiler/qsc_qasm3/src/tests/sample_circuits/rgqft_multiplier.rs rename to compiler/qsc_qasm/src/tests/sample_circuits/rgqft_multiplier.rs index a686856fa8..ee0f9387a7 100644 --- a/compiler/qsc_qasm3/src/tests/sample_circuits/rgqft_multiplier.rs +++ b/compiler/qsc_qasm/src/tests/sample_circuits/rgqft_multiplier.rs @@ -2,8 +2,7 @@ // Licensed under the MIT License. use crate::{ - qasm_to_program, - tests::{gen_qsharp, parse, print_compilation_errors}, + tests::{compile_with_config, gen_qsharp, print_compilation_errors}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; @@ -11,19 +10,15 @@ use crate::{ fn it_compiles() { let source = SOURCE; - let res = parse(source).expect("should parse"); - assert!(!res.has_errors()); - let unit = qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::OpenQasm, - ProgramType::File, - Some("Test".into()), - None, - ), + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::File, + Some("Test".into()), + None, ); + let unit = compile_with_config(source, config).expect("parse failed"); + print_compilation_errors(&unit); assert!(!unit.has_errors()); let Some(package) = &unit.package else { diff --git a/compiler/qsc_qasm/src/tests/scopes.rs b/compiler/qsc_qasm/src/tests/scopes.rs new file mode 100644 index 0000000000..15270a3562 --- /dev/null +++ b/compiler/qsc_qasm/src/tests/scopes.rs @@ -0,0 +1,226 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::tests::compile_qasm_to_qsharp; +use expect_test::expect; +use miette::Report; + +#[test] +fn can_access_const_decls_from_global_scope() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + const int i = 7; + gate my_h q { + if (i == 0) { + h q; + } + } + qubit q; + my_h q; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let i = 7; + operation my_h(q : Qubit) : Unit is Adj + Ctl { + if 7 == 0 { + h(q); + }; + } + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + my_h(q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn cannot_access_mutable_decls_from_global_scope() { + let source = r#" + include "stdgates.inc"; + int i; + gate my_h q { + if (i == 0) { + h q; + } + } + "#; + + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected an error"); + }; + expect!["undefined symbol: i"].assert_eq(&errors[0].to_string()); +} + +#[test] +fn gates_can_call_previously_declared_gates() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + gate my_h q { + h q; + } + gate my_hx q { + my_h q; + x q; + } + qubit q; + my_hx q; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + operation my_h(q : Qubit) : Unit is Adj + Ctl { + h(q); + } + operation my_hx(q : Qubit) : Unit is Adj + Ctl { + my_h(q); + x(q); + } + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + my_hx(q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn def_can_call_previously_declared_def() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + def apply_h(qubit q) { + h q; + } + def apply_hx(qubit q) { + apply_h(q); + x q; + } + qubit q; + apply_hx(q); + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + operation apply_h(q : Qubit) : Unit { + h(q); + } + operation apply_hx(q : Qubit) : Unit { + apply_h(q); + x(q); + } + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + apply_hx(q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn gate_can_call_previously_declared_def() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + def apply_h(qubit q) { + h q; + } + gate my_hx q { + apply_h(q); + x q; + } + qubit q; + my_hx q; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + operation apply_h(q : Qubit) : Unit { + h(q); + } + operation my_hx(q : Qubit) : Unit is Adj + Ctl { + apply_h(q); + x(q); + } + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + my_hx(q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn def_can_call_previously_declared_gate() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + gate my_h q { + h q; + } + def apply_hx(qubit q) { + my_h q; + x q; + } + qubit q; + apply_hx(q); + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + operation my_h(q : Qubit) : Unit is Adj + Ctl { + h(q); + } + operation apply_hx(q : Qubit) : Unit { + my_h(q); + x(q); + } + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + apply_hx(q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn def_can_call_itself_recursively() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + def apply_hx(int limit, qubit q) { + if (limit > 0) { + apply_hx(limit - 1, q); + x q; + } + h q; + } + qubit q; + apply_hx(2, q); + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + operation apply_hx(limit : Int, q : Qubit) : Unit { + if limit > 0 { + apply_hx(limit - 1, q); + x(q); + }; + h(q); + } + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + apply_hx(2, q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} diff --git a/compiler/qsc_qasm3/src/tests/statement.rs b/compiler/qsc_qasm/src/tests/statement.rs similarity index 94% rename from compiler/qsc_qasm3/src/tests/statement.rs rename to compiler/qsc_qasm/src/tests/statement.rs index 9938cb4160..495d7df324 100644 --- a/compiler/qsc_qasm3/src/tests/statement.rs +++ b/compiler/qsc_qasm/src/tests/statement.rs @@ -2,6 +2,7 @@ // Licensed under the MIT License. mod annotation; +mod const_eval; mod end; mod for_loop; mod gate_call; diff --git a/compiler/qsc_qasm/src/tests/statement/annotation.rs b/compiler/qsc_qasm/src/tests/statement/annotation.rs new file mode 100644 index 0000000000..b02929c326 --- /dev/null +++ b/compiler/qsc_qasm/src/tests/statement/annotation.rs @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::tests::compile_qasm_to_qsharp; +use expect_test::expect; +use miette::Report; + +#[test] +fn simulatable_intrinsic_can_be_applied_to_gate() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + @SimulatableIntrinsic + gate my_h q { + h q; + } + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + @SimulatableIntrinsic() + operation my_h(q : Qubit) : Unit { + h(q); + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn simulatable_intrinsic_can_be_applied_to_def() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + @SimulatableIntrinsic + def my_h(qubit q) { + h q; + } + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + @SimulatableIntrinsic() + operation my_h(q : Qubit) : Unit { + h(q); + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn config_can_be_applied_to_gate() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + @Config Base + gate my_h q { + h q; + } + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + @Config(Base) + operation my_h(q : Qubit) : Unit is Adj + Ctl { + h(q); + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn config_can_be_applied_to_def() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + @Config Base + def my_h(qubit q) { + h q; + } + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + @Config(Base) + operation my_h(q : Qubit) : Unit { + h(q); + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn unknown_annotation_raises_error() { + let source = r#" + include "stdgates.inc"; + @SomeUnknownAnnotation + gate my_h q { + h q; + } + "#; + + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected an error"); + }; + expect!["unexpected annotation: @SomeUnknownAnnotation"].assert_eq(&errors[0].to_string()); +} + +#[test] +fn annotation_without_target_in_global_scope_raises_error() { + let source = r#" + int i; + @SimulatableIntrinsic + "#; + + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected an error"); + }; + expect![r#"Annotation missing target statement."#].assert_eq(&errors[0].to_string()); +} + +#[test] +fn annotation_without_target_in_block_scope_raises_error() { + let source = r#" + int i; + if (0 == 1) { + @SimulatableIntrinsic + } + "#; + + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected an error"); + }; + expect![r#"Annotation missing target statement."#].assert_eq(&errors[0].to_string()); +} diff --git a/compiler/qsc_qasm/src/tests/statement/const_eval.rs b/compiler/qsc_qasm/src/tests/statement/const_eval.rs new file mode 100644 index 0000000000..8451088e90 --- /dev/null +++ b/compiler/qsc_qasm/src/tests/statement/const_eval.rs @@ -0,0 +1,2132 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! The tests in this file need to check that const exprs are +//! evaluatable at lowering time. To do that we use them in +//! contexts where they need to be const-evaluated, like array +//! sizes or type widths. + +use crate::tests::compile_qasm_to_qsharp; +use expect_test::expect; +use miette::Report; + +#[test] +fn const_exprs_work_in_bitarray_size_position() -> miette::Result<(), Vec> { + let source = r#" + const int a = 1; + const int b = 2 + a; + const int c = a + 3; + bit[b] r1; + bit[c] r2; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 1; + let b = 2 + a; + let c = a + 3; + mutable r1 = [Zero, Zero, Zero]; + mutable r2 = [Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn const_exprs_implicit_cast_work_in_bitarray_size_position() -> miette::Result<(), Vec> { + let source = r#" + const int a = 1; + const float b = 2.0 + a; + const float c = a + 3.0; + bit[b] r1; + bit[c] r2; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 1; + let b = 2. + Microsoft.Quantum.Convert.IntAsDouble(a); + let c = Microsoft.Quantum.Convert.IntAsDouble(a) + 3.; + mutable r1 = [Zero, Zero, Zero]; + mutable r2 = [Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn non_const_exprs_fail_in_bitarray_size_position() { + let source = r#" + const int a = 1; + int b = 2 + a; + int c = a + 3; + bit[b] r1; + bit[c] r2; + "#; + + let Err(errs) = compile_qasm_to_qsharp(source) else { + panic!("should have generated an error"); + }; + let errs: Vec<_> = errs.iter().map(|e| format!("{e:?}")).collect(); + let errs_string = errs.join("\n"); + expect![[r#" + Qasm.Compiler.ExprMustBeConst + + x expression must be const + ,-[Test.qasm:5:13] + 4 | int c = a + 3; + 5 | bit[b] r1; + : ^ + 6 | bit[c] r2; + `---- + + Qasm.Lowerer.ExprMustBeConst + + x designator must be a const expression + ,-[Test.qasm:5:13] + 4 | int c = a + 3; + 5 | bit[b] r1; + : ^ + 6 | bit[c] r2; + `---- + + Qasm.Compiler.ExprMustBeConst + + x expression must be const + ,-[Test.qasm:6:13] + 5 | bit[b] r1; + 6 | bit[c] r2; + : ^ + 7 | + `---- + + Qasm.Lowerer.ExprMustBeConst + + x designator must be a const expression + ,-[Test.qasm:6:13] + 5 | bit[b] r1; + 6 | bit[c] r2; + : ^ + 7 | + `---- + "#]] + .assert_eq(&errs_string); +} + +#[test] +fn can_assign_const_expr_to_non_const_decl() -> miette::Result<(), Vec> { + let source = r#" + const int a = 1; + const int b = 2; + int c = a + b; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 1; + let b = 2; + mutable c = a + b; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn ident_const() -> miette::Result<(), Vec> { + let source = r#" + const uint a = 1; + bit[a] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 1; + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "indexed ident is not yet supported"] +fn indexed_ident() -> miette::Result<(), Vec> { + let source = r#" + const array[uint, 2] a = {1, 2}; + bit[a[1]] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + let a = 1; + let b = 2; + mutable c = a + b; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// UnaryOp Float + +#[test] +fn unary_op_neg_float() -> miette::Result<(), Vec> { + let source = r#" + const float a = -1.0; + const float b = -a; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = -1.; + let b = -a; + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn unary_op_neg_int() -> miette::Result<(), Vec> { + let source = r#" + const int a = -1; + const int b = -a; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = -1; + let b = -a; + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn unary_op_neg_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle[32] a = -1.0; + const bit b = a; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = __DoubleAsAngle__(-1., 32); + let b = __AngleAsResult__(a); + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn unary_op_negb_uint() -> miette::Result<(), Vec> { + let source = r#" + const uint[3] a = 5; + const uint[3] b = ~a; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 5; + let b = ~~~a; + mutable r = [Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] + +fn unary_op_negb_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle[32] a = 1.0; + const bit b = ~a; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = new __Angle__ { + Value = 683565276, + Size = 32 + }; + let b = __AngleAsResult__(__AngleNotB__(a)); + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn unary_op_negb_bit() -> miette::Result<(), Vec> { + let source = r#" + const bit a = 0; + const bit b = ~a; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = Zero; + let b = ~~~a; + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn unary_op_negb_bitarray() -> miette::Result<(), Vec> { + let source = r#" + const bit[3] a = "101"; + const uint[3] b = ~a; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = [One, Zero, One]; + let b = __ResultArrayAsIntBE__(~~~a); + mutable r = [Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// BinaryOp + +#[test] +fn lhs_ty_equals_rhs_ty_assumption_holds() -> miette::Result<(), Vec> { + let source = r#" + const int a = 1; + const float b = 2.0; + const uint c = a + b; + bit[c] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 1; + let b = 2.; + let c = Microsoft.Quantum.Math.Truncate(Microsoft.Quantum.Convert.IntAsDouble(a) + b); + mutable r = [Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// BinaryOp: Bit Shifts + +// Shl + +#[test] +fn binary_op_shl_uint() -> miette::Result<(), Vec> { + let source = r#" + const uint a = 1; + const uint b = a << 2; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 1; + let b = a <<< 2; + mutable r = [Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] + +fn binary_op_shl_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle[32] a = 1.0; + const angle[32] b = a << 2; + const bit c = b; + bit[c] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = new __Angle__ { + Value = 683565276, + Size = 32 + }; + let b = __AngleShl__(a, 2); + let c = __AngleAsResult__(b); + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_shl_bit() -> miette::Result<(), Vec> { + let source = r#" + const bit a = 1; + const bit b = a << 2; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = One; + let b = if __ResultAsInt__(a) <<< 2 == 0 { + One + } else { + Zero + }; + mutable r = []; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_shl_bitarray() -> miette::Result<(), Vec> { + let source = r#" + const bit[3] a = "101"; + const bit[3] b = a << 2; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = [One, Zero, One]; + let b = __IntAsResultArrayBE__(__ResultArrayAsIntBE__(a) <<< 2, 3); + mutable r = [Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_shl_creg_fails() { + let source = r#" + const creg a[3] = "101"; + const creg b[3] = a << 2; + bit[b] r; + "#; + + let Err(errs) = compile_qasm_to_qsharp(source) else { + panic!("should have generated an error"); + }; + let errs: Vec<_> = errs.iter().map(|e| format!("{e:?}")).collect(); + let errs_string = errs.join("\n"); + expect![[r#" + Qasm.Parser.Rule + + x expected scalar or array type, found keyword `creg` + ,-[Test.qasm:2:15] + 1 | + 2 | const creg a[3] = "101"; + : ^^^^ + 3 | const creg b[3] = a << 2; + `---- + + Qasm.Parser.Rule + + x expected scalar or array type, found keyword `creg` + ,-[Test.qasm:3:15] + 2 | const creg a[3] = "101"; + 3 | const creg b[3] = a << 2; + : ^^^^ + 4 | bit[b] r; + `---- + + Qasm.Lowerer.UndefinedSymbol + + x undefined symbol: b + ,-[Test.qasm:4:13] + 3 | const creg b[3] = a << 2; + 4 | bit[b] r; + : ^ + 5 | + `---- + + Qasm.Lowerer.CannotCast + + x cannot cast expression of type Err to type UInt(None, true) + ,-[Test.qasm:4:13] + 3 | const creg b[3] = a << 2; + 4 | bit[b] r; + : ^ + 5 | + `---- + + Qasm.Compiler.ExprMustBeConst + + x expression must be const + ,-[Test.qasm:4:13] + 3 | const creg b[3] = a << 2; + 4 | bit[b] r; + : ^ + 5 | + `---- + + Qasm.Lowerer.ExprMustBeConst + + x designator must be a const expression + ,-[Test.qasm:4:13] + 3 | const creg b[3] = a << 2; + 4 | bit[b] r; + : ^ + 5 | + `---- + "#]] + .assert_eq(&errs_string); +} + +// Shr + +#[test] +fn binary_op_shr_uint() -> miette::Result<(), Vec> { + let source = r#" + const uint a = 5; + const uint b = a >> 2; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 5; + let b = a >>> 2; + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] + +fn binary_op_shr_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle[32] a = 1.0; + const angle[32] b = a >> 2; + const bit c = b; + bit[c] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = new __Angle__ { + Value = 683565276, + Size = 32 + }; + let b = __AngleShr__(a, 2); + let c = __AngleAsResult__(b); + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_shr_bit() -> miette::Result<(), Vec> { + let source = r#" + const bit a = 1; + const bit b = a >> 2; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = One; + let b = if __ResultAsInt__(a) >>> 2 == 0 { + One + } else { + Zero + }; + mutable r = []; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_shr_bitarray() -> miette::Result<(), Vec> { + let source = r#" + const bit[4] a = "1011"; + const bit[4] b = a >> 2; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = [One, Zero, One, One]; + let b = __IntAsResultArrayBE__(__ResultArrayAsIntBE__(a) >>> 2, 4); + mutable r = [Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_shr_creg_fails() { + let source = r#" + const creg a[4] = "1011"; + const creg b[4] = a >> 2; + bit[b] r; + "#; + + let Err(errs) = compile_qasm_to_qsharp(source) else { + panic!("should have generated an error"); + }; + let errs: Vec<_> = errs.iter().map(|e| format!("{e:?}")).collect(); + let errs_string = errs.join("\n"); + expect![[r#" + Qasm.Parser.Rule + + x expected scalar or array type, found keyword `creg` + ,-[Test.qasm:2:15] + 1 | + 2 | const creg a[4] = "1011"; + : ^^^^ + 3 | const creg b[4] = a >> 2; + `---- + + Qasm.Parser.Rule + + x expected scalar or array type, found keyword `creg` + ,-[Test.qasm:3:15] + 2 | const creg a[4] = "1011"; + 3 | const creg b[4] = a >> 2; + : ^^^^ + 4 | bit[b] r; + `---- + + Qasm.Lowerer.UndefinedSymbol + + x undefined symbol: b + ,-[Test.qasm:4:13] + 3 | const creg b[4] = a >> 2; + 4 | bit[b] r; + : ^ + 5 | + `---- + + Qasm.Lowerer.CannotCast + + x cannot cast expression of type Err to type UInt(None, true) + ,-[Test.qasm:4:13] + 3 | const creg b[4] = a >> 2; + 4 | bit[b] r; + : ^ + 5 | + `---- + + Qasm.Compiler.ExprMustBeConst + + x expression must be const + ,-[Test.qasm:4:13] + 3 | const creg b[4] = a >> 2; + 4 | bit[b] r; + : ^ + 5 | + `---- + + Qasm.Lowerer.ExprMustBeConst + + x designator must be a const expression + ,-[Test.qasm:4:13] + 3 | const creg b[4] = a >> 2; + 4 | bit[b] r; + : ^ + 5 | + `---- + "#]] + .assert_eq(&errs_string); +} + +// BinaryOp: Bitwise + +// AndB + +#[test] +fn binary_op_andb_uint() -> miette::Result<(), Vec> { + let source = r#" + const uint a = 5; + const uint b = a & 6; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 5; + let b = a &&& 6; + mutable r = [Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] + +fn binary_op_andb_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle[32] a = 1.0; + const angle[32] b = 2.0; + const angle[32] c = a & b; + const bit d = c; + bit[d] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = new __Angle__ { + Value = 683565276, + Size = 32 + }; + let b = new __Angle__ { + Value = 1367130551, + Size = 32 + }; + let c = __AngleAndB__(a, b); + let d = __AngleAsResult__(c); + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_andb_bit() -> miette::Result<(), Vec> { + let source = r#" + const bit a = 1; + const bit b = a & 0; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = One; + let b = if __ResultAsInt__(a) &&& 0 == 0 { + One + } else { + Zero + }; + mutable r = []; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_andb_bitarray() -> miette::Result<(), Vec> { + let source = r#" + const bit[4] a = "1011"; + const bit[4] b = a & "0110"; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = [One, Zero, One, One]; + let b = __IntAsResultArrayBE__(__ResultArrayAsIntBE__(a) &&& __ResultArrayAsIntBE__([Zero, One, One, Zero]), 4); + mutable r = [Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// OrB + +#[test] +fn binary_op_orb_uint() -> miette::Result<(), Vec> { + let source = r#" + const uint a = 5; + const uint b = a | 6; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 5; + let b = a ||| 6; + mutable r = [Zero, Zero, Zero, Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] + +fn binary_op_orb_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle[32] a = 1.0; + const angle[32] b = 2.0; + const angle[32] c = a | b; + const bool d = c; + bit[d] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = new __Angle__ { + Value = 683565276, + Size = 32 + }; + let b = new __Angle__ { + Value = 1367130551, + Size = 32 + }; + let c = __AngleOrB__(a, b); + let d = __AngleAsBool__(c); + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_orb_bit() -> miette::Result<(), Vec> { + let source = r#" + const bit a = 1; + const bit b = a | 0; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = One; + let b = if __ResultAsInt__(a) ||| 0 == 0 { + One + } else { + Zero + }; + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_orb_bitarray() -> miette::Result<(), Vec> { + let source = r#" + const bit[3] a = "001"; + const bit[3] b = a | "100"; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = [Zero, Zero, One]; + let b = __IntAsResultArrayBE__(__ResultArrayAsIntBE__(a) ||| __ResultArrayAsIntBE__([One, Zero, Zero]), 3); + mutable r = [Zero, Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// XorB + +#[test] +fn binary_op_xorb_uint() -> miette::Result<(), Vec> { + let source = r#" + const uint a = 5; + const uint b = a ^ 6; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 5; + let b = a ^^^ 6; + mutable r = [Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] + +fn binary_op_xorb_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle[32] a = 1.0; + const angle[32] b = 2.0; + const angle[32] c = a ^ b; + const bit d = c; + bit[d] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = new __Angle__ { + Value = 683565276, + Size = 32 + }; + let b = new __Angle__ { + Value = 1367130551, + Size = 32 + }; + let c = __AngleXorB__(a, b); + let d = __AngleAsResult__(c); + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_xorb_bit() -> miette::Result<(), Vec> { + let source = r#" + const bit a = 1; + const bit b = a ^ 1; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = One; + let b = if __ResultAsInt__(a) ^^^ 1 == 0 { + One + } else { + Zero + }; + mutable r = []; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_xorb_bitarray() -> miette::Result<(), Vec> { + let source = r#" + const bit[4] a = "1011"; + const bit[4] b = a ^ "1110"; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = [One, Zero, One, One]; + let b = __IntAsResultArrayBE__(__ResultArrayAsIntBE__(a) ^^^ __ResultArrayAsIntBE__([One, One, One, Zero]), 4); + mutable r = [Zero, Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// Binary Logical + +#[test] +fn binary_op_andl_bool() -> miette::Result<(), Vec> { + let source = r#" + const bool f = false; + const bool t = true; + bit[f && f] r1; + bit[f && t] r2; + bit[t && f] r3; + bit[t && t] r4; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let f = false; + let t = true; + mutable r1 = []; + mutable r2 = []; + mutable r3 = []; + mutable r4 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_orl_bool() -> miette::Result<(), Vec> { + let source = r#" + const bool f = false; + const bool t = true; + bit[f || f] r1; + bit[f || t] r2; + bit[t || f] r3; + bit[t || t] r4; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let f = false; + let t = true; + mutable r1 = []; + mutable r2 = [Zero]; + mutable r3 = [Zero]; + mutable r4 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// BinaryOp: Comparison + +// Eq + +#[test] +fn binary_op_comparison_int() -> miette::Result<(), Vec> { + let source = r#" + const int a = 2; + bit[a == a] r1; + bit[a != a] r2; + bit[a > a] r3; + bit[a >= a] r4; + bit[a < a] r5; + bit[a <= a] r6; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 2; + mutable r1 = [Zero]; + mutable r2 = []; + mutable r3 = []; + mutable r4 = [Zero]; + mutable r5 = []; + mutable r6 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_comparison_uint() -> miette::Result<(), Vec> { + let source = r#" + const uint a = 2; + bit[a == a] r1; + bit[a != a] r2; + bit[a > a] r3; + bit[a >= a] r4; + bit[a < a] r5; + bit[a <= a] r6; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 2; + mutable r1 = [Zero]; + mutable r2 = []; + mutable r3 = []; + mutable r4 = [Zero]; + mutable r5 = []; + mutable r6 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] + +fn binary_op_comparison_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle[32] a = 2.0; + bit[a == a] r1; + bit[a != a] r2; + bit[a > a] r3; + bit[a >= a] r4; + bit[a < a] r5; + bit[a <= a] r6; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = new __Angle__ { + Value = 1367130551, + Size = 32 + }; + mutable r1 = [Zero]; + mutable r2 = []; + mutable r3 = []; + mutable r4 = [Zero]; + mutable r5 = []; + mutable r6 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_comparison_bit() -> miette::Result<(), Vec> { + let source = r#" + const bit a = 1; + bit[a == a] r1; + bit[a != a] r2; + bit[a > a] r3; + bit[a >= a] r4; + bit[a < a] r5; + bit[a <= a] r6; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = One; + mutable r1 = [Zero]; + mutable r2 = []; + mutable r3 = []; + mutable r4 = [Zero]; + mutable r5 = []; + mutable r6 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_comparison_bitarray() -> miette::Result<(), Vec> { + let source = r#" + const bit[2] a = "10"; + bit[a == a] r1; + bit[a != a] r2; + bit[a > a] r3; + bit[a >= a] r4; + bit[a < a] r5; + bit[a <= a] r6; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = [One, Zero]; + mutable r1 = [Zero]; + mutable r2 = []; + mutable r3 = []; + mutable r4 = [Zero]; + mutable r5 = []; + mutable r6 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// BinaryOp: Arithmetic + +// Add + +#[test] +fn binary_op_add_int() -> miette::Result<(), Vec> { + let source = r#" + const int a = 1; + const int b = 2; + bit[a + b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 1; + let b = 2; + mutable r = [Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_add_uint() -> miette::Result<(), Vec> { + let source = r#" + const uint a = 1; + const uint b = 2; + bit[a + b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 1; + let b = 2; + mutable r = [Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_add_float() -> miette::Result<(), Vec> { + let source = r#" + const float a = 1.0; + const float b = 2.0; + bit[a + b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 1.; + let b = 2.; + mutable r = [Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] + +fn binary_op_add_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle[32] a = 1.0; + const angle[32] b = 2.0; + const bit c = a + b; + bit[c] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = new __Angle__ { + Value = 683565276, + Size = 32 + }; + let b = new __Angle__ { + Value = 1367130551, + Size = 32 + }; + let c = __AngleAsResult__(__AddAngles__(a, b)); + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// Sub + +#[test] +fn binary_op_sub_int() -> miette::Result<(), Vec> { + let source = r#" + const int a = 3; + const int b = 2; + bit[a - b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 3; + let b = 2; + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_sub_uint() -> miette::Result<(), Vec> { + let source = r#" + const uint a = 3; + const uint b = 2; + bit[a - b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 3; + let b = 2; + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_sub_float() -> miette::Result<(), Vec> { + let source = r#" + const float a = 3.0; + const float b = 2.0; + bit[a - b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 3.; + let b = 2.; + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] + +fn binary_op_sub_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle[32] a = 1.0; + const angle[32] b = 2.0; + const bit c = a - b; + bit[c] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = new __Angle__ { + Value = 683565276, + Size = 32 + }; + let b = new __Angle__ { + Value = 1367130551, + Size = 32 + }; + let c = __AngleAsResult__(__SubtractAngles__(a, b)); + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// Mul + +#[test] +fn binary_op_mul_int() -> miette::Result<(), Vec> { + let source = r#" + const int a = 3; + const int b = 2; + bit[a * b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 3; + let b = 2; + mutable r = [Zero, Zero, Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_mul_uint() -> miette::Result<(), Vec> { + let source = r#" + const uint a = 3; + const uint b = 2; + bit[a * b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 3; + let b = 2; + mutable r = [Zero, Zero, Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_mul_float() -> miette::Result<(), Vec> { + let source = r#" + const float a = 3.0; + const float b = 2.0; + bit[a * b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 3.; + let b = 2.; + mutable r = [Zero, Zero, Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] + +fn binary_op_mul_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle[32] a = 1.0; + const uint b = 2; + const bit c1 = a * b; + const bit c2 = b * a; + bit[c1] r1; + bit[c2] r2; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = new __Angle__ { + Value = 683565276, + Size = 32 + }; + let b = 2; + let c1 = __AngleAsResult__(__MultiplyAngleByInt__(a, b)); + let c2 = __AngleAsResult__(__MultiplyAngleByInt__(a, b)); + mutable r1 = [Zero]; + mutable r2 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// Div + +#[test] +fn binary_op_div_int() -> miette::Result<(), Vec> { + let source = r#" + const int a = 6; + const int b = 2; + bit[a / b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 6; + let b = 2; + mutable r = [Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_div_uint() -> miette::Result<(), Vec> { + let source = r#" + const uint a = 6; + const uint b = 2; + bit[a / b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 6; + let b = 2; + mutable r = [Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_div_float() -> miette::Result<(), Vec> { + let source = r#" + const float a = 6.0; + const float b = 2.0; + bit[a / b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 6.; + let b = 2.; + mutable r = [Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] + +fn binary_op_div_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle[32] a = 12.0; + const angle[48] b = 6.0; + const uint c = 2; + const bit d = a / b; + const bit e = a / c; + bit[d] r1; + bit[e] r2; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = new __Angle__ { + Value = 3907816011, + Size = 32 + }; + let b = new __Angle__ { + Value = 268788803401062, + Size = 48 + }; + let c = 2; + let d = if __DivideAngleByAngle__(__ConvertAngleToWidthNoTrunc__(a, 48), b) == 0 { + One + } else { + Zero + }; + let e = __AngleAsResult__(__DivideAngleByInt__(a, c)); + mutable r1 = []; + mutable r2 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// Mod + +#[test] +fn binary_op_mod_int() -> miette::Result<(), Vec> { + let source = r#" + const int a = 8; + bit[a % 3] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 8; + mutable r = [Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_mod_uint() -> miette::Result<(), Vec> { + let source = r#" + const uint a = 8; + bit[a % 3] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 8; + mutable r = [Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// Pow + +#[test] +fn binary_op_pow_int() -> miette::Result<(), Vec> { + let source = r#" + const int a = 2; + const int b = 3; + bit[a ** b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 2; + let b = 3; + mutable r = [Zero, Zero, Zero, Zero, Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_pow_uint() -> miette::Result<(), Vec> { + let source = r#" + const uint a = 2; + const uint b = 3; + bit[a ** b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 2; + let b = 3; + mutable r = [Zero, Zero, Zero, Zero, Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_pow_float() -> miette::Result<(), Vec> { + let source = r#" + const float a = 2.0; + const float b = 3.0; + bit[a ** b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 2.; + let b = 3.; + mutable r = [Zero, Zero, Zero, Zero, Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +// Cast + +#[test] +fn cast_to_bool() -> miette::Result<(), Vec> { + let source = r#" + const int a = 0; + const uint b = 1; + const float c = 2.0; + const angle[32] d = 2.0; + const bit e = 1; + + const bool s1 = a; + const bool s2 = b; + const bool s3 = c; + const bool s4 = d; + const bool s5 = e; + + bit[s1] r1; + bit[s2] r2; + bit[s3] r3; + bit[s4] r4; + bit[s5] r5; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 0; + let b = 1; + let c = 2.; + let d = new __Angle__ { + Value = 1367130551, + Size = 32 + }; + let e = One; + let s1 = if a == 0 { + false + } else { + true + }; + let s2 = if b == 0 { + false + } else { + true + }; + let s3 = if Microsoft.Quantum.Math.Truncate(c) == 0 { + false + } else { + true + }; + let s4 = __AngleAsBool__(d); + let s5 = __ResultAsBool__(e); + mutable r1 = []; + mutable r2 = [Zero]; + mutable r3 = [Zero]; + mutable r4 = [Zero]; + mutable r5 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn cast_to_int() -> miette::Result<(), Vec> { + let source = r#" + const bool a = true; + const uint b = 2; + const float c = 3.0; + const bit d = 0; + + const int s1 = a; + const int s2 = b; + const int s3 = c; + const int s4 = d; + + bit[s1] r1; + bit[s2] r2; + bit[s3] r3; + bit[s4] r4; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = true; + let b = 2; + let c = 3.; + let d = Zero; + let s1 = __BoolAsInt__(a); + let s2 = b; + let s3 = Microsoft.Quantum.Math.Truncate(c); + let s4 = __ResultAsInt__(d); + mutable r1 = [Zero]; + mutable r2 = [Zero, Zero]; + mutable r3 = [Zero, Zero, Zero]; + mutable r4 = []; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn cast_to_uint() -> miette::Result<(), Vec> { + let source = r#" + const bool a = true; + const uint b = 2; + const float c = 3.0; + const bit d = 0; + + const uint s1 = a; + const uint s2 = b; + const uint s3 = c; + const uint s4 = d; + + bit[s1] r1; + bit[s2] r2; + bit[s3] r3; + bit[s4] r4; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = true; + let b = 2; + let c = 3.; + let d = Zero; + let s1 = __BoolAsInt__(a); + let s2 = b; + let s3 = Microsoft.Quantum.Math.Truncate(c); + let s4 = __ResultAsInt__(d); + mutable r1 = [Zero]; + mutable r2 = [Zero, Zero]; + mutable r3 = [Zero, Zero, Zero]; + mutable r4 = []; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn cast_to_float() -> miette::Result<(), Vec> { + let source = r#" + const bool a = true; + const int b = 2; + const uint c = 3; + + const float s1 = a; + const float s2 = b; + const float s3 = c; + + bit[s1] r1; + bit[s2] r2; + bit[s3] r3; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = true; + let b = 2; + let c = 3; + let s1 = __BoolAsDouble__(a); + let s2 = Microsoft.Quantum.Convert.IntAsDouble(b); + let s3 = Microsoft.Quantum.Convert.IntAsDouble(c); + mutable r1 = [Zero]; + mutable r2 = [Zero, Zero]; + mutable r3 = [Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] + +fn cast_to_angle() -> miette::Result<(), Vec> { + let source = r#" + const float a1 = 2.0; + const bit a2 = 1; + + const angle[32] b1 = a1; + const angle[32] b2 = a2; + + const bit s1 = b1; + const bit s2 = b2; + + bit[s1] r1; + bit[s2] r2; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a1 = 2.; + let a2 = One; + let b1 = __DoubleAsAngle__(a1, 32); + let b2 = __ResultAsAngle__(a2); + let s1 = __AngleAsResult__(b1); + let s2 = __AngleAsResult__(b2); + mutable r1 = [Zero]; + mutable r2 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn cast_to_bit() -> miette::Result<(), Vec> { + let source = r#" + const bool a = false; + const int b = 1; + const uint c = 2; + const angle[32] d = 3.0; + + const bit s1 = a; + const bit s2 = b; + const bit s3 = c; + const bit s4 = d; + + bit[s1] r1; + bit[s2] r2; + bit[s3] r3; + bit[s4] r4; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = false; + let b = 1; + let c = 2; + let d = new __Angle__ { + Value = 2050695827, + Size = 32 + }; + let s1 = __BoolAsResult__(a); + let s2 = if b == 0 { + One + } else { + Zero + }; + let s3 = if c == 0 { + One + } else { + Zero + }; + let s4 = __AngleAsResult__(d); + mutable r1 = []; + mutable r2 = [Zero]; + mutable r3 = [Zero]; + mutable r4 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn binary_op_err_type_fails() { + let source = r#" + int[a + b] x = 2; + "#; + + let Err(errs) = compile_qasm_to_qsharp(source) else { + panic!("should have generated an error"); + }; + let errs: Vec<_> = errs.iter().map(|e| format!("{e:?}")).collect(); + let errs_string = errs.join("\n"); + expect![[r#" + Qasm.Lowerer.UndefinedSymbol + + x undefined symbol: a + ,-[Test.qasm:2:13] + 1 | + 2 | int[a + b] x = 2; + : ^ + 3 | + `---- + + Qasm.Lowerer.UndefinedSymbol + + x undefined symbol: b + ,-[Test.qasm:2:17] + 1 | + 2 | int[a + b] x = 2; + : ^ + 3 | + `---- + + Qasm.Lowerer.CannotCast + + x cannot cast expression of type Err to type UInt(None, true) + ,-[Test.qasm:2:13] + 1 | + 2 | int[a + b] x = 2; + : ^^^^^ + 3 | + `---- + + Qasm.Compiler.ExprMustBeConst + + x expression must be const + ,-[Test.qasm:2:13] + 1 | + 2 | int[a + b] x = 2; + : ^^^^^ + 3 | + `---- + + Qasm.Lowerer.ExprMustBeConst + + x designator must be a const expression + ,-[Test.qasm:2:13] + 1 | + 2 | int[a + b] x = 2; + : ^^^^^ + 3 | + `---- + + Qasm.Lowerer.CannotCastLiteral + + x cannot cast literal expression of type Int(None, true) to type Err + ,-[Test.qasm:2:9] + 1 | + 2 | int[a + b] x = 2; + : ^^^^^^^^^^^^^^^^^ + 3 | + `---- + "#]] + .assert_eq(&errs_string); +} + +#[test] +fn fuzzer_issue_2294() { + let source = r#" + ctrl(5/_)@l + "#; + + let Err(errs) = compile_qasm_to_qsharp(source) else { + panic!("should have generated an error"); + }; + let errs: Vec<_> = errs.iter().map(|e| format!("{e:?}")).collect(); + let errs_string = errs.join("\n"); + expect![[r#" + Qasm.Parser.Token + + x expected `;`, found EOF + ,-[Test.qasm:3:5] + 2 | ctrl(5/_)@l + 3 | + `---- + + Qasm.Parser.MissingGateCallOperands + + x missing gate call operands + ,-[Test.qasm:2:9] + 1 | + 2 | ctrl(5/_)@l + : ^^^^^^^^^^^ + 3 | + `---- + + Qasm.Lowerer.UndefinedSymbol + + x undefined symbol: _ + ,-[Test.qasm:2:16] + 1 | + 2 | ctrl(5/_)@l + : ^ + 3 | + `---- + + Qasm.Lowerer.CannotCast + + x cannot cast expression of type Err to type Float(None, true) + ,-[Test.qasm:2:16] + 1 | + 2 | ctrl(5/_)@l + : ^ + 3 | + `---- + + Qasm.Compiler.ExprMustBeConst + + x expression must be const + ,-[Test.qasm:2:16] + 1 | + 2 | ctrl(5/_)@l + : ^ + 3 | + `---- + + Qasm.Lowerer.ExprMustBeConst + + x ctrl modifier argument must be a const expression + ,-[Test.qasm:2:14] + 1 | + 2 | ctrl(5/_)@l + : ^^^ + 3 | + `---- + "#]] + .assert_eq(&errs_string); +} diff --git a/compiler/qsc_qasm3/src/tests/statement/end.rs b/compiler/qsc_qasm/src/tests/statement/end.rs similarity index 68% rename from compiler/qsc_qasm3/src/tests/statement/end.rs rename to compiler/qsc_qasm/src/tests/statement/end.rs index 411f9fe7ea..61a8a58703 100644 --- a/compiler/qsc_qasm3/src/tests/statement/end.rs +++ b/compiler/qsc_qasm/src/tests/statement/end.rs @@ -15,14 +15,15 @@ fn end_can_be_in_nested_scope() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable sum = 0; for i : Int in [1, 5, 10] { - fail "end" + fail "end"; } - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -34,6 +35,12 @@ fn end_can_be_in_global_scope() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![r#"fail "end""#].assert_eq(&qsharp); + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + fail "end"; + "#]] + .assert_eq(&qsharp); Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/statement/for_loop.rs b/compiler/qsc_qasm/src/tests/statement/for_loop.rs similarity index 81% rename from compiler/qsc_qasm3/src/tests/statement/for_loop.rs rename to compiler/qsc_qasm/src/tests/statement/for_loop.rs index 4c476bdf96..091037302b 100644 --- a/compiler/qsc_qasm3/src/tests/statement/for_loop.rs +++ b/compiler/qsc_qasm/src/tests/statement/for_loop.rs @@ -15,14 +15,15 @@ fn for_loops_can_iterate_over_discrete_set() -> miette::Result<(), Vec> "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable sum = 0; for i : Int in [1, 5, 10] { set sum += i; } - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -36,14 +37,15 @@ fn for_loops_can_have_stmt_bodies() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable sum = 0; for i : Int in [1, 5, 10] { set sum += i; } - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -58,14 +60,15 @@ fn for_loops_can_iterate_over_range() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable sum = 0; for i : Int in 0..2..20 { set sum += i; } - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -80,14 +83,15 @@ fn for_loops_can_iterate_float_set() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable sum = 0.; for f : Double in [1.2, -3.4, 0.5, 9.8] { set sum += f; } - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -104,15 +108,13 @@ fn for_loops_can_iterate_float_array_symbol() -> miette::Result<(), Vec> "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" mutable sum = 0.; let my_floats = [1.2, -3.4, 0.5, 9.8]; for f : Double in my_floats { set sum += f; } - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -133,22 +135,16 @@ fn for_loops_can_iterate_bit_register() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __ResultAsInt__(input : Result) : Int { - if Microsoft.Quantum.Convert.ResultAsBool(input) { - 1 - } else { - 0 - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable sum = 0; let reg = [One, Zero, One, Zero, One]; for b : Result in reg { set sum += __ResultAsInt__(b); } - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -165,5 +161,5 @@ fn loop_variables_should_be_scoped_to_for_loop() { panic!("Expected error"); }; - expect![r#"Undefined symbol: i."#].assert_eq(&errors[0].to_string()); + expect!["undefined symbol: i"].assert_eq(&errors[0].to_string()); } diff --git a/compiler/qsc_qasm/src/tests/statement/gate_call.rs b/compiler/qsc_qasm/src/tests/statement/gate_call.rs new file mode 100644 index 0000000000..b640e15448 --- /dev/null +++ b/compiler/qsc_qasm/src/tests/statement/gate_call.rs @@ -0,0 +1,692 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::tests::{compile_qasm_to_qir, compile_qasm_to_qsharp}; +use expect_test::expect; +use miette::Report; +use qsc::target::Profile; + +#[test] +fn u_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + qubit q; + U(1.0, 2.0, 3.0) q; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + U(__DoubleAsAngle__(1., 53), __DoubleAsAngle__(2., 53), __DoubleAsAngle__(3., 53), q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn gphase_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + gphase(2.0); + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + gphase(__DoubleAsAngle__(2., 53)); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn custom_gates_can_be_called_bypassing_stdgates() -> miette::Result<(), Vec> { + let source = r#" + gate h a { U(π/2, 0., π) a; gphase(-π/4);} + gate x a { U(π, 0., π) a; gphase(-π/2);} + gate cx a, b { ctrl @ x a, b; } + gate rz(λ) a { gphase(-λ/2); U(0., 0., λ) a; } + gate rxx(theta) a, b { h a; h b; cx a, b; rz(theta) b; cx a, b; h b; h a; } + + qubit a; + qubit b; + x a; + rxx(π/2) a, b; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + operation h(a : Qubit) : Unit is Adj + Ctl { + U(__DoubleAsAngle__(3.141592653589793 / 2., 53), __DoubleAsAngle__(0., 53), __DoubleAsAngle__(3.141592653589793, 53), a); + gphase(__DoubleAsAngle__(-3.141592653589793 / 4., 53)); + } + operation x(a : Qubit) : Unit is Adj + Ctl { + U(__DoubleAsAngle__(3.141592653589793, 53), __DoubleAsAngle__(0., 53), __DoubleAsAngle__(3.141592653589793, 53), a); + gphase(__DoubleAsAngle__(-3.141592653589793 / 2., 53)); + } + operation cx(a : Qubit, b : Qubit) : Unit is Adj + Ctl { + Controlled x([a], b); + } + operation rz(λ : __Angle__, a : Qubit) : Unit is Adj + Ctl { + gphase(__DivideAngleByInt__(__NegAngle__(λ), 2)); + U(__DoubleAsAngle__(0., 53), __DoubleAsAngle__(0., 53), λ, a); + } + operation rxx(theta : __Angle__, a : Qubit, b : Qubit) : Unit is Adj + Ctl { + h(a); + h(b); + cx(a, b); + rz(theta, b); + cx(a, b); + h(b); + h(a); + } + let a = QIR.Runtime.__quantum__rt__qubit_allocate(); + let b = QIR.Runtime.__quantum__rt__qubit_allocate(); + x(a); + rxx(__DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 2., 53), a, b); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn x_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit q; + x q; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + x(q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn barrier_can_be_called_on_single_qubit() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit q; + barrier q; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + __quantum__qis__barrier__body(); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn barrier_can_be_called_without_qubits() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit q; + barrier; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + __quantum__qis__barrier__body(); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn barrier_generates_qir() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + bit[1] c; + qubit[2] q; + barrier q[0], q[1]; + barrier q[0]; + barrier; + barrier q[0], q[1], q[0]; + c[0] = measure q[0]; + "#; + + let qsharp = compile_qasm_to_qir(source, Profile::AdaptiveRI)?; + expect![[ + r#" + %Result = type opaque + %Qubit = type opaque + + define void @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__qis__barrier__body() + call void @__quantum__qis__barrier__body() + call void @__quantum__qis__barrier__body() + call void @__quantum__qis__barrier__body() + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__array_record_output(i64 1, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + ret void + } + + declare void @__quantum__qis__barrier__body() + + declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + + declare void @__quantum__rt__array_record_output(i64, i8*) + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="2" "required_num_results"="1" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 1, !"int_computations", !"i64"} + "# + ]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn barrier_can_be_called_on_two_qubit() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit[2] q; + barrier q[0], q[1]; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.AllocateQubitArray(2); + __quantum__qis__barrier__body(); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn cx_called_with_one_qubit_generates_error() { + let source = r#" + include "stdgates.inc"; + qubit[2] q; + cx q[0]; + "#; + + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Lowerer.InvalidNumberOfQubitArgs + + x gate expects 2 qubit arguments, but 1 were provided + ,-[Test.qasm:4:9] + 3 | qubit[2] q; + 4 | cx q[0]; + : ^^^^^^^^ + 5 | + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} + +#[test] +fn cx_called_with_too_many_qubits_generates_error() { + let source = r#" + include "stdgates.inc"; + qubit[3] q; + cx q[0], q[1], q[2]; + "#; + + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Lowerer.InvalidNumberOfQubitArgs + + x gate expects 2 qubit arguments, but 3 were provided + ,-[Test.qasm:4:9] + 3 | qubit[3] q; + 4 | cx q[0], q[1], q[2]; + : ^^^^^^^^^^^^^^^^^^^^ + 5 | + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} + +#[test] +fn rx_gate_with_no_angles_generates_error() { + let source = r#" + include "stdgates.inc"; + qubit q; + rx q; + "#; + + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Lowerer.InvalidNumberOfClassicalArgs + + x gate expects 1 classical arguments, but 0 were provided + ,-[Test.qasm:4:9] + 3 | qubit q; + 4 | rx q; + : ^^^^^ + 5 | + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} + +#[test] +fn rx_gate_with_one_angle_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit q; + rx(2.0) q; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + rx(__DoubleAsAngle__(2., 53), q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn rx_gate_with_too_many_angles_generates_error() { + let source = r#" + include "stdgates.inc"; + qubit q; + rx(2.0, 3.0) q; + "#; + + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qasm.Lowerer.InvalidNumberOfClassicalArgs + + x gate expects 1 classical arguments, but 2 were provided + ,-[Test.qasm:4:9] + 3 | qubit q; + 4 | rx(2.0, 3.0) q; + : ^^^^^^^^^^^^^^^ + 5 | + `---- + ]"#]] + .assert_eq(&format!("{errors:?}")); +} + +#[test] +fn implicit_cast_to_angle_works() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit q; + float a = 2.0; + rx(a) q; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + mutable a = 2.; + rx(__DoubleAsAngle__(a, 53), q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn custom_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + gate my_gate q1, q2 { + h q1; + h q2; + } + + qubit[2] q; + my_gate q[0], q[1]; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + operation my_gate(q1 : Qubit, q2 : Qubit) : Unit is Adj + Ctl { + h(q1); + h(q2); + } + let q = QIR.Runtime.AllocateQubitArray(2); + my_gate(q[0], q[1]); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn custom_gate_can_be_called_with_inv_modifier() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + gate my_gate q1, q2 { + h q1; + h q2; + } + + qubit[2] q; + inv @ my_gate q[0], q[1]; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + operation my_gate(q1 : Qubit, q2 : Qubit) : Unit is Adj + Ctl { + h(q1); + h(q2); + } + let q = QIR.Runtime.AllocateQubitArray(2); + Adjoint my_gate(q[0], q[1]); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn custom_gate_can_be_called_with_ctrl_modifier() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + gate my_gate q1, q2 { + h q1; + h q2; + } + + qubit[2] ctl; + qubit[2] q; + ctrl(2) @ my_gate ctl[0], ctl[1], q[0], q[1]; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + operation my_gate(q1 : Qubit, q2 : Qubit) : Unit is Adj + Ctl { + h(q1); + h(q2); + } + let ctl = QIR.Runtime.AllocateQubitArray(2); + let q = QIR.Runtime.AllocateQubitArray(2); + Controlled my_gate([ctl[0], ctl[1]], (q[0], q[1])); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn custom_gate_can_be_called_with_negctrl_modifier() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + gate my_gate q1, q2 { + h q1; + h q2; + } + + qubit[2] ctl; + qubit[2] q; + negctrl(2) @ my_gate ctl[0], ctl[1], q[0], q[1]; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + operation my_gate(q1 : Qubit, q2 : Qubit) : Unit is Adj + Ctl { + h(q1); + h(q2); + } + let ctl = QIR.Runtime.AllocateQubitArray(2); + let q = QIR.Runtime.AllocateQubitArray(2); + ApplyControlledOnInt(0, my_gate, [ctl[0], ctl[1]], (q[0], q[1])); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn custom_gate_can_be_called_with_pow_modifier() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + gate my_gate q1, q2 { + h q1; + h q2; + } + + qubit[2] q; + pow(2) @ my_gate q[0], q[1]; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + operation my_gate(q1 : Qubit, q2 : Qubit) : Unit is Adj + Ctl { + h(q1); + h(q2); + } + let q = QIR.Runtime.AllocateQubitArray(2); + __Pow__(2, my_gate, (q[0], q[1])); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn simulatable_intrinsic_on_gate_stmt_generates_correct_qir() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + + @SimulatableIntrinsic + gate my_gate q { + x q; + } + + qubit q; + my_gate q; + bit result = measure q; + "#; + + let qsharp = compile_qasm_to_qir(source, Profile::AdaptiveRI)?; + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + define void @ENTRYPOINT__main() #0 { + block_0: + call void @my_gate(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__tuple_record_output(i64 0, i8* null) + ret void + } + + declare void @my_gate(%Qubit*) + + declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + + declare void @__quantum__rt__tuple_record_output(i64, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 1, !"int_computations", !"i64"} + "#]].assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn rxx_gate_with_one_angle_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + qubit[2] q; + rxx(2.0) q[1], q[0]; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.AllocateQubitArray(2); + rxx(__DoubleAsAngle__(2., 53), q[1], q[0]); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn ryy_gate_with_one_angle_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + qubit[2] q; + ryy(2.0) q[1], q[0]; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.AllocateQubitArray(2); + ryy(__DoubleAsAngle__(2., 53), q[1], q[0]); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn rzz_gate_with_one_angle_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + qubit[2] q; + rzz(2.0) q[1], q[0]; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.AllocateQubitArray(2); + rzz(__DoubleAsAngle__(2., 53), q[1], q[0]); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn all_qiskit_stdgates_can_be_called_included() -> miette::Result<(), Vec> { + let source = r#" + qubit[4] q; + rxx(pi / 2.0) q[1], q[0]; + ryy(pi / 2.0) q[1], q[0]; + rzz(pi / 2.0) q[1], q[0]; + dcx q[0], q[1]; + ecr q[0], q[1]; + r(pi / 2.0, pi / 4.0) q[1]; + rzx(pi / 2.0) q[1], q[0]; + cs q[0], q[1]; + csdg q[0], q[1]; + sxdg q[0]; + csx q[0], q[1]; + cu1(pi / 2.0) q[1], q[0]; + cu3(pi / 2.0, pi / 4.0, pi / 8.0) q[1], q[0]; + rccx q[0], q[1], q[2]; + c3sqrtx q[0], q[1], q[2], q[3]; + c3x q[0], q[1], q[2], q[3]; + rc3x q[0], q[1], q[2], q[3]; + xx_minus_yy(pi / 2.0, pi / 4.0) q[1], q[0]; + xx_plus_yy(pi / 2.0, pi / 4.0) q[1], q[0]; + ccz q[0], q[1], q[2]; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.AllocateQubitArray(4); + rxx(__DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 2., 53), q[1], q[0]); + ryy(__DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 2., 53), q[1], q[0]); + rzz(__DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 2., 53), q[1], q[0]); + dcx(q[0], q[1]); + ecr(q[0], q[1]); + r(__DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 2., 53), __DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 4., 53), q[1]); + rzx(__DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 2., 53), q[1], q[0]); + cs(q[0], q[1]); + csdg(q[0], q[1]); + sxdg(q[0]); + csx(q[0], q[1]); + cu1(__DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 2., 53), q[1], q[0]); + cu3(__DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 2., 53), __DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 4., 53), __DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 8., 53), q[1], q[0]); + rccx(q[0], q[1], q[2]); + c3sqrtx(q[0], q[1], q[2], q[3]); + c3x(q[0], q[1], q[2], q[3]); + rc3x(q[0], q[1], q[2], q[3]); + xx_minus_yy(__DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 2., 53), __DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 4., 53), q[1], q[0]); + xx_plus_yy(__DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 2., 53), __DoubleAsAngle__(Microsoft.Quantum.Math.PI() / 4., 53), q[1], q[0]); + ccz(q[0], q[1], q[2]); + "#]] + .assert_eq(&qsharp); + Ok(()) +} diff --git a/compiler/qsc_qasm3/src/tests/statement/if_stmt.rs b/compiler/qsc_qasm/src/tests/statement/if_stmt.rs similarity index 81% rename from compiler/qsc_qasm3/src/tests/statement/if_stmt.rs rename to compiler/qsc_qasm/src/tests/statement/if_stmt.rs index 6dbd2129f0..bfc7839d6e 100644 --- a/compiler/qsc_qasm3/src/tests/statement/if_stmt.rs +++ b/compiler/qsc_qasm/src/tests/statement/if_stmt.rs @@ -20,11 +20,11 @@ fn can_use_cond_with_implicit_cast_to_bool() -> miette::Result<(), Vec> let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" - function __ResultAsBool__(input : Result) : Bool { - Microsoft.Quantum.Convert.ResultAsBool(input) - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); - H(q); + h(q); mutable result = QIR.Intrinsic.__quantum__qis__m__body(q); if __ResultAsBool__(result) { Reset(q); @@ -49,11 +49,11 @@ fn can_use_negated_cond_with_implicit_cast_to_bool() -> miette::Result<(), Vec miette::Result<(), Vec miette::Result<(), Vec> { let source = r#" include "stdgates.inc"; @@ -79,17 +78,19 @@ fn then_branch_can_be_stmt() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); if 0 == 1 { - Z(q); + z(q); }; - "#]] + "#]] .assert_eq(&qsharp); Ok(()) } #[test] -#[ignore = "QASM3 Parser bug"] fn else_branch_can_be_stmt() -> miette::Result<(), Vec> { let source = r#" include "stdgates.inc"; @@ -100,19 +101,21 @@ fn else_branch_can_be_stmt() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); if 0 == 1 { - Z(q); + z(q); } else { - Y(q); + y(q); }; - "#]] + "#]] .assert_eq(&qsharp); Ok(()) } #[test] -#[ignore = "QASM3 Parser bug"] fn then_and_else_branch_can_be_stmt() -> miette::Result<(), Vec> { let source = r#" include "stdgates.inc"; @@ -123,13 +126,16 @@ fn then_and_else_branch_can_be_stmt() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); if 0 == 1 { - Z(q); + z(q); } else { - Y(q); + y(q); }; - "#]] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -147,6 +153,6 @@ fn using_cond_that_cannot_implicit_cast_to_bool_fail() { panic!("Expected error"); }; - expect![r#"Cannot cast expression of type Qubit to type Bool(False)"#] + expect!["cannot cast expression of type Qubit to type Bool(false)"] .assert_eq(&errors[0].to_string()); } diff --git a/compiler/qsc_qasm/src/tests/statement/implicit_modified_gate_call.rs b/compiler/qsc_qasm/src/tests/statement/implicit_modified_gate_call.rs new file mode 100644 index 0000000000..c75fca08fb --- /dev/null +++ b/compiler/qsc_qasm/src/tests/statement/implicit_modified_gate_call.rs @@ -0,0 +1,244 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::tests::compile_qasm_to_qsharp; +use expect_test::expect; +use miette::Report; + +#[test] +fn cy_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit ctl; + qubit target; + cy ctl, target; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let ctl = QIR.Runtime.__quantum__rt__qubit_allocate(); + let target = QIR.Runtime.__quantum__rt__qubit_allocate(); + Controlled y([ctl], target); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn cz_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit ctl; + qubit target; + cz ctl, target; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let ctl = QIR.Runtime.__quantum__rt__qubit_allocate(); + let target = QIR.Runtime.__quantum__rt__qubit_allocate(); + Controlled z([ctl], target); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn ch_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit ctl; + qubit target; + ch ctl, target; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let ctl = QIR.Runtime.__quantum__rt__qubit_allocate(); + let target = QIR.Runtime.__quantum__rt__qubit_allocate(); + Controlled h([ctl], target); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn sdg_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit q; + sdg q; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + Adjoint s(q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn tdg_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit q; + tdg q; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + Adjoint t(q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn crx_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit ctl; + qubit target; + crx(0.5) ctl, target; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let ctl = QIR.Runtime.__quantum__rt__qubit_allocate(); + let target = QIR.Runtime.__quantum__rt__qubit_allocate(); + Controlled rx([ctl], (__DoubleAsAngle__(0.5, 53), target)); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn cry_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit ctl; + qubit target; + cry(0.5) ctl, target; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let ctl = QIR.Runtime.__quantum__rt__qubit_allocate(); + let target = QIR.Runtime.__quantum__rt__qubit_allocate(); + Controlled ry([ctl], (__DoubleAsAngle__(0.5, 53), target)); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn crz_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit ctl; + qubit target; + crz(0.5) ctl, target; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let ctl = QIR.Runtime.__quantum__rt__qubit_allocate(); + let target = QIR.Runtime.__quantum__rt__qubit_allocate(); + Controlled rz([ctl], (__DoubleAsAngle__(0.5, 53), target)); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn cswap_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit ctl; + qubit[2] q; + cswap ctl, q[0], q[1]; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let ctl = QIR.Runtime.__quantum__rt__qubit_allocate(); + let q = QIR.Runtime.AllocateQubitArray(2); + Controlled swap([ctl], (q[0], q[1])); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn legacy_cx_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit ctl; + qubit target; + CX ctl, target; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let ctl = QIR.Runtime.__quantum__rt__qubit_allocate(); + let target = QIR.Runtime.__quantum__rt__qubit_allocate(); + Controlled x([ctl], target); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn legacy_cphase_gate_can_be_called() -> miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + qubit ctl; + qubit target; + cphase(1.0) ctl, target; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let ctl = QIR.Runtime.__quantum__rt__qubit_allocate(); + let target = QIR.Runtime.__quantum__rt__qubit_allocate(); + Controlled phase([ctl], (__DoubleAsAngle__(1., 53), target)); + "#]] + .assert_eq(&qsharp); + Ok(()) +} diff --git a/compiler/qsc_qasm/src/tests/statement/include.rs b/compiler/qsc_qasm/src/tests/statement/include.rs new file mode 100644 index 0000000000..ee9854ea5b --- /dev/null +++ b/compiler/qsc_qasm/src/tests/statement/include.rs @@ -0,0 +1,310 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::{ + tests::{compile_all_with_config, qsharp_from_qasm_compilation}, + CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, +}; +use expect_test::expect; +use miette::Report; + +#[test] +fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { + let source = r#" + OPENQASM 3.0; + include "stdgates.inc"; + include "custom_intrinsics.inc"; + bit[1] c; + qubit[1] q; + my_gate q[0]; + c[0] = measure q[0]; + "#; + let custom_intrinsics = r#" + @SimulatableIntrinsic + gate my_gate q { + x q; + } + "#; + let all_sources = [ + ("source0.qasm".into(), source.into()), + ("custom_intrinsics.inc".into(), custom_intrinsics.into()), + ]; + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + let r = compile_all_with_config("source0.qasm", all_sources, config)?; + let qsharp = qsharp_from_qasm_compilation(r)?; + expect![[r#" + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + @EntryPoint() + operation Test() : Result[] { + @SimulatableIntrinsic() + operation my_gate(q : Qubit) : Unit { + x(q); + } + mutable c = [Zero]; + let q = QIR.Runtime.AllocateQubitArray(1); + my_gate(q[0]); + set c w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[0]); + Microsoft.Quantum.Arrays.Reversed(c) + } + }"#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn programs_with_includes_with_includes_can_be_compiled() -> miette::Result<(), Vec> { + let source0 = r#" + include "stdgates.inc"; + include "source1.qasm"; + "#; + let source1 = r#"include "source2.qasm";"#; + let source2 = "bit a;"; + let all_sources = [ + ("source0.qasm".into(), source0.into()), + ("source1.qasm".into(), source1.into()), + ("source2.qasm".into(), source2.into()), + ]; + + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + let r = compile_all_with_config("source0.qasm", all_sources, config)?; + let qsharp = qsharp_from_qasm_compilation(r)?; + expect![[r#" + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + @EntryPoint() + operation Test() : Unit { + mutable a = Zero; + () + } + }"#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn including_stdgates_multiple_times_causes_symbol_redifintion_errors() { + let source0 = r#" + include "stdgates.inc"; + include "source1.qasm"; + "#; + let source1 = r#"include "source2.qasm";"#; + let source2 = r#"include "stdgates.inc";"#; + let all_sources = [ + ("source0.qasm".into(), source0.into()), + ("source1.qasm".into(), source1.into()), + ("source2.qasm".into(), source2.into()), + ]; + + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + + let Err(errors) = compile_all_with_config("main.qasm", all_sources, config) else { + panic!("expected errors") + }; + + let errors: Vec<_> = errors.iter().map(|e| format!("{e}")).collect(); + let errors_string = errors.join("\n"); + expect!["Not Found Could not resolve include file: main.qasm"].assert_eq(&errors_string); +} + +#[test] +fn multiple_include_in_same_file_errors() { + let main = r#" + include "source1.inc"; + include "source1.inc"; + "#; + let source1 = r#" + bit[1] c; + "#; + let all_sources = [ + ("main.qasm".into(), main.into()), + ("source1.inc".into(), source1.into()), + ]; + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + + let Err(errors) = compile_all_with_config("main.qasm", all_sources, config) else { + panic!("expected errors") + }; + + let errors: Vec<_> = errors.iter().map(|e| format!("{e}")).collect(); + let errors_string = errors.join("\n"); + expect!["source1.inc was already included in: main.qasm"].assert_eq(&errors_string); +} + +#[test] +fn multiple_include_in_different_files_errors() { + let main = r#" + include "source1.inc"; + include "source2.inc"; + "#; + let source1 = r#" + include "source3.inc"; + "#; + let source2 = r#" + include "source3.inc"; + "#; + let source3 = r#" + bit[1] c; + "#; + let all_sources = [ + ("main.qasm".into(), main.into()), + ("source1.inc".into(), source1.into()), + ("source2.inc".into(), source2.into()), + ("source3.inc".into(), source3.into()), + ]; + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + + let Err(errors) = compile_all_with_config("main.qasm", all_sources, config) else { + panic!("expected errors") + }; + + let errors: Vec<_> = errors.iter().map(|e| format!("{e}")).collect(); + let errors_string = errors.join("\n"); + expect!["source3.inc was already included in: source1.inc"].assert_eq(&errors_string); +} + +#[test] +fn self_include_errors() { + let main = r#" + include "source1.inc"; + "#; + let source1 = r#" + include "source1.inc"; + "#; + let all_sources = [ + ("main.qasm".into(), main.into()), + ("source1.inc".into(), source1.into()), + ]; + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + + let Err(errors) = compile_all_with_config("main.qasm", all_sources, config) else { + panic!("expected errors") + }; + + let errors: Vec<_> = errors.iter().map(|e| format!("{e}")).collect(); + let errors_string = errors.join("\n"); + expect![[r#" + Cyclic include: + source1.inc includes source1.inc"#]] + .assert_eq(&errors_string); +} + +#[test] +fn mutual_include_errors() { + let main = r#" + include "source1.inc"; + "#; + let source1 = r#" + include "source2.inc"; + "#; + let source2 = r#" + include "source1.inc"; + "#; + let all_sources = [ + ("main.qasm".into(), main.into()), + ("source1.inc".into(), source1.into()), + ("source2.inc".into(), source2.into()), + ]; + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + + let Err(errors) = compile_all_with_config("main.qasm", all_sources, config) else { + panic!("expected errors") + }; + + let errors: Vec<_> = errors.iter().map(|e| format!("{e}")).collect(); + let errors_string = errors.join("\n"); + expect![[r#" + Cyclic include: + source1.inc includes source2.inc + source2.inc includes source1.inc"#]] + .assert_eq(&errors_string); +} + +#[test] +fn cyclic_include_errors() { + let main = r#" + include "source1.inc"; + "#; + let source1 = r#" + include "source2.inc"; + "#; + let source2 = r#" + include "source3.inc"; + "#; + let source3 = r#" + include "source1.inc"; + "#; + let all_sources = [ + ("main.qasm".into(), main.into()), + ("source1.inc".into(), source1.into()), + ("source2.inc".into(), source2.into()), + ("source3.inc".into(), source3.into()), + ]; + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + + let Err(errors) = compile_all_with_config("main.qasm", all_sources, config) else { + panic!("expected errors") + }; + + let errors: Vec<_> = errors.iter().map(|e| format!("{e}")).collect(); + let errors_string = errors.join("\n"); + + expect![[r#" + Cyclic include: + source1.inc includes source2.inc + source2.inc includes source3.inc + source3.inc includes source1.inc"#]] + .assert_eq(&errors_string); +} diff --git a/compiler/qsc_qasm3/src/tests/statement/measure.rs b/compiler/qsc_qasm/src/tests/statement/measure.rs similarity index 67% rename from compiler/qsc_qasm3/src/tests/statement/measure.rs rename to compiler/qsc_qasm/src/tests/statement/measure.rs index c21b8e6a87..77c768c3e6 100644 --- a/compiler/qsc_qasm3/src/tests/statement/measure.rs +++ b/compiler/qsc_qasm/src/tests/statement/measure.rs @@ -4,6 +4,7 @@ use crate::tests::compile_qasm_to_qsharp; use expect_test::expect; use miette::Report; +use std::fmt::Write; #[test] fn single_qubit_can_be_measured_into_single_bit() -> miette::Result<(), Vec> { @@ -15,6 +16,9 @@ fn single_qubit_can_be_measured_into_single_bit() -> miette::Result<(), Vec miette::Result<(), Vec miette::Result<(), Vec> { let source = r#" bit c; @@ -34,10 +37,13 @@ fn single_qubit_can_be_arrow_measured_into_single_bit() -> miette::Result<(), Ve let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" - mutable c = Zero; - let q = QIR.Runtime.__quantum__rt__qubit_allocate(); - set c = QIR.Intrinsic.__quantum__qis__m__body(q); - "#]] + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = Zero; + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + set c = QIR.Intrinsic.__quantum__qis__m__body(q); + "#]] .assert_eq(&qsharp); Ok(()) } @@ -53,6 +59,9 @@ fn indexed_single_qubit_can_be_measured_into_indexed_bit_register( let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable c = [Zero]; let q = QIR.Runtime.AllocateQubitArray(1); set c w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[0]); @@ -72,6 +81,9 @@ fn indexed_single_qubit_can_be_measured_into_single_bit_register() -> miette::Re let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable c = Zero; let q = QIR.Runtime.AllocateQubitArray(1); set c = QIR.Intrinsic.__quantum__qis__m__body(q[0]); @@ -87,17 +99,29 @@ fn measuring_hardware_qubits_generates_an_error() { c = measure $2; "#; - let Err(err) = compile_qasm_to_qsharp(source) else { + let Err(errs) = compile_qasm_to_qsharp(source) else { panic!("Measuring HW qubit should have generated an error"); }; - assert!( - err.len() == 1, - "Expected a single error when measuring a HW qubit, got: {err:#?}" - ); - - assert!(err[0] - .to_string() - .contains("Hardware qubit operands are not supported")); + + let mut errs_string = String::new(); + + for err in errs { + writeln!(&mut errs_string, "{err:?}").expect(""); + } + + expect![[r#" + Qasm.Compiler.NotSupported + + x hardware qubit operands are not supported + ,-[Test.qasm:3:21] + 2 | bit c; + 3 | c = measure $2; + : ^^ + 4 | + `---- + + "#]] + .assert_eq(&errs_string); } #[test] @@ -109,6 +133,9 @@ fn value_from_measurement_can_be_dropped() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); QIR.Intrinsic.__quantum__qis__m__body(q); "#]] diff --git a/compiler/qsc_qasm3/src/tests/statement/modified_gate_call.rs b/compiler/qsc_qasm/src/tests/statement/modified_gate_call.rs similarity index 70% rename from compiler/qsc_qasm3/src/tests/statement/modified_gate_call.rs rename to compiler/qsc_qasm/src/tests/statement/modified_gate_call.rs index 0f535f8385..9872fe4fcc 100644 --- a/compiler/qsc_qasm3/src/tests/statement/modified_gate_call.rs +++ b/compiler/qsc_qasm/src/tests/statement/modified_gate_call.rs @@ -14,12 +14,13 @@ fn adj_x_gate_can_be_called() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); - Adjoint X(q); - "# - ] + Adjoint x(q); + "#]] .assert_eq(&qsharp); Ok(()) } @@ -33,12 +34,13 @@ fn adj_adj_x_gate_can_be_called() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); - Adjoint Adjoint X(q); - "# - ] + Adjoint Adjoint x(q); + "#]] .assert_eq(&qsharp); Ok(()) } @@ -53,13 +55,14 @@ fn multiple_controls_on_x_gate_can_be_called() -> miette::Result<(), Vec "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.AllocateQubitArray(3); let f = QIR.Runtime.__quantum__rt__qubit_allocate(); - Controlled X([q[1], q[0], q[2]], f); - "# - ] + Controlled x([q[1], q[0], q[2]], f); + "#]] .assert_eq(&qsharp); Ok(()) } @@ -75,14 +78,15 @@ fn repeated_multi_controls_on_x_gate_can_be_called() -> miette::Result<(), Vec miette::Result<( "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.AllocateQubitArray(2); let r = QIR.Runtime.AllocateQubitArray(3); let f = QIR.Runtime.__quantum__rt__qubit_allocate(); - Controlled Adjoint Controlled Adjoint X([q[1], r[0]], ([q[0], f, r[1]], r[2])); - "# - ] + Controlled Adjoint Controlled Adjoint x([q[1], r[0]], ([q[0], f, r[1]], r[2])); + "#]] .assert_eq(&qsharp); Ok(()) } @@ -120,13 +125,14 @@ fn multiple_controls_on_cx_gate_can_be_called() -> miette::Result<(), Vec miette::Result<(), Vec miette::Result<( "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.AllocateQubitArray(4); let f = QIR.Runtime.__quantum__rt__qubit_allocate(); - Adjoint ApplyControlledOnInt(0, Adjoint Controlled Rx, [q[1], q[0], q[2]], ([f], (0.5, q[3]))); - "# - ] + Adjoint ApplyControlledOnInt(0, Adjoint Controlled rx, [q[1], q[0], q[2]], ([f], (__DoubleAsAngle__(0.5, 53), q[3]))); + "#]] .assert_eq(&qsharp); Ok(()) } @@ -183,13 +191,14 @@ fn neg_ctrl_can_wrap_another_neg_crtl_modifier() -> miette::Result<(), Vec miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - operation __Pow__ < 'T > (N : Int, op : ('T => Unit is Adj), target : 'T) : Unit is Adj { - let op = if N > 0 { - () => op(target) - } else { - () => Adjoint op(target) - }; - for _ in 1..Microsoft.Quantum.Math.AbsI(N) { - op() - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.AllocateQubitArray(6); let f = QIR.Runtime.__quantum__rt__qubit_allocate(); - __Pow__(1, __Pow__, (1, __Pow__, (1, Controlled Rx, ([f], (0.5, q[5]))))); - "# - ] + __Pow__(1, __Pow__, (1, __Pow__, (1, Controlled rx, ([f], (__DoubleAsAngle__(0.5, 53), q[5]))))); + "#]] .assert_eq(&qsharp); Ok(()) } @@ -234,22 +234,13 @@ fn pow_can_be_applied_on_a_simple_gate() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - operation __Pow__ < 'T > (N : Int, op : ('T => Unit is Adj), target : 'T) : Unit is Adj { - let op = if N > 0 { - () => op(target) - } else { - () => Adjoint op(target) - }; - for _ in 1..Microsoft.Quantum.Math.AbsI(N) { - op() - } - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let f = QIR.Runtime.__quantum__rt__qubit_allocate(); - __Pow__(2, X, (f)); - "# - ] + __Pow__(2, x, (f)); + "#]] .assert_eq(&qsharp); Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/statement/reset.rs b/compiler/qsc_qasm/src/tests/statement/reset.rs similarity index 90% rename from compiler/qsc_qasm3/src/tests/statement/reset.rs rename to compiler/qsc_qasm/src/tests/statement/reset.rs index c6d9a7b86a..69957f09f2 100644 --- a/compiler/qsc_qasm3/src/tests/statement/reset.rs +++ b/compiler/qsc_qasm/src/tests/statement/reset.rs @@ -2,8 +2,7 @@ // Licensed under the MIT License. use crate::{ - qasm_to_program, - tests::{fail_on_compilation_errors, gen_qsharp, parse}, + tests::{compile_with_config, fail_on_compilation_errors, gen_qsharp}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; use expect_test::expect; @@ -24,35 +23,31 @@ fn reset_calls_are_generated_from_qasm() -> miette::Result<(), Vec> { meas[0] = measure q[0]; "#; - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::Qiskit, - ProgramType::File, - Some("Test".into()), - None, - ), + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, ); + let unit = compile_with_config(source, config)?; fail_on_compilation_errors(&unit); let qsharp = gen_qsharp(&unit.package.expect("no package found")); - expect![ - r#" - namespace qasm3_import { + expect![[r#" + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; @EntryPoint() operation Test() : Result[] { mutable meas = [Zero]; let q = QIR.Runtime.AllocateQubitArray(1); Reset(q[0]); - H(q[0]); + h(q[0]); set meas w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[0]); Microsoft.Quantum.Arrays.Reversed(meas) } - }"# - ] + }"#]] .assert_eq(&qsharp); Ok(()) diff --git a/compiler/qsc_qasm3/src/tests/statement/switch.rs b/compiler/qsc_qasm/src/tests/statement/switch.rs similarity index 71% rename from compiler/qsc_qasm3/src/tests/statement/switch.rs rename to compiler/qsc_qasm/src/tests/statement/switch.rs index 17aa7257bd..330b3271d2 100644 --- a/compiler/qsc_qasm3/src/tests/statement/switch.rs +++ b/compiler/qsc_qasm/src/tests/statement/switch.rs @@ -8,7 +8,7 @@ use miette::Report; #[test] fn default_is_optional() -> miette::Result<(), Vec> { let source = r#" - OPENQASM 3.0; + OPENQASM 3.1; int i = 15; switch (i) { case 1 { @@ -18,14 +18,15 @@ fn default_is_optional() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable i = 15; if i == 1 { set i = 2; }; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -33,7 +34,7 @@ fn default_is_optional() -> miette::Result<(), Vec> { #[test] fn default_as_only_case_causes_parse_error() { let source = r#" - OPENQASM 3.0; + OPENQASM 3.1; int i = 15; switch (i) { default { @@ -47,13 +48,13 @@ fn default_as_only_case_causes_parse_error() { panic!("Expected an error, got {res:?}"); }; assert_eq!(errors.len(), 1); - expect![r#"QASM3 Parse Error: expecting `case` keyword"#].assert_eq(&errors[0].to_string()); + expect![["missing switch statement cases"]].assert_eq(&errors[0].to_string()); } #[test] fn no_cases_causes_parse_error() { let source = r#" - OPENQASM 3.0; + OPENQASM 3.1; int i = 15; switch (i) { } @@ -64,13 +65,13 @@ fn no_cases_causes_parse_error() { panic!("Expected an error, got {res:?}"); }; assert_eq!(errors.len(), 1); - expect![r#"QASM3 Parse Error: expecting `case` keyword"#].assert_eq(&errors[0].to_string()); + expect![["missing switch statement cases"]].assert_eq(&errors[0].to_string()); } #[test] fn spec_case_1() -> miette::Result<(), Vec> { let source = r#" - OPENQASM 3.0; + OPENQASM 3.1; include "stdgates.inc"; qubit q; @@ -93,21 +94,22 @@ fn spec_case_1() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); mutable i = 15; if i == 1 or i == 3 or i == 5 { - H(q); + h(q); } elif i == 2 or i == 4 or i == 6 { - X(q); + x(q); } elif i == -1 { - Y(q); + y(q); } else { - Z(q); + z(q); }; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -115,7 +117,7 @@ fn spec_case_1() -> miette::Result<(), Vec> { #[test] fn spec_case_2() -> miette::Result<(), Vec> { let source = r#" - OPENQASM 3.0; + OPENQASM 3.1; include "stdgates.inc"; qubit q; @@ -140,23 +142,24 @@ fn spec_case_2() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); let A = 0; let B = 1; mutable i = 15; if i == A { - H(q); + h(q); } elif i == B { - X(q); + x(q); } elif i == B + 1 { - Y(q); + y(q); } else { - Z(q); + z(q); }; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -164,7 +167,7 @@ fn spec_case_2() -> miette::Result<(), Vec> { #[test] fn spec_case_3() -> miette::Result<(), Vec> { let source = r#" - OPENQASM 3.0; + OPENQASM 3.1; include "stdgates.inc"; qubit q; bit[2] b; @@ -186,38 +189,35 @@ fn spec_case_3() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp_file(source)?; - expect![ - r#" - namespace qasm3_import { + expect![[r#" + namespace qasm_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; @EntryPoint() operation Test() : Result[] { - function __ResultArrayAsIntBE__(results : Result[]) : Int { - Microsoft.Quantum.Convert.ResultArrayAsInt(Microsoft.Quantum.Arrays.Reversed(results)) - } let q = QIR.Runtime.__quantum__rt__qubit_allocate(); mutable b = [Zero, Zero]; if __ResultArrayAsIntBE__(b) == 0 { - H(q); + h(q); } elif __ResultArrayAsIntBE__(b) == 1 { - X(q); + x(q); } elif __ResultArrayAsIntBE__(b) == 2 { - Y(q); + y(q); } elif __ResultArrayAsIntBE__(b) == 3 { - Z(q); + z(q); }; b } - }"# - ] + }"#]] .assert_eq(&qsharp); Ok(()) } #[test] -#[ignore = "Function decls are not supported yet"] fn spec_case_4() -> miette::Result<(), Vec> { let source = r#" - OPENQASM 3.0; + OPENQASM 3.1; include "stdgates.inc"; qubit q; bit[2] b; @@ -249,10 +249,26 @@ fn spec_case_4() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - "# - ] + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + mutable b = [Zero, Zero]; + operation foo(i : Int, d : Qubit[]) : Result { + return QIR.Intrinsic.__quantum__qis__m__body(d[i]); + } + mutable i = 15; + mutable j = 1; + mutable k = 2; + mutable c1 = Zero; + let q0 = QIR.Runtime.AllocateQubitArray(8); + if i == 1 { + set j = k + __ResultAsInt__(foo(k, q0)); + } elif i == 2 { + mutable d = Microsoft.Quantum.Convert.IntAsDouble(j / k); + } elif i == 3 {} else {}; + "#]] .assert_eq(&qsharp); Ok(()) } @@ -260,7 +276,7 @@ fn spec_case_4() -> miette::Result<(), Vec> { #[test] fn spec_case_5() -> miette::Result<(), Vec> { let source = r#" - OPENQASM 3.0; + OPENQASM 3.1; include "stdgates.inc"; @@ -283,18 +299,19 @@ fn spec_case_5() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.AllocateQubitArray(8); mutable j = 30; mutable i = 0; if i == 1 or i == 2 or i == 5 or i == 12 {} elif i == 3 { if j == 10 or j == 15 or j == 20 { - H(q); + h(q); }; }; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/statement/while_loop.rs b/compiler/qsc_qasm/src/tests/statement/while_loop.rs similarity index 84% rename from compiler/qsc_qasm3/src/tests/statement/while_loop.rs rename to compiler/qsc_qasm/src/tests/statement/while_loop.rs index 2f92febde3..8d1ae79787 100644 --- a/compiler/qsc_qasm3/src/tests/statement/while_loop.rs +++ b/compiler/qsc_qasm/src/tests/statement/while_loop.rs @@ -24,14 +24,14 @@ fn can_iterate_over_mutable_var_cmp_expr() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" - function __ResultAsBool__(input : Result) : Bool { - Microsoft.Quantum.Convert.ResultAsBool(input) - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); mutable result = Zero; mutable i = 0; while i < 10 { - H(q); + h(q); set result = QIR.Intrinsic.__quantum__qis__m__body(q); if __ResultAsBool__(result) { set i += 1; @@ -55,6 +55,6 @@ fn using_cond_that_cannot_implicit_cast_to_bool_fail() { panic!("Expected error"); }; - expect![r#"Cannot cast expression of type Qubit to type Bool(False)"#] + expect!["cannot cast expression of type Qubit to type Bool(false)"] .assert_eq(&errors[0].to_string()); } diff --git a/compiler/qsc_qasm/src/types.rs b/compiler/qsc_qasm/src/types.rs new file mode 100644 index 0000000000..2d03e55a75 --- /dev/null +++ b/compiler/qsc_qasm/src/types.rs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::fmt::{self, Display, Formatter}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Complex { + pub real: f64, + pub imaginary: f64, +} + +impl Complex { + pub fn new(real: f64, imaginary: f64) -> Self { + Self { real, imaginary } + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub enum Type { + Angle(bool), + Bool(bool), + BigInt(bool), + Complex(bool), + Int(bool), + Double(bool), + Qubit, + Result(bool), + Tuple(Vec), + Range, + BoolArray(ArrayDimensions, bool), + BigIntArray(ArrayDimensions, bool), + IntArray(ArrayDimensions, bool), + DoubleArray(ArrayDimensions), + QubitArray(ArrayDimensions), + ResultArray(ArrayDimensions, bool), + TupleArray(ArrayDimensions, Vec), + /// Function or operation, with the number of classical parameters and qubits. + Callable(CallableKind, u32, u32), + #[default] + Err, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CallableKind { + /// A function. + Function, + /// An operation. + Operation, +} + +impl Display for CallableKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + CallableKind::Function => write!(f, "Function"), + CallableKind::Operation => write!(f, "Operation"), + } + } +} + +/// QASM supports up to seven dimensions, but we are going to limit it to three. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ArrayDimensions { + One(usize), + Two(usize, usize), + Three(usize, usize, usize), +} + +impl From<&crate::semantic::types::ArrayDimensions> for ArrayDimensions { + fn from(value: &crate::semantic::types::ArrayDimensions) -> Self { + match value { + crate::semantic::types::ArrayDimensions::One(dim) => { + ArrayDimensions::One(*dim as usize) + } + crate::semantic::types::ArrayDimensions::Two(dim1, dim2) => { + ArrayDimensions::Two(*dim1 as usize, *dim2 as usize) + } + crate::semantic::types::ArrayDimensions::Three(dim1, dim2, dim3) => { + ArrayDimensions::Three(*dim1 as usize, *dim2 as usize, *dim3 as usize) + } + _ => unimplemented!("Array dimensions greater than three are not supported."), + } + } +} + +impl Display for Type { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Type::Angle(_) => write!(f, "Angle"), + Type::Bool(_) => write!(f, "bool"), + Type::BigInt(_) => write!(f, "BigInt"), + Type::Complex(_) => write!(f, "Complex"), + Type::Int(_) => write!(f, "Int"), + Type::Double(_) => write!(f, "Double"), + Type::Qubit => write!(f, "Qubit"), + Type::Range => write!(f, "Range"), + Type::Result(_) => write!(f, "Result"), + Type::Tuple(types) => { + write!(f, "(")?; + for (i, ty) in types.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{ty}")?; + } + write!(f, ")") + } + Type::BoolArray(dim, _) => write!(f, "bool{dim}"), + Type::BigIntArray(dim, _) => write!(f, "BigInt{dim}"), + Type::IntArray(dim, _) => write!(f, "Int{dim}"), + Type::DoubleArray(dim) => write!(f, "Double{dim}"), + Type::QubitArray(dim) => write!(f, "Qubit{dim}"), + Type::ResultArray(dim, _) => write!(f, "Result{dim}"), + Type::TupleArray(dim, types) => { + write!(f, "(")?; + for (i, ty) in types.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{ty}")?; + } + write!(f, "){dim}") + } + Type::Callable(kind, num_classical, num_qubits) => { + write!(f, "Callable({kind}, {num_classical}, {num_qubits})") + } + Type::Err => write!(f, "Err"), + } + } +} + +impl Display for ArrayDimensions { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + ArrayDimensions::One(..) => write!(f, "[]"), + ArrayDimensions::Two(..) => write!(f, "[][]"), + ArrayDimensions::Three(..) => write!(f, "[][][]"), + } + } +} diff --git a/compiler/qsc_qasm3/src/compile.rs b/compiler/qsc_qasm3/src/compile.rs deleted file mode 100644 index 0be78c1467..0000000000 --- a/compiler/qsc_qasm3/src/compile.rs +++ /dev/null @@ -1,4280 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use core::panic; -use std::path::PathBuf; - -use crate::ast_builder::{ - self, build_arg_pat, build_array_reverse_expr, build_assignment_statement, build_attr, - build_barrier_call, build_binary_expr, build_cast_call, build_cast_call_two_params, - build_classical_decl, build_complex_binary_expr, build_complex_from_expr, - build_convert_call_expr, build_default_result_array_expr, build_expr_array_expr, - build_gate_call_param_expr, build_gate_decl_lambda, build_if_expr_then_block, - build_if_expr_then_block_else_block, build_if_expr_then_block_else_expr, - build_if_expr_then_expr_else_expr, build_implicit_return_stmt, - build_indexed_assignment_statement, build_lit_bigint_expr, build_lit_bool_expr, - build_lit_complex_expr, build_lit_double_expr, build_lit_int_expr, - build_lit_result_array_expr_from_bitstring, build_lit_result_expr, build_managed_qubit_alloc, - build_math_call_no_params, build_measure_call, build_operation_with_stmts, - build_path_ident_expr, build_range_expr, build_reset_call, build_stmt_semi_from_expr, - build_stmt_wrapped_block_expr, build_top_level_ns_with_item, build_tuple_expr, - build_unary_op_expr, build_unmanaged_qubit_alloc, build_unmanaged_qubit_alloc_array, - build_wrapped_block_expr, is_complex_binop_supported, managed_qubit_alloc_array, - map_qsharp_type_to_ast_ty, -}; - -use crate::oqasm_helpers::{ - binop_requires_symmetric_int_conversion, can_cast_literal_with_value_knowledge, - extract_dims_from_designator, get_designator_from_scalar_type, requires_symmetric_conversion, - requires_types_already_match_conversion, safe_u128_to_f64, span_for_named_item, - span_for_syntax_node, span_for_syntax_token, -}; -use crate::oqasm_types::{promote_to_uint_ty, promote_types, types_equal_except_const}; -use crate::runtime::{get_runtime_function_decls, RuntimeFunctions}; -use crate::symbols::IOKind; -use crate::symbols::Symbol; -use crate::symbols::SymbolTable; -use crate::types::{get_indexed_type, get_qsharp_gate_name, GateModifier, QasmTypedExpr}; -use crate::{ - CompilerConfig, OperationSignature, OutputSemantics, ProgramType, QubitSemantics, - SemanticError, SemanticErrorKind, -}; - -use ast::NodeId; -use ast::Package; -use ast::TopLevelNode; -use num_bigint::BigInt; -use oq3_semantics::types::{ArrayDims, IsConst, Type}; -use oq3_syntax::ast::{ - AnnotationStatement, ArithOp, BinaryOp, BitString, CastExpression, DelayStmt, Expr, - GateOperand, HasArgList, HasName, Literal, LiteralKind, Modifier, ParamList, ParenExpr, - PragmaStatement, Stmt, TimeUnit, TimingLiteral, UnaryOp, -}; -use oq3_syntax::SyntaxNode; -use oq3_syntax::{AstNode, HasTextName}; -use qsc_ast::ast; -use qsc_data_structures::span::Span; -use qsc_frontend::{compile::SourceMap, error::WithSource}; - -use crate::{parse::QasmSource, QasmCompileUnit}; - -#[cfg(test)] -pub(crate) mod tests; - -/// Compiles a QASM3 source to a Q# AST package. -#[must_use] -pub fn qasm_to_program( - source: QasmSource, - source_map: SourceMap, - config: CompilerConfig, -) -> QasmCompileUnit { - assert!(!source.has_errors(), "Source has errors"); - assert!( - source.parse_result().have_parse(), - "Source has not been successfully parsed" - ); - let compiler = QasmCompiler { - source, - source_map, - config, - stmts: Vec::new(), - runtime: RuntimeFunctions::default(), - errors: Vec::new(), - file_stack: Vec::new(), - symbols: SymbolTable::new(), - version: None, - next_gate_as_item: false, - }; - compiler.compile_program() -} - -struct QasmCompiler { - /// The root QASM source to compile. - source: QasmSource, - /// The source map of QASM sources for error reporting. - source_map: SourceMap, - /// The configuration for the compiler. - /// This includes the qubit semantics to follow when compiling to Q# AST. - /// The output semantics to follow when compiling to Q# AST. - /// The program type to compile to. - config: CompilerConfig, - /// The compiled statments accumulated during compilation. - stmts: Vec, - /// The runtime functions that need to be included at the end of - /// compilation - runtime: RuntimeFunctions, - errors: Vec>, - /// The file stack is used to track the current file for error reporting. - /// When we include a file, we push the file path to the stack and pop it - /// when we are done with the file. - /// This allows us to report errors with the correct file path. - file_stack: Vec, - symbols: SymbolTable, - /// The QASM version parsed from the source file. This is a placeholder - /// for future use. We may want to use this to generate/parse different code - /// based on the QASM version. - version: Option, - /// If the next gate should be compiled as a top level item instead of a lambda. - /// In order to close over captured variables, we compile gates as a lambda - /// operations; however, if the gate is annotated, we need to compile it as a - /// top level item as attributes are not supported on lambdas. This isn't an - /// issue as any gates that need attributes can't be used in a lambda anyway. - /// This value is set once we encounter an annotation statement and is reset - /// after the next gate is compiled, we run out of statements, or we encounter - /// an error. - next_gate_as_item: bool, -} - -impl QasmCompiler { - /// The main entry into compilation. This function will compile the - /// source file and build the appropriate package based on the - /// configuration. - fn compile_program(mut self) -> QasmCompileUnit { - self.compile_source(&self.source.clone()); - self.prepend_runtime_decls(); - let program_ty = self.config.program_ty.clone(); - let (package, signature) = match program_ty { - ProgramType::File => self.build_file(), - ProgramType::Operation => self.build_operation(), - ProgramType::Fragments => (self.build_fragments(), None), - }; - - QasmCompileUnit::new(self.source_map, self.errors, Some(package), signature) - } - - /// Prepends the runtime declarations to the beginning of the statements. - /// Any runtime functions that are required by the compiled code are set - /// in the `self.runtime` field during compilation. - /// - /// We could declare these as top level functions when compiling to - /// `ProgramType::File`, but prepending them to the statements is the - /// most flexible approach. - fn prepend_runtime_decls(&mut self) { - let mut runtime = get_runtime_function_decls(self.runtime); - self.stmts.splice(0..0, runtime.drain(..)); - } - - /// Build a package with namespace and an operation - /// containing the compiled statements. - fn build_file(&mut self) -> (Package, Option) { - let tree = self.source.tree(); - let whole_span = span_for_syntax_node(tree.syntax()); - let operation_name = self.config.operation_name(); - let (operation, mut signature) = self.create_entry_operation(operation_name, whole_span); - let ns = self.config.namespace(); - signature.ns = Some(ns.to_string()); - let top = build_top_level_ns_with_item(whole_span, ns, operation); - ( - Package { - nodes: Box::new([top]), - ..Default::default() - }, - Some(signature), - ) - } - - /// Creates an operation with the given name. - fn build_operation(&mut self) -> (Package, Option) { - let tree = self.source.tree(); - let whole_span = span_for_syntax_node(tree.syntax()); - let operation_name = self.config.operation_name(); - let (operation, signature) = self.create_entry_operation(operation_name, whole_span); - ( - Package { - nodes: Box::new([TopLevelNode::Stmt(Box::new(ast::Stmt { - kind: Box::new(ast::StmtKind::Item(Box::new(operation))), - span: whole_span, - id: NodeId::default(), - }))]), - ..Default::default() - }, - Some(signature), - ) - } - - /// Turns the compiled statements into package of top level nodes - fn build_fragments(&mut self) -> Package { - let nodes = self - .stmts - .drain(..) - .map(Box::new) - .map(TopLevelNode::Stmt) - .collect::>() - .into_boxed_slice(); - Package { - nodes, - ..Default::default() - } - } - - /// Root recursive function for compiling the source. - fn compile_source(&mut self, source: &QasmSource) { - // we push the file path to the stack so we can track the current file - // for reporting errors. This saves us from having to pass around - // the current QasmSource value. - self.file_stack.push(source.path()); - - let mut annotations = Vec::new(); - - // we keep an iterator of the includes so we can match them with the - // source includes. The include statements only have the path, but - // we have already loaded all of source files in the - // `source.includes()` - let mut includes = source.includes().iter(); - for stmt in source.tree().statements() { - match stmt { - Stmt::AnnotationStatement(annotation) => { - if let Some(annotation) = self.compile_annotation_stmt(&annotation) { - annotations.push(annotation); - self.next_gate_as_item = true; - } - } - Stmt::Include(include) => { - let Some(Some(path)) = include.file().map(|f| f.to_string()) else { - let span = span_for_syntax_node(include.syntax()); - let kind = SemanticErrorKind::IncludeStatementMissingPath(span); - self.push_semantic_error(kind); - continue; - }; - - // if we are not in the root we should not be able to include - // as this is a limitation of the QASM3 language - if !self.symbols.is_current_scope_global() { - let kind = SemanticErrorKind::IncludeNotInGlobalScope( - path.to_string(), - span_for_syntax_node(include.syntax()), - ); - self.push_semantic_error(kind); - continue; - } - - // special case for stdgates.inc - // it won't be in the includes list - if path.to_lowercase() == "stdgates.inc" { - self.define_stdgates(&include); - continue; - } - - let include = includes.next().expect("missing include"); - self.compile_source(include); - } - _ => { - if let Some(stmt) = self.compile_stmt(&stmt) { - if annotations.is_empty() { - self.stmts.push(stmt); - continue; - } - - // we drain the annotations regardless of whether the statement - // can have them attached or not. This is to prevent the attrs - // from being attached to the wrong statement. - - // If there is an error, we record the error, push the stmt - // without the annotations, and continue. - // The error is fatal for overall compilation, but this way we - // can continue to compile the rest of the statements - let mut stmt = stmt; - self.apply_annotations_to_stmt(&mut annotations, &mut stmt); - self.stmts.push(stmt); - self.next_gate_as_item = false; - } - } - } - } - - if !annotations.is_empty() { - let span = annotations.last().map(|x| x.span).unwrap_or_default(); - let kind = SemanticErrorKind::AnnotationWithoutStatement(span); - self.push_semantic_error(kind); - self.next_gate_as_item = false; - } - - // Finally we pop the file path from the stack so that we - // can return to the previous file for error handling. - self.file_stack.pop(); - } - - fn compile_stmts(&mut self, stmt: &[oq3_syntax::ast::Stmt]) -> Vec { - let mut annotations = Vec::new(); - let mut stmts = Vec::new(); - for stmt in stmt { - if let Stmt::AnnotationStatement(annotation) = stmt { - // we compile the annotation and push it to the annotations. - // If compiling fails, we record the error and continue. - if let Some(annotation) = self.compile_annotation_stmt(annotation) { - annotations.push(annotation); - self.next_gate_as_item = true; - } - continue; - } - let stmt = self.compile_stmt(stmt); - if stmt.is_none() { - continue; - } - let stmt = stmt.expect("stmt is not None"); - if annotations.is_empty() { - stmts.push(stmt); - continue; - } - let mut stmt = stmt; - - // we drain the annotations regardless of whether the statement - // can have them attached or not. This is to prevent the attrs - // from being attached to the wrong statement. - - // If there is an error, we record the error, push the stmt - // without the annotations, and continue. - // The error is fatal for overall compilation, but this way we - // can continue to compile the rest of the statements - - self.apply_annotations_to_stmt(&mut annotations, &mut stmt); - self.next_gate_as_item = false; - } - if !annotations.is_empty() { - let span = annotations.last().map(|x| x.span).unwrap_or_default(); - let kind = SemanticErrorKind::AnnotationWithoutStatement(span); - self.push_semantic_error(kind); - self.next_gate_as_item = false; - } - stmts - } - - fn apply_annotations_to_stmt( - &mut self, - annotations: &mut Vec, - stmt: &mut ast::Stmt, - ) { - let current_annotations: Vec<_> = annotations.drain(..).map(Box::new).collect(); - if let ast::StmtKind::Item(item) = stmt.kind.as_mut() { - if let ast::ItemKind::Callable(_) = item.kind.as_ref() { - let mut existing_attrs = item.attrs.to_vec(); - existing_attrs.extend(current_annotations); - item.attrs = existing_attrs.into_boxed_slice(); - } else { - let kind = SemanticErrorKind::InvalidAnnotationTarget(stmt.span); - self.push_semantic_error(kind); - } - } else { - let kind = SemanticErrorKind::InvalidAnnotationTarget(stmt.span); - self.push_semantic_error(kind); - } - } - - /// Match against the different types of statements and compile them - /// to the appropriate AST statement. There should be no logic here. - fn compile_stmt(&mut self, stmt: &oq3_syntax::ast::Stmt) -> Option { - match stmt { - Stmt::AliasDeclarationStatement(alias) => self.compile_alias_decl(alias), - Stmt::AssignmentStmt(assignment) => self.compile_assignment_stmt(assignment), - Stmt::Barrier(barrier) => self.compile_barrier_stmt(barrier), - Stmt::BreakStmt(break_stmt) => self.compile_break_stmt(break_stmt), - Stmt::ClassicalDeclarationStatement(decl) => self.compile_classical_decl(decl), - Stmt::ContinueStmt(continue_stmt) => self.compile_continue_stmt(continue_stmt), - Stmt::Def(def) => self.compile_def_decl(def), - Stmt::EndStmt(end) => Some(compile_end_stmt(end)), - Stmt::ExprStmt(expr) => self.compile_expr_stmt(expr), - Stmt::ForStmt(for_stmt) => self.compile_for_stmt(for_stmt), - Stmt::Gate(gate) => self.compile_gate_decl(gate), - Stmt::IfStmt(if_stmt) => self.compile_if_stmt(if_stmt), - Stmt::IODeclarationStatement(io_decl) => self.compile_io_decl_stmt(io_decl), - Stmt::LetStmt(let_stmt) => self.compile_let_stmt(let_stmt), - Stmt::Measure(measure) => self.compile_measure_stmt(measure), - Stmt::QuantumDeclarationStatement(decl) => self.compile_quantum_decl(decl), - Stmt::Reset(reset) => self.compile_reset_call(reset), - Stmt::SwitchCaseStmt(switch_case) => self.compile_switch_stmt(switch_case), - Stmt::VersionString(version) => self.compile_version_stmt(version), - Stmt::WhileStmt(while_stmt) => self.compile_while_stmt(while_stmt), - Stmt::Include(include) => self.compile_include_stmt(include), - Stmt::Cal(..) | Stmt::DefCal(..) | Stmt::DefCalGrammar(..) => { - self.compile_calibration_stmt(stmt) - } - Stmt::AnnotationStatement(..) => { - panic!("Annotation statements should have been handled in compile_stmts") - } - Stmt::DelayStmt(delay) => self.compile_delay_stmt(delay), - Stmt::PragmaStatement(pragma) => self.compile_pragma_stmt(pragma), - } - } - - fn compile_pragma_stmt(&mut self, stmt: &PragmaStatement) -> Option { - self.push_unsupported_error_message("Pragma statements", stmt.syntax()); - None - } - - fn compile_delay_stmt(&mut self, stmt: &DelayStmt) -> Option { - self.push_unsupported_error_message("Delay statements", stmt.syntax()); - None - } - - /// Annotations are defined by the compiler and their behavior are not part of the QASM3 - /// specification. They start with an `@` symbol and are used to define attributes for - /// the next statement. Their values are not validated by the compiler and are passed - /// directly to the AST as a string containing the whole line. It is up to the compiler - /// to interpret the annotation. - /// - /// We use annotations to define intrinsic gates that are simulatable in Q# mapping the - /// annotation to a Q# attribute. We also only allow the annotation to be attached to - /// gates. Any other usage will result in a semantic error. - fn compile_annotation_stmt(&mut self, stmt: &AnnotationStatement) -> Option { - let text = stmt.annotation_text(); - let span = span_for_syntax_node(stmt.syntax()); - if let "@SimulatableIntrinsic" = text.as_str() { - let (_at, name) = text.split_at(1); - Some(build_attr(name.to_string(), span)) - } else { - let span = span_for_syntax_node(stmt.syntax()); - let kind = SemanticErrorKind::UnknownAnnotation(text.to_string(), span); - self.push_semantic_error(kind); - None - } - } - - /// We don't support callibration statements in Q# so we push an error - fn compile_calibration_stmt(&mut self, stmt: &Stmt) -> Option { - self.push_calibration_error(stmt.syntax()); - None - } - - /// This function is always a indication of a error. Either the - /// program is declaring include in a non-global scope or the - /// include is not handled in `self.compile_source` properly. - fn compile_include_stmt(&mut self, include: &oq3_syntax::ast::Include) -> Option { - // if we are not in the root we should not be able to include - if !self.symbols.is_current_scope_global() { - let name = include.to_string(); - let span = span_for_syntax_node(include.syntax()); - let kind = SemanticErrorKind::IncludeNotInGlobalScope(name, span); - self.push_semantic_error(kind); - return None; - } - // if we are at the root and we have an include, we should have - // already handled it and we are in an invalid state - panic!("Include should have been handled in compile_source") - } - - /// Aliasing allows declared qubit bits and registers to be referred to by another name. - /// Aliasing can slice a qubit array or register. Aliasing uses the `let` keyword. - /// Example: - /// ```qasm - /// qubit[4] = q; - /// let qubit = q[1:3]; // qubit now points to the middle two qubits of q - /// // qubit[0] is now q[1], and qubit[1] is now q[2] - /// ``` - /// We can eventually support this for qubits, but we don't support it for registers. - /// Creating an array slice of results in Q# would be difficult as it would normally - /// copy the conents of the array. We could potentially create a view of the array, but - /// further investigation is needed. - fn compile_alias_decl( - &mut self, - alias: &oq3_syntax::ast::AliasDeclarationStatement, - ) -> Option { - let name = alias.name().expect("Alias declaration must have a name"); - let name_span = span_for_named_item(alias); - let decl_span = span_for_syntax_node(alias.syntax()); - let rhs = alias.expr().and_then(|f| self.compile_expr(&f))?; - - if !matches!(rhs.ty, Type::Qubit | Type::QubitArray(_)) { - let kind = SemanticErrorKind::CannotAliasType(format!("{:?}", rhs.ty), decl_span); - self.push_semantic_error(kind); - return None; - } - - let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&rhs.ty, name.syntax())?; - let symbol = Symbol { - name: name.to_string(), - span: span_for_syntax_node(name.syntax()), - ty: rhs.ty.clone(), - qsharp_ty, - io_kind: IOKind::Default, - }; - - if self.symbols.insert_symbol(symbol).is_err() { - self.push_redefined_symbol_error(name.to_string(), name_span); - return None; - } - - let name = name.to_string(); - let is_const = true; - let ty_span = Span::default(); - let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&rhs.ty, alias.syntax())?; - let stmt = build_classical_decl( - name, is_const, ty_span, decl_span, name_span, &qsharp_ty, rhs.expr, - ); - Some(stmt) - } - - /// Assignment statements have two forms: simple and indexed. - /// Simple assignments are of the form `ident = expr;` and indexed - /// assignments are of the form `ident[index] = expr;`. - /// This function will dispatch to the appropriate function based - /// on the type of assignment. - /// - /// While they are similar, the indexed assignment is far more complex. - fn compile_assignment_stmt( - &mut self, - assignment: &oq3_syntax::ast::AssignmentStmt, - ) -> Option { - if let Some(name) = assignment.identifier() { - self.compile_simple_assignment_stmt(assignment, &name) - } else { - self.compile_indexed_assignment_stmt(assignment) - } - } - - /// `ident = expr;` - fn compile_simple_assignment_stmt( - &mut self, - assignment: &oq3_syntax::ast::AssignmentStmt, - name: &oq3_syntax::ast::Identifier, - ) -> Option { - let name_span = span_for_named_item(assignment); - let assignment_span = span_for_syntax_node(assignment.syntax()); - let lhs_symbol = self - .symbols - .get_symbol_by_name(name.to_string().as_str())? - .clone(); - if lhs_symbol.ty.is_const() { - let kind = SemanticErrorKind::CannotUpdateConstVariable(name.to_string(), name_span); - self.push_semantic_error(kind); - // usually we'd return None here, but we'll continue to compile the rhs - // looking for more errors. There is nothing in this type of error that - // would prevent us from compiling the rhs. - } - // resolve the rhs expression to match the lhs type - let rhs = self.compile_expr_to_ty_with_casts( - assignment.rhs(), - &lhs_symbol.ty, - assignment.syntax(), - )?; - let stmt = build_assignment_statement(name_span, name.to_string(), rhs, assignment_span); - Some(stmt) - } - - /// `ident[index] = expr;` - fn compile_indexed_assignment_stmt( - &mut self, - assignment: &oq3_syntax::ast::AssignmentStmt, - ) -> Option { - let indexed_ident = assignment - .indexed_identifier() - .expect("assignment without name must have an indexed identifier"); - let name = indexed_ident - .identifier() - .expect("indexed identifier must have a name"); - let string_name = name.to_string(); - let name_span = span_for_named_item(&indexed_ident); - let stmt_span = span_for_syntax_node(assignment.syntax()); - let rhs_span = span_for_syntax_node(assignment.rhs()?.syntax()); - - // resolve the index expression - // we only support single index expressions for now - // but in the future we may support slice/range/array indexing - let indices: Vec<_> = indexed_ident - .index_operators() - .filter_map(|op| self.compile_index_operator(&op)) - .flatten() - .collect(); - - if indices.len() != 1 { - // This is a temporary limitation. We can only handle - // single index expressions for now. - let kind = SemanticErrorKind::IndexMustBeSingleExpr(span_for_syntax_node( - indexed_ident.syntax(), - )); - self.push_semantic_error(kind); - return None; - } - let index = indices[0].clone(); - - let lhs_symbol = self - .symbols - .get_symbol_by_name(name.to_string().as_str())? - .clone(); - if index.ty.num_dims() > lhs_symbol.ty.num_dims() { - let kind = SemanticErrorKind::TypeRankError(rhs_span); - self.push_semantic_error(kind); - } - let index_expr = index.expr.clone(); - - let Some(indexed_ty) = get_indexed_type(&lhs_symbol.ty) else { - let kind = SemanticErrorKind::CannotIndexType(format!("{:?}", lhs_symbol.ty), rhs_span); - self.push_semantic_error(kind); - return None; - }; - let rhs = - self.compile_expr_to_ty_with_casts(assignment.rhs(), &indexed_ty, assignment.syntax())?; - let stmt = - build_indexed_assignment_statement(name_span, string_name, index_expr, rhs, stmt_span); - Some(stmt) - } - - /// Barrier isn't supported in Q# so we insert a runtime function that is - /// a no-op for simulation but is `@SimulatableIntrinsic()` so that it can - /// be emited in code gen as `__quantum__qis__barrier__body()`. This - /// matches the existing `qiskit-qir` behavior. Qiskit barriers are - /// variadic, but QIR doesn't support variadic gates. - /// We should look at how to handle this better in the future. - fn compile_barrier_stmt(&mut self, barrier: &oq3_syntax::ast::Barrier) -> Option { - let qubit_args: Vec<_> = if let Some(qubits) = barrier.qubit_list() { - qubits - .gate_operands() - .map(|op| self.compile_gate_operand(&op).map(|x| x.expr)) - .collect() - } else { - vec![] - }; - - if qubit_args.iter().any(Option::is_none) { - // if any of the qubit arguments failed to compile, we can't proceed - // This can happen if the qubit is not defined or if the qubit was - // a hardware qubit - return None; - } - let call_span = span_for_syntax_node(barrier.syntax()); - // we don't support barrier, but we can insert a runtime function - // which will generate a barrier call in QIR - self.runtime.insert(RuntimeFunctions::Barrier); - Some(build_barrier_call(call_span)) - } - - /// Need to add break support in Q# to support break statements - fn compile_break_stmt(&mut self, break_stmt: &oq3_syntax::ast::BreakStmt) -> Option { - self.push_unsupported_error_message("break statements", break_stmt.syntax()); - None - } - - /// Classical decls are used to declare classical variables. They have two - /// main forms: - /// - `type ident;` - /// - `type ident = expr;` - /// - /// Q# requires classical variables to be initialized, so we will use the - /// default value for the type to initialize the variable. In theory this - /// isn't a problem as any classical variable that is used should be - /// initialized before use and would be a bug anyway. This leads to awkward - /// code in Q# where we have to initialize classical variables that are - /// always overwritten before use. - fn compile_classical_decl( - &mut self, - decl: &oq3_syntax::ast::ClassicalDeclarationStatement, - ) -> Option { - let name = decl.name().expect("classical declaration must have a name"); - let name_span = span_for_syntax_node(name.syntax()); - let scalar_ty = decl - .scalar_type() - .expect("Classical declaration must have a scalar type"); - let is_const = decl.const_token().is_some(); - // if we can't convert the scalar type, we can't proceed, an error has been pushed - let ty = self.get_semantic_type_from_scalar_type(&scalar_ty, is_const)?; - let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty, name.syntax())?; - - let symbol = Symbol { - name: name.to_string(), - span: name_span, - ty: ty.clone(), - qsharp_ty: qsharp_ty.clone(), - io_kind: IOKind::Default, - }; - - if self.symbols.insert_symbol(symbol).is_err() { - self.push_redefined_symbol_error(name.to_string(), name_span); - return None; - } - - // if there is an expression, compile it to match the decl type - let rhs = self.compile_expr_to_ty_with_casts(decl.expr(), &ty, decl.syntax())?; - - // create the let binding and assign the rhs to the lhs - let ty_span = span_for_syntax_node(scalar_ty.syntax()); - let stmt_span = span_for_syntax_node(decl.syntax()); - let stmt = build_classical_decl( - name.to_string(), - is_const, - ty_span, - stmt_span, - name_span, - &qsharp_ty, - rhs, - ); - - Some(stmt) - } - - /// The expr type is fixed so we try to resolve the expression to match the type - /// via implicit casts or literal conversion if possible. - fn compile_expr_to_ty_with_casts( - &mut self, - expr: Option, - ty: &Type, - node: &SyntaxNode, - ) -> Option { - let Some(expr) = expr else { - // In OpenQASM, classical variables may be uninitialized, but in Q#, - // they must be initialized. We will use the default value for the type - // to initialize the variable. - return self.get_default_value(ty, node); - }; - - // since we have an expr, we can refine the node for errors - let span = span_for_syntax_node(expr.syntax()); - - let rhs = self.compile_expr(&expr)?; - let rhs_ty = rhs.ty.clone(); - - // if we have an exact type match, we can use the rhs as is - if types_equal_except_const(ty, &rhs_ty) { - return Some(rhs.expr); - } - - if let Expr::Literal(literal) = &expr { - // if the rhs is a literal, we can try to cast it to the lhs type - // we can do better than just types given we have a literal value - if can_cast_literal(ty, &rhs_ty) || can_cast_literal_with_value_knowledge(ty, literal) { - return Some(self.cast_literal_expr_to_type(ty, &rhs, literal)?.expr); - } - // if we can't cast the literal, we can't proceed - // create a semantic error and return - let kind = SemanticErrorKind::CannotAssignToType( - format!("{:?}", rhs.ty), - format!("{ty:?}"), - span, - ); - self.push_semantic_error(kind); - return None; - } - let is_negated_lit = if let Expr::PrefixExpr(prefix_expr) = &expr { - if let Some(UnaryOp::Neg) = prefix_expr.op_kind() { - matches!(&prefix_expr.expr(), Some(Expr::Literal(..))) - } else { - false - } - } else { - false - }; - if matches!(ty, Type::UInt(..)) && is_negated_lit { - let kind = SemanticErrorKind::CannotAssignToType( - "Negative Int".to_string(), - format!("{ty:?}"), - span, - ); - self.push_semantic_error(kind); - return None; - } - if let Expr::PrefixExpr(prefix_expr) = &expr { - if let Some(UnaryOp::Neg) = prefix_expr.op_kind() { - if let Some(Expr::Literal(literal)) = &prefix_expr.expr() { - // if the rhs is a literal, we can try to cast it to the lhs type - // we can do better than just types given we have a literal value - - if can_cast_literal(ty, &rhs_ty) - || can_cast_literal_with_value_knowledge(ty, literal) - { - // if the literal is negated, we need to compile it as a negated literal - // This will only work for int/float as we can't express any other - // kind of negated literal - return Some(self.compile_negated_literal_as_ty(literal, Some(ty))?.expr); - } - // if we can't cast the literal, we can't proceed - // create a semantic error and return - let kind = SemanticErrorKind::CannotAssignToType( - format!("{:?}", rhs.ty), - format!("{ty:?}"), - span_for_syntax_node(node), - ); - self.push_semantic_error(kind); - return None; - } - } - } - // the lhs has a type, but the rhs may be of a different type with - // implicit and explicit conversions. We need to cast the rhs to the - // lhs type, but if that cast fails, we will have already pushed an error - // and we can't proceed - - Some(self.cast_expr_to_type(ty, &rhs, node)?.expr) - } - - /// Need to add continue support in Q# to support continue statements - fn compile_continue_stmt( - &mut self, - continue_stmt: &oq3_syntax::ast::ContinueStmt, - ) -> Option { - self.push_unsupported_error_message("continue statements", continue_stmt.syntax()); - None - } - - /// - /// Qiskit can't handle def statements, so we push an error - /// and return None. We also push a semantic error if the def - /// statement is not in the global scope. - fn compile_def_decl(&mut self, def: &oq3_syntax::ast::Def) -> Option { - let def_span = span_for_syntax_node(def.syntax()); - if !self.symbols.is_current_scope_global() { - let kind = SemanticErrorKind::QuantumDeclarationInNonGlobalScope(def_span); - self.push_semantic_error(kind); - return None; - } - self.push_unsupported_error_message("def declarations", def.syntax()); - None - } - - /// Some statements don't fall into the normal categories, so they - /// are handled here. This is a catch-all for non-declaration - /// assignments and calculations. - /// Example: - /// ```qasm - /// input int a; - /// input int b; - /// a * b; // this is an expr statement - /// ``` - fn compile_expr_stmt(&mut self, expr: &oq3_syntax::ast::ExprStmt) -> Option { - let expr = expr.expr()?; - let texpr = self.compile_expr(&expr)?; - Some(build_stmt_semi_from_expr(texpr.expr)) - } - - /// This is the heart of compilation. All statements eventually call this - /// function and it is where we start to build the AST. From here we start - /// to return `QasmTypedExpr` so that we can track the QASM type of for the - /// Q# expresssion that we are building. This is needed for type checking - /// and casting. We must make sure all types match while building the AST - /// as Q# doesn't have implicit casting and would otherwise fail to compile. - fn compile_expr(&mut self, expr: &oq3_syntax::ast::Expr) -> Option { - match expr { - Expr::ArrayExpr(array_expr) => self.compile_array_expr(array_expr, expr), - Expr::ArrayLiteral(array_literal) => { - self.compile_array_literal_expr(array_literal, expr) - } - Expr::BinExpr(bin_expr) => self.compile_bin_expr(bin_expr, expr), - Expr::BlockExpr(_) => { - // block expressions are handled by their containing statements - panic!("Block expressions should not be compiled directly") - } - Expr::BoxExpr(box_expr) => self.compile_box_expr(box_expr), - Expr::CallExpr(call_expr) => self.compile_call_expr(call_expr), - Expr::CastExpression(cast_expr) => self.compile_cast_expr(cast_expr), - Expr::GateCallExpr(gate_call_expr) => self.compile_gate_call_expr(gate_call_expr, expr), - Expr::GPhaseCallExpr(_) => { - panic!("GPhase expressions should not be compiled directly") - } - Expr::HardwareQubit(hardware_qubit) => { - self.compile_hardware_qubit_expr(hardware_qubit, expr) - } - Expr::Identifier(identifier) => self.compile_identifier_expr(identifier, expr), - Expr::IndexExpr(index_expr) => self.compile_index_expr(index_expr), - Expr::IndexedIdentifier(indexed_identifier) => { - self.compile_indexed_identifier_expr(indexed_identifier) - } - Expr::Literal(lit) => self.compile_literal_expr(lit, expr), - Expr::TimingLiteral(lit) => self.compile_timing_literal_expr(lit, expr), - Expr::MeasureExpression(measure_expr) => self.compile_measure_expr(measure_expr, expr), - Expr::ModifiedGateCallExpr(modified_gate_call_expr) => { - self.compile_modified_gate_call_expr(modified_gate_call_expr) - } - Expr::ParenExpr(paren_expr) => self.compile_paren_expr(paren_expr), - Expr::PrefixExpr(prefix_expr) => self.compile_prefix_expr(prefix_expr), - Expr::RangeExpr(range_expr) => self.compile_range_expr(range_expr, expr.syntax()), - Expr::ReturnExpr(return_expr) => self.compile_return_expr(return_expr), - } - } - - /// Qubit and bit registers are handled in the their own statements. - /// Arrays for classical variables would be handled here. Qiskit - /// can't handle array expressions yet, so we push an error and return None. - fn compile_array_expr( - &mut self, - _array_expr: &oq3_syntax::ast::ArrayExpr, - expr: &Expr, - ) -> Option { - self.push_unimplemented_error_message("array expressions", expr.syntax()); - None - } - - /// Qubit and bit registers are handled in the their own statements. - /// Arrays for classical variables would be handled here. Qiskit - /// can't handle array expressions yet, so we push an error and return None. - fn compile_array_literal_expr( - &mut self, - _array_literal: &oq3_syntax::ast::ArrayLiteral, - expr: &Expr, - ) -> Option { - self.push_unimplemented_error_message("array literal expressions", expr.syntax()); - None - } - - /// Create a binary expression from the given binary expression node - /// The binary expression is created by recursively compiling the left and right - /// expressions and then creating a binary expression from the compiled expressions - /// - /// This is more complex than it seems because we need to handle type promotion - /// and casting. The `OpenQASM3` language has a specific set of rules for casting - /// between types. The rules can be found at: - /// - /// - /// This harder than decl statements as we need to deal with promotion and casting - /// for both the lhs and rhs expressions instead of having a fixed LHS type. - /// - /// complex > float > int/uint - /// within type widths are promoted to the larger type - #[allow(clippy::too_many_lines)] - fn compile_bin_expr( - &mut self, - bin_expr: &oq3_syntax::ast::BinExpr, - expr: &Expr, - ) -> Option { - // We don't need to worry about semantic errors as binary expression - // must have a lhs and rhs expression and an operator. This is - // verified in the binary expression tests. - let op = bin_expr.op_kind()?; - let lhs_expr = bin_expr.lhs()?; - let rhs_expr = bin_expr.rhs()?; - - let lhs = self.compile_expr(&lhs_expr)?; - let rhs = self.compile_expr(&rhs_expr)?; - - if lhs.ty.is_quantum() { - let kind = SemanticErrorKind::QuantumTypesInBinaryExpression(lhs.expr.span); - self.push_semantic_error(kind); - } - if rhs.ty.is_quantum() { - let kind = SemanticErrorKind::QuantumTypesInBinaryExpression(rhs.expr.span); - self.push_semantic_error(kind); - } - - let qsop = self.map_bin_op(op, expr.syntax())?; - let is_assignment = matches!(op, oq3_syntax::ast::BinaryOp::Assignment { op: _ }); - - let left_type = lhs.ty.clone(); - let right_type = rhs.ty.clone(); - - if binop_requires_bitwise_conversion(op, &left_type) { - // if the operator requires bitwise conversion, we need to determine - // what size of UInt to promote to. If we can't promote to a UInt, we - // push an error and return None. - let (ty, lhs_uint_promotion, rhs_uint_promotion) = - promote_to_uint_ty(&left_type, &right_type); - let Some(ty) = ty else { - let target_ty = Type::UInt(None, IsConst::False); - if lhs_uint_promotion.is_none() { - let target_str: String = format!("{target_ty:?}"); - let kind = SemanticErrorKind::CannotCast( - format!("{left_type:?}"), - target_str, - lhs.expr.span, - ); - self.push_semantic_error(kind); - } - if rhs_uint_promotion.is_none() { - let target_str: String = format!("{target_ty:?}"); - let kind = SemanticErrorKind::CannotCast( - format!("{right_type:?}"), - target_str, - rhs.expr.span, - ); - self.push_semantic_error(kind); - } - return None; - }; - // Now that we know the effective Uint type, we can cast the lhs and rhs - // so that operations can be performed on them. - let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs_expr.syntax())?; - // only cast the rhs if the operator requires symmetric conversion - let new_rhs = if binop_requires_bitwise_symmetric_conversion(op) { - self.cast_expr_to_type(&ty, &rhs, rhs_expr.syntax())? - } else { - rhs - }; - let expr = build_binary_expr( - is_assignment, - qsop, - new_lhs.expr, - new_rhs.expr, - span_for_syntax_node(expr.syntax()), - ); - let texpr = QasmTypedExpr { ty, expr }; - let final_expr = self.cast_expr_to_type(&left_type, &texpr, bin_expr.syntax())?; - return Some(final_expr); - } - - let span = span_for_syntax_node(expr.syntax()); - - if requires_types_already_match_conversion(op) { - if !types_equal_except_const(&left_type, &right_type) { - let kind = SemanticErrorKind::CannotApplyOperatorToTypes( - format!("{op:?}"), - format!("{left_type:?}"), - format!("{right_type:?}"), - span, - ); - self.push_semantic_error(kind); - return None; - } - let expr = build_binary_expr(is_assignment, qsop, lhs.expr, rhs.expr, span); - return Some(QasmTypedExpr { - ty: left_type, - expr, - }); - } - - // for int, uint, float, and complex, the lesser of the two types is cast to - // the greater of the two. Within each level of complex and float, types with - // greater width are greater than types with lower width. - // complex > float > int/uint - // Q# has built-in functions: IntAsDouble, IntAsBigInt to handle two cases. - // If the width of a float is greater than 64, we can't represent it as a double. - - let (lhs, rhs, ty) = if binop_requires_bool_conversion_for_type(op) { - let ty = Type::Bool(IsConst::False); - let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs_expr.syntax())?; - let new_rhs = self.cast_expr_to_type(&ty, &rhs, rhs_expr.syntax())?; - (new_lhs.expr, new_rhs.expr, ty) - } else if binop_requires_int_conversion_for_type(op, &left_type, &rhs.ty) { - let ty = Type::Int(None, IsConst::False); - let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs_expr.syntax())?; - let new_rhs = self.cast_expr_to_type(&ty, &rhs, rhs_expr.syntax())?; - (new_lhs.expr, new_rhs.expr, ty) - } else if requires_symmetric_conversion(op) { - let promoted_type = try_promote_with_casting(&left_type, &right_type); - let new_left = if promoted_type == left_type { - lhs - } else { - let node = lhs_expr.syntax(); - match &lhs_expr { - Expr::Literal(literal) => { - if can_cast_literal(&promoted_type, &left_type) - || can_cast_literal_with_value_knowledge(&promoted_type, literal) - { - self.cast_literal_expr_to_type(&promoted_type, &lhs, literal)? - } else { - self.cast_expr_to_type(&promoted_type, &lhs, node)? - } - } - _ => self.cast_expr_to_type(&promoted_type, &lhs, node)?, - } - }; - let new_right = if promoted_type == right_type { - rhs - } else { - let node = rhs_expr.syntax(); - match &rhs_expr { - Expr::Literal(literal) => { - if can_cast_literal(&promoted_type, &right_type) - || can_cast_literal_with_value_knowledge(&promoted_type, literal) - { - self.cast_literal_expr_to_type(&promoted_type, &rhs, literal)? - } else { - self.cast_expr_to_type(&promoted_type, &rhs, node)? - } - } - _ => self.cast_expr_to_type(&promoted_type, &rhs, node)?, - } - }; - (new_left.expr, new_right.expr, promoted_type) - } else { - // we don't have symmetric promotion, so we need to promote the rhs only - if is_assignment { - let oq3_syntax::ast::BinaryOp::Assignment { op: arith_op } = op else { - unreachable!() - }; - let (lhs, rhs) = - if arith_op.is_some() && binop_requires_symmetric_int_conversion(op) { - let ty = Type::Int(None, IsConst::False); - let lhs = self.cast_expr_to_type(&ty, &lhs, lhs_expr.syntax())?.expr; - let rhs = self.cast_expr_to_type(&ty, &rhs, rhs_expr.syntax())?.expr; - (lhs, rhs) - } else { - let rhs = self.compile_expr_to_ty_with_casts( - Some(rhs_expr.clone()), - &left_type, - rhs_expr.syntax(), - )?; - (lhs.expr, rhs) - }; - - (lhs, rhs, left_type) - } else if binop_requires_symmetric_int_conversion(op) { - let ty = Type::Int(None, IsConst::False); - let new_rhs = self.cast_expr_to_type(&ty, &rhs, rhs_expr.syntax())?; - (lhs.expr, new_rhs.expr, left_type) - } else { - (lhs.expr, rhs.expr, left_type) - } - }; - - // now that we have the lhs and rhs expressions, we can create the binary expression - // but we need to check if the chosen operator is supported by the types after - // promotion and conversion. - - let expr = if matches!(ty, Type::Complex(..)) { - if is_assignment { - let kind = SemanticErrorKind::ComplexBinaryAssignment(span); - self.push_semantic_error(kind); - None - } else if is_complex_binop_supported(qsop) { - Some(build_complex_binary_expr( - is_assignment, - qsop, - lhs, - rhs, - span, - )) - } else { - let kind = SemanticErrorKind::OperatorNotSupportedForTypes( - format!("{qsop:?}"), - format!("{ty:?}"), - format!("{ty:?}"), - span, - ); - self.push_semantic_error(kind); - None - } - } else { - Some(build_binary_expr(is_assignment, qsop, lhs, rhs, span)) - }; - let expr = expr?; - let ty = match &op { - BinaryOp::CmpOp(..) | BinaryOp::LogicOp(..) => Type::Bool(IsConst::False), - _ => ty, - }; - Some(QasmTypedExpr { ty, expr }) - } - - fn map_bin_op( - &mut self, - op: oq3_syntax::ast::BinaryOp, - node: &SyntaxNode, - ) -> Option { - match op { - oq3_syntax::ast::BinaryOp::LogicOp(logic_op) => Some(match logic_op { - oq3_syntax::ast::LogicOp::And => ast::BinOp::AndL, - oq3_syntax::ast::LogicOp::Or => ast::BinOp::OrL, - }), - oq3_syntax::ast::BinaryOp::ArithOp(arith) => Some(match arith { - oq3_syntax::ast::ArithOp::Add => ast::BinOp::Add, - oq3_syntax::ast::ArithOp::Mul => ast::BinOp::Mul, - oq3_syntax::ast::ArithOp::Sub => ast::BinOp::Sub, - oq3_syntax::ast::ArithOp::Div => ast::BinOp::Div, - oq3_syntax::ast::ArithOp::Rem => ast::BinOp::Mod, - oq3_syntax::ast::ArithOp::Shl => ast::BinOp::Shl, - oq3_syntax::ast::ArithOp::Shr => ast::BinOp::Shr, - oq3_syntax::ast::ArithOp::BitXor => ast::BinOp::XorB, - oq3_syntax::ast::ArithOp::BitOr => ast::BinOp::OrB, - oq3_syntax::ast::ArithOp::BitAnd => ast::BinOp::AndB, - }), - oq3_syntax::ast::BinaryOp::CmpOp(cmp_op) => Some(match cmp_op { - oq3_syntax::ast::CmpOp::Eq { negated } => { - if negated { - ast::BinOp::Neq - } else { - ast::BinOp::Eq - } - } - oq3_syntax::ast::CmpOp::Ord { ordering, strict } => match ordering { - oq3_syntax::ast::Ordering::Less => { - if strict { - ast::BinOp::Lt - } else { - ast::BinOp::Lte - } - } - oq3_syntax::ast::Ordering::Greater => { - if strict { - ast::BinOp::Gt - } else { - ast::BinOp::Gte - } - } - }, - }), - oq3_syntax::ast::BinaryOp::ConcatenationOp => { - // This is only used for types which we don't currently support. - self.push_unimplemented_error_message("Concatenation operators", node); - None - } - oq3_syntax::ast::BinaryOp::Assignment { op } => op.map(|op| match op { - oq3_syntax::ast::ArithOp::Add => ast::BinOp::Add, - oq3_syntax::ast::ArithOp::Mul => ast::BinOp::Mul, - oq3_syntax::ast::ArithOp::Sub => ast::BinOp::Sub, - oq3_syntax::ast::ArithOp::Div => ast::BinOp::Div, - oq3_syntax::ast::ArithOp::Rem => ast::BinOp::Mod, - oq3_syntax::ast::ArithOp::Shl => ast::BinOp::Shl, - oq3_syntax::ast::ArithOp::Shr => ast::BinOp::Shr, - oq3_syntax::ast::ArithOp::BitXor => ast::BinOp::XorB, - oq3_syntax::ast::ArithOp::BitOr => ast::BinOp::OrB, - oq3_syntax::ast::ArithOp::BitAnd => ast::BinOp::AndB, - }), - } - } - - fn compile_block_expr(&mut self, block_expr: &oq3_syntax::ast::BlockExpr) -> ast::Block { - let stmts = self.compile_stmts(&block_expr.statements().collect::>()); - let stmts = stmts - .into_iter() - .map(Box::new) - .collect::>() - .into_boxed_slice(); - - let span = span_for_syntax_node(block_expr.syntax()); - ast::Block { - id: NodeId::default(), - span, - stmts, - } - } - - /// - /// Qiskit can't emit the box/delay, so we push an error and return None. - /// The QASM grammar specifies this is a statement, but the parser has it as an expression. - /// - /// We don't really have anything in Q# the box statement is for scoping the - /// timing of a particular part of the circuit. We could generates call around - /// a new block, but that would be a bit of a hack. - fn compile_box_expr(&mut self, box_expr: &oq3_syntax::ast::BoxExpr) -> Option { - self.push_unimplemented_error_message("box expressions", box_expr.syntax()); - None - } - - /// Qiskit can't handle call expressions yet, so we push an error and return None. - fn compile_call_expr( - &mut self, - call_expr: &oq3_syntax::ast::CallExpr, - ) -> Option { - self.push_unimplemented_error_message("call expressions", call_expr.syntax()); - None - } - - /// explicit casts have no defined behavior AFAICT from the spec. I'm - /// guessing that they are a best effort for the compiler implementor. - fn compile_cast_expr(&mut self, cast_expr: &CastExpression) -> Option { - let scalar_ty = cast_expr - .scalar_type() - .expect("cast expression must have a scalar type"); - let is_const = false; - let ty = self.get_semantic_type_from_scalar_type(&scalar_ty, is_const)?; - let expr = self.compile_expr_to_ty_with_casts(cast_expr.expr(), &ty, cast_expr.syntax())?; - Some(QasmTypedExpr { ty, expr }) - } - - /// Gate call expression. We delegate compilation to - /// `compile_gate_call_expr_impl` with empty modifiers so that we can reuse - /// gate call compilation logic. - fn compile_gate_call_expr( - &mut self, - gate_call_expr: &oq3_syntax::ast::GateCallExpr, - expr: &Expr, - ) -> Option { - let expr_span = span_for_syntax_node(expr.syntax()); - self.compile_gate_call_expr_impl(gate_call_expr, expr_span, &[]) - } - - /// Compile gate call expression with modifiers. - /// We have to resolve the gate name and modifiers as some of the stdgates - /// have implicit modifiers and different gate names that we have to map - /// into Q# names with the appropriate modifiers/fuctors. - /// - The `inv` modifier is the `Adjoint` fuctor. - /// - The `pow` modifier is the `__Pow__` function which we define as a - /// runtime function at the end of code generation if it is used. - /// - the `ctrl` modifier is the `Controlled` functor. - /// the `negctrl` modifier is a special case equivalent to - /// `ApplyControlledOnInt(0, _, _, _)`. - /// - /// Apply the modifiers are applied in reverse order to the gate call. - /// A simplified binding of the modifiers to the gate call with all - /// operations being on a single qubit gate would look like: - /// `a @ b @ c g(r) q0, q1, q2` `=>` `(a @ (b @ (c g(r) q0), q1), q2)` - /// - /// This get more complex when we have multiple qubit gates and controls. - #[allow(clippy::too_many_lines)] - fn compile_gate_call_expr_impl( - &mut self, - gate_call_expr: &oq3_syntax::ast::GateCallExpr, - expr_span: Span, - modifiers: &[crate::types::GateModifier], - ) -> Option { - let name = gate_call_expr - .identifier() - .expect("gate call must have a name"); - let name_span = span_for_syntax_node(name.syntax()); - let name_text = name.to_string(); - let call_span = span_for_syntax_node(gate_call_expr.syntax()); - // if we fail to map the name, we don't have a valid Q# gate - // but the user may have defined their own. We check the symbol - // table looking for such a definition. - let gate_name = get_qsharp_gate_name(&name_text).unwrap_or(&name_text); - let (gate_name, additional_modifiers) = get_implicit_modifiers(gate_name, name_span); - let Some(sym) = self.symbols.get_symbol_by_name(&gate_name) else { - self.push_missing_symbol_error(name_text, name.syntax()); - return None; - }; - let Type::Gate(cargs_len, qargs_len) = sym.ty else { - let kind = SemanticErrorKind::CannotCallNonGate(call_span); - self.push_semantic_error(kind); - return None; - }; - - let classical_args = self.compile_gate_call_classical_args(gate_call_expr, cargs_len)?; - let mut qubit_args = self.compile_gate_call_quantum_args(gate_call_expr)?; - - // at this point we all of the information we need, but we have to deal with modifiers - // We have the modifiers which we have been given, plus the implicit modifiers - // from the gate definition. We need to merge these two sets of modifiers - // See: ch, crx, cry, crz, sdg, and tdg - let modifiers = modifiers - .iter() - .chain(additional_modifiers.iter()) - .rev() - .collect::>(); - let num_ctrls = calculate_num_ctrls(&modifiers); - self.verify_qubit_args_match_gate_and_ctrls( - &qubit_args, - qargs_len, - num_ctrls, - gate_call_expr, - )?; - // take the nuber of qubit args that the gates expects from the source qubits - let gate_qubits = qubit_args.split_off(qubit_args.len() - qargs_len); - // and then merge the classical args with the qubit args - // this will give us the args for the call prior to wrapping in tuples - // for controls - let args: Vec<_> = classical_args.into_iter().chain(gate_qubits).collect(); - let mut args = build_gate_call_param_expr(args, qubit_args.len()); - let mut callee = build_path_ident_expr(&gate_name, name_span, expr_span); - - for modifier in modifiers { - match modifier { - GateModifier::Inv(mod_span) => { - callee = build_unary_op_expr( - ast::UnOp::Functor(ast::Functor::Adj), - callee, - *mod_span, - ); - } - GateModifier::Pow(exponent, mod_span) => { - // The exponent is only an option when initially parsing the gate - // call. The stmt would not have been created. If we don't have an - // an eponent at this point it is a bug - let exponent = exponent.expect("Exponent must be present"); - let exponent_expr = build_lit_int_expr(exponent, *mod_span); - self.runtime |= RuntimeFunctions::Pow; - args = build_tuple_expr(vec![exponent_expr, callee, args]); - callee = build_path_ident_expr("__Pow__", *mod_span, expr_span); - } - GateModifier::Ctrl(controls, mod_span) => { - // remove the last n qubits from the qubit list - let num_ctrls = controls.unwrap_or(1); - if qubit_args.len() < num_ctrls { - let kind = - SemanticErrorKind::InvalidNumberOfQubitArgs(qargs_len, 0, call_span); - self.push_semantic_error(kind); - return None; - } - let ctrl = qubit_args.split_off(qubit_args.len() - num_ctrls); - let ctrls = build_expr_array_expr(ctrl, *mod_span); - args = build_tuple_expr(vec![ctrls, args]); - callee = build_unary_op_expr( - ast::UnOp::Functor(ast::Functor::Ctl), - callee, - *mod_span, - ); - } - GateModifier::NegCtrl(controls, mod_span) => { - // remove the last n qubits from the qubit list - let num_ctrls = controls.unwrap_or(1); - if qubit_args.len() < num_ctrls { - let kind = - SemanticErrorKind::InvalidNumberOfQubitArgs(qargs_len, 0, call_span); - self.push_semantic_error(kind); - return None; - } - let ctrl = qubit_args.split_off(qubit_args.len() - num_ctrls); - let ctrls = build_expr_array_expr(ctrl, *mod_span); - let lit_0 = build_lit_int_expr(0, Span::default()); - args = build_tuple_expr(vec![lit_0, callee, ctrls, args]); - callee = build_path_ident_expr("ApplyControlledOnInt", *mod_span, expr_span); - } - } - } - - self.validate_all_quantum_args_have_been_consumed(&qubit_args, qargs_len, call_span)?; - - let expr = ast_builder::build_gate_call_with_params_and_callee(args, callee, expr_span); - Some(QasmTypedExpr { - ty: Type::Void, - expr, - }) - } - - /// Push if all qubit args have not been consumed. - /// Resurns None for an error, Some(()) for success. - /// This allows short-circuiting of the function. - fn validate_all_quantum_args_have_been_consumed( - &mut self, - qubit_args: &[ast::Expr], - qargs_len: usize, - call_span: Span, - ) -> Option<()> { - // This is a safety check. We should have peeled off all the controls - // but if we haven't, we need to push an error - if qubit_args.is_empty() { - return Some(()); - } - let kind = - SemanticErrorKind::InvalidNumberOfQubitArgs(qargs_len, qubit_args.len(), call_span); - self.push_semantic_error(kind); - None - } - - /// Raises an error if the number of qubit arguments does not match the number - /// of qubit arguments expected by the gate and the number of controls. - fn verify_qubit_args_match_gate_and_ctrls( - &mut self, - qubit_args: &[ast::Expr], - qargs_len: usize, - num_ctrls: u64, - gate_call_expr: &oq3_syntax::ast::GateCallExpr, - ) -> Option<()> { - let gate_call_span = span_for_syntax_node(gate_call_expr.syntax()); - let Some(num_ctrls) = usize::try_from(num_ctrls).ok() else { - let kind = SemanticErrorKind::TooManyControls(gate_call_span); - self.push_semantic_error(kind); - return None; - }; - - if qubit_args.len() != qargs_len + num_ctrls { - let span = if qubit_args.is_empty() { - gate_call_span - } else { - span_for_syntax_node( - gate_call_expr - .qubit_list() - .expect("Qubit list must exist") - .syntax(), - ) - }; - let kind = - SemanticErrorKind::InvalidNumberOfQubitArgs(qargs_len, qubit_args.len(), span); - self.push_semantic_error(kind); - return None; - } - Some(()) - } - - /// Compiles the gate call qubit arguments. This is a helper function - fn compile_gate_call_quantum_args( - &mut self, - gate_call_expr: &oq3_syntax::ast::GateCallExpr, - ) -> Option> { - let qubit_args: Vec<_> = gate_call_expr - .qubit_list() - .expect("Cannot call a gate without qubit arguments") - .gate_operands() - .map(|op| self.compile_gate_operand(&op).map(|x| x.expr)) - .collect(); - if qubit_args.iter().any(Option::is_none) { - // if any of the qubit arguments failed to compile, we can't proceed - // This can happen if the qubit is not defined or if the qubit was - // a hardware qubit - return None; - } - let qubit_args = qubit_args - .into_iter() - .map(|x| x.expect("All items should have value")) - .collect::>(); - Some(qubit_args) - } - - /// Compiles the gate call classical argument expressions. This is a helper function - fn compile_gate_call_classical_args( - &mut self, - gate_call_expr: &oq3_syntax::ast::GateCallExpr, - cargs_len: usize, - ) -> Option> { - let classical_args = match gate_call_expr.arg_list() { - Some(params) => { - let list = params - .expression_list() - .expect("Arg list must have an expression list"); - - // the classical args list is a list of expressions - // but the type of the args is fixed by the gate definition - // which should always move to float. - let angle_ty = Type::Float(None, IsConst::False); - let exprs = list - .exprs() - .map(|expr| { - self.compile_expr_to_ty_with_casts( - Some(expr), - &angle_ty, - gate_call_expr.syntax(), - ) - }) - .collect::>(); - - if !exprs.iter().all(Option::is_some) { - // There was an issue with one of the expressions - // and an error was pushed - return None; - } - exprs - .into_iter() - .map(|expr| expr.expect("All items should have value")) - .collect::>() - } - None => Vec::new(), - }; - - if classical_args.len() != cargs_len { - let gate_call_span = span_for_syntax_node(gate_call_expr.syntax()); - let span = if classical_args.is_empty() { - gate_call_span - } else { - span_for_syntax_node( - gate_call_expr - .arg_list() - .expect("Qubit list must exist") - .syntax(), - ) - }; - let kind = SemanticErrorKind::InvalidNumberOfClassicalArgs( - cargs_len, - classical_args.len(), - span, - ); - self.push_semantic_error(kind); - return None; - } - Some(classical_args) - } - - /// Compiles the expression list. Returns None if any of the expressions - /// fail to compile. If all expressions compile, returns a vector of - /// the compiled expressions. An error is pushed if any of the expressions - /// fail to compile. - fn compile_expression_list( - &mut self, - expr_list: &oq3_syntax::ast::ExpressionList, - ) -> Option> { - let exprs: Vec<_> = expr_list.exprs().collect(); - let exprs_len = exprs.len(); - let mapped_exprs: Vec<_> = exprs - .into_iter() - .filter_map(|expr| self.compile_expr(&expr)) - .collect(); - if exprs_len == mapped_exprs.len() { - return Some(mapped_exprs); - } - let kind = SemanticErrorKind::FailedToCompileExpressionList(span_for_syntax_node( - expr_list.syntax(), - )); - self.push_semantic_error(kind); - None - } - - /// Compiles the expression list attempting to coerce the expressions to a - /// specific type. - /// Returns None if any of the expressions fail to compile and an error is - /// pushed. If all expressions compile, returns a vector of the compiled - /// expressions. - fn compile_typed_expression_list( - &mut self, - expr_list: &oq3_syntax::ast::ExpressionList, - ty: &Type, - ) -> Option> { - let exprs: Vec<_> = expr_list.exprs().collect(); - let exprs_len = exprs.len(); - let mapped_exprs: Vec<_> = exprs - .into_iter() - .filter_map(|expr| { - self.compile_expr_to_ty_with_casts(Some(expr.clone()), ty, expr.syntax()) - .map(|expr| QasmTypedExpr { - expr, - ty: ty.clone(), - }) - }) - .collect(); - if exprs_len == mapped_exprs.len() { - return Some(mapped_exprs); - } - let kind = SemanticErrorKind::FailedToCompileExpressionList(span_for_syntax_node( - expr_list.syntax(), - )); - self.push_semantic_error(kind); - None - } - - /// Compiles qubit arguments for an instruction call. - fn compile_gate_operand(&mut self, op: &GateOperand) -> Option { - let op_span = span_for_syntax_node(op.syntax()); - match op { - GateOperand::HardwareQubit(hw) => { - // We don't support hardware qubits, so we need to push an error - // but we can still create an identifier for the hardware qubit - // and let the rest of the containing expression compile to - // catch any other errors - let message = "Hardware qubit operands"; - self.push_unsupported_error_message(message, hw.syntax()); - - let name = hw.to_string(); - let name_span = span_for_syntax_node(hw.syntax()); - let ident = build_path_ident_expr(name, name_span, op_span); - Some(QasmTypedExpr { - ty: Type::HardwareQubit, - expr: ident, - }) - } - GateOperand::Identifier(ident) => { - let name = ident.to_string(); - let name_span = span_for_syntax_node(ident.syntax()); - let Some(sym) = self.symbols.get_symbol_by_name(name.as_str()) else { - self.push_missing_symbol_error(name.as_str(), op.syntax()); - return None; - }; - let ty = sym.ty.clone(); - if !matches!(ty, Type::Qubit | Type::QubitArray(_)) { - let kind = SemanticErrorKind::InvalidGateOperand(op_span); - self.push_semantic_error(kind); - } - let ident = build_path_ident_expr(name, name_span, op_span); - Some(QasmTypedExpr { ty, expr: ident }) - } - GateOperand::IndexedIdentifier(indexed_ident) => { - let expr: QasmTypedExpr = self.compile_indexed_identifier_expr(indexed_ident)?; - // the type of the ident may be been Type::QubitArray, but the type of - // the returned expression should be Type::Qubit - if !matches!(expr.ty, Type::Qubit) { - let kind = SemanticErrorKind::InvalidIndexedGateOperand(op_span); - self.push_semantic_error(kind); - } - Some(expr) - } - } - } - fn compile_index_operator( - &mut self, - op: &oq3_syntax::ast::IndexOperator, - ) -> Option> { - match op.index_kind() { - Some(oq3_syntax::ast::IndexKind::SetExpression(expr)) => { - let expr = expr.expression_list()?; - self.compile_expression_list(&expr) - } - Some(oq3_syntax::ast::IndexKind::ExpressionList(expr)) => { - self.compile_expression_list(&expr) - } - None => { - let span = span_for_syntax_node(op.syntax()); - let kind = SemanticErrorKind::UnknownIndexedOperatorKind(span); - self.push_semantic_error(kind); - None - } - } - } - - #[allow(dead_code)] - fn compile_gphase_call_expr( - &mut self, - gphase_call_expr: &oq3_syntax::ast::GPhaseCallExpr, - expr: &Expr, - ) -> Option { - let expr_span = span_for_syntax_node(expr.syntax()); - self.compile_gphase_call_expr_impl(gphase_call_expr, expr_span, &[]) - } - - fn compile_gphase_call_expr_impl( - &mut self, - gphase_call_expr: &oq3_syntax::ast::GPhaseCallExpr, - _expr_span: Span, - _modifiers: &[crate::types::GateModifier], - ) -> Option { - self.push_unimplemented_error_message("gphase expressions", gphase_call_expr.syntax()); - None - } - - fn compile_hardware_qubit_expr( - &mut self, - _hardware_qubit: &oq3_syntax::ast::HardwareQubit, - expr: &Expr, - ) -> Option { - self.push_unsupported_error_message("hardware qubit expressions", expr.syntax()); - None - } - - fn compile_identifier_expr( - &mut self, - identifier: &oq3_syntax::ast::Identifier, - expr: &Expr, - ) -> Option { - let name = identifier.to_string(); - let Some(sym) = self.symbols.get_symbol_by_name(name.as_str()) else { - self.push_missing_symbol_error(&name, expr.syntax()); - return None; - }; - let span = span_for_syntax_node(identifier.syntax()); - let expr_span = span_for_syntax_node(expr.syntax()); - match sym.name.as_str() { - "euler" | "ℇ" => { - let expr = build_math_call_no_params("E", span); - let ty = Type::Float(None, IsConst::True); - Some(QasmTypedExpr { ty, expr }) - } - "pi" | "π" => { - let expr = build_math_call_no_params("PI", span); - let ty = Type::Float(None, IsConst::True); - Some(QasmTypedExpr { ty, expr }) - } - "tau" | "τ" => { - let expr = build_math_call_no_params("PI", span); - let ty = Type::Float(None, IsConst::True); - let expr = ast::Expr { - kind: Box::new(ast::ExprKind::BinOp( - ast::BinOp::Mul, - Box::new(build_lit_double_expr(2.0, span)), - Box::new(expr), - )), - span, - id: NodeId::default(), - }; - Some(QasmTypedExpr { ty, expr }) - } - _ => { - let expr = build_path_ident_expr(&sym.name, span, expr_span); - let ty = sym.ty.clone(); - Some(QasmTypedExpr { ty, expr }) - } - } - } - - fn compile_index_expr( - &mut self, - index_expr: &oq3_syntax::ast::IndexExpr, - ) -> Option { - let expr = index_expr.expr()?; - let expr_span = span_for_syntax_node(index_expr.syntax()); - let texpr = self.compile_expr(&expr)?; - let index = index_expr.index_operator()?; - let indices = self.compile_index_operator(&index)?; - let index_span = span_for_syntax_node(index.syntax()); - - if indices.len() != 1 { - // This is a temporary limitation. We can only handle - // single index expressions for now. - let kind = SemanticErrorKind::IndexMustBeSingleExpr(index_span); - self.push_semantic_error(kind); - return None; - } - let index = indices[0].clone(); - if index.ty.num_dims() > texpr.ty.num_dims() { - let kind = SemanticErrorKind::TypeRankError(index_span); - self.push_semantic_error(kind); - } - let index_expr = index.expr.clone(); - let Some(indexed_ty) = get_indexed_type(&texpr.ty) else { - let kind = - SemanticErrorKind::CannotIndexType(format!("{:?}", texpr.ty), texpr.expr.span); - self.push_semantic_error(kind); - return None; - }; - - let expr = ast_builder::build_index_expr(texpr.expr, index_expr, expr_span); - Some(QasmTypedExpr { - ty: indexed_ty, - expr, - }) - } - - /// Compiles a indexed expr `a[i]` where `a` is an identifier and `i` is an expression. - /// The type of the expression is determined by the indexed type of the identifier - /// resolved by `self.get_indexed_type`. - fn compile_indexed_identifier_expr( - &mut self, - indexed_ident: &oq3_syntax::ast::IndexedIdentifier, - ) -> Option { - let name = indexed_ident.identifier()?.to_string(); - let name_span = span_for_syntax_node(indexed_ident.syntax()); - let Some(sym) = self.symbols.get_symbol_by_name(name.as_str()) else { - self.push_missing_symbol_error(name.as_str(), indexed_ident.syntax()); - return None; - }; - let sym = sym.clone(); - let op_span = span_for_syntax_node(indexed_ident.syntax()); - - let index: Vec<_> = indexed_ident - .index_operators() - .filter_map(|op| self.compile_index_operator(&op)) - .flatten() - .collect(); - - assert!(index.len() == 1, "index must be a single expression"); - let ident = build_path_ident_expr(name, name_span, op_span); - let expr = ast::Expr { - id: NodeId::default(), - span: span_for_syntax_node(indexed_ident.syntax()), - kind: Box::new(ast::ExprKind::Index( - Box::new(ident), - Box::new(index[0].expr.clone()), - )), - }; - let Some(indexed_ty) = get_indexed_type(&sym.ty) else { - let kind = SemanticErrorKind::CannotIndexType(format!("{:?}", sym.ty), op_span); - self.push_semantic_error(kind); - return None; - }; - Some(QasmTypedExpr { - ty: indexed_ty, - expr, - }) - } - - fn compile_literal_expr( - &mut self, - lit: &oq3_syntax::ast::Literal, - expr: &Expr, - ) -> Option { - let span = span_for_syntax_node(lit.syntax()); - match lit.kind() { - LiteralKind::BitString(bitstring) => compile_bitstring(&bitstring, span), - LiteralKind::Bool(value) => { - let expr = build_lit_bool_expr(value, span); - let ty = Type::Bool(IsConst::True); - Some(QasmTypedExpr { ty, expr }) - } - LiteralKind::Byte(_) => { - self.push_unimplemented_error_message("byte literal expressions", expr.syntax()); - None - } - LiteralKind::Char(_) => { - self.push_unimplemented_error_message("char literal expressions", expr.syntax()); - None - } - LiteralKind::FloatNumber(value) => { - let expr = Self::compile_float_literal(&value, span); - let ty = Type::Float(None, IsConst::True); - Some(QasmTypedExpr { ty, expr }) - } - LiteralKind::IntNumber(value) => { - let expr = Self::compile_int_literal(&value, span); - let ty = Type::UInt(None, IsConst::True); - Some(QasmTypedExpr { ty, expr }) - } - LiteralKind::String(string) => self.compile_string_literal(&string, expr), - } - } - - /// Compiles a complex literal expression from a literal int. - fn compile_int_to_double_literal_to_complex( - &mut self, - value: &oq3_syntax::ast::IntNumber, - span: Span, - ) -> Option { - let value = value.value().expect("FloatNumber must have a value"); - if let Some(value) = safe_u128_to_f64(value) { - Some(build_complex_from_expr(build_lit_double_expr(value, span))) - } else { - let kind = SemanticErrorKind::InvalidCastValueRange( - "Integer".to_string(), - "Double".to_string(), - span, - ); - self.push_semantic_error(kind); - None - } - } - - /// Compiles a double expression from a literal int. - fn compile_int_to_double_literal( - &mut self, - value: &oq3_syntax::ast::IntNumber, - negate: bool, - span: Span, - ) -> Option { - let value = value.value().expect("FloatNumber must have a value"); - if let Some(value) = safe_u128_to_f64(value) { - let value = if negate { -value } else { value }; - Some(build_lit_double_expr(value, span)) - } else { - let kind = SemanticErrorKind::InvalidCastValueRange( - "Integer".to_string(), - "Double".to_string(), - span, - ); - self.push_semantic_error(kind); - None - } - } - - fn compile_float_literal(value: &oq3_syntax::ast::FloatNumber, span: Span) -> ast::Expr { - build_lit_double_expr(value.value().expect("FloatNumber must have a value"), span) - } - - fn compile_int_literal(value: &oq3_syntax::ast::IntNumber, span: Span) -> ast::Expr { - if let Some(value) = value.value() { - match value.try_into() { - Ok(value) => build_lit_int_expr(value, span), - Err(_) => build_lit_bigint_expr(value.into(), span), - } - } else { - panic!("IntNumber must have a value"); - } - } - - fn compile_string_literal( - &mut self, - _string: &oq3_syntax::ast::String, - expr: &Expr, - ) -> Option { - self.push_unimplemented_error_message("string literal expressions", expr.syntax()); - None - } - - fn compile_timing_literal_expr( - &mut self, - lit: &oq3_syntax::ast::TimingLiteral, - expr: &Expr, - ) -> Option { - self.compile_timing_literal_as_complex(lit, expr, false) - } - - // OpenQASM parser bundles complex numbers with timing literals - // so we have to disambiguate them during timing literal compilation - fn compile_timing_literal_as_complex( - &mut self, - lit: &TimingLiteral, - expr: &Expr, - negate: bool, - ) -> Option { - if let Some(TimeUnit::Imaginary) = lit.time_unit() { - let literal = lit.literal()?; - match literal.kind() { - LiteralKind::FloatNumber(value) => { - let value = value.value().expect("FloatNumber must have a value"); - let value = if negate { -value } else { value }; - let expr = build_lit_complex_expr( - crate::types::Complex::new(0.0, value), - span_for_syntax_node(lit.syntax()), - ); - let ty = Type::Complex(None, IsConst::True); - Some(QasmTypedExpr { ty, expr }) - } - LiteralKind::IntNumber(value) => { - let value = value.value().expect("IntNumber must have a value"); - - if let Some(value) = safe_u128_to_f64(value) { - let value = if negate { -value } else { value }; - let expr = build_lit_complex_expr( - crate::types::Complex::new(0.0, value), - span_for_syntax_node(lit.syntax()), - ); - let ty = Type::Complex(None, IsConst::True); - Some(QasmTypedExpr { ty, expr }) - } else { - let kind = SemanticErrorKind::InvalidCastValueRange( - "Complex imaginary".to_string(), - "Float".to_string(), - span_for_syntax_node(literal.syntax()), - ); - self.push_semantic_error(kind); - None - } - } - _ => { - // parser bug - unreachable!( - "Expected float or int literal, there is a bug in the OpenQASM parser." - ) - } - } - } else { - self.push_unsupported_error_message("Timing literal expressions", expr.syntax()); - None - } - } - - fn compile_measure_expr( - &mut self, - measure_expr: &oq3_syntax::ast::MeasureExpression, - expr: &Expr, - ) -> Option { - let Some(measure_token) = measure_expr.measure_token() else { - let span = span_for_syntax_node(expr.syntax()); - let kind = SemanticErrorKind::MeasureExpressionsMustHaveName(span); - self.push_semantic_error(kind); - return None; - }; - let name_span = span_for_syntax_token(&measure_token); - let stmt_span = span_for_syntax_node(measure_expr.syntax()); - - let Some(operand) = measure_expr.gate_operand() else { - let span = span_for_syntax_node(expr.syntax()); - let kind = SemanticErrorKind::MeasureExpressionsMustHaveGateOperand(span); - self.push_semantic_error(kind); - return None; - }; - - let args = self.compile_gate_operand(&operand)?; - let operand_span = span_for_syntax_node(operand.syntax()); - let expr = build_measure_call(args.expr, name_span, operand_span, stmt_span); - - Some(QasmTypedExpr { - ty: Type::Bit(IsConst::False), - expr, - }) - } - - fn compile_modified_gate_call_expr( - &mut self, - modified_gate_call_expr: &oq3_syntax::ast::ModifiedGateCallExpr, - ) -> Option { - let expr_span = span_for_syntax_node(modified_gate_call_expr.syntax()); - let modifiers = modified_gate_call_expr - .modifiers() - .map(|modifier| { - let span = span_for_syntax_node(modifier.syntax()); - match modifier { - Modifier::InvModifier(_) => GateModifier::Inv(span), - Modifier::PowModifier(pow_mod) => { - let Some(expr) = pow_mod.paren_expr() else { - let kind = SemanticErrorKind::PowModifierMustHaveExponent(span); - self.push_semantic_error(kind); - return GateModifier::Pow(None, span); - }; - extract_pow_exponent(&expr, span) - } - Modifier::CtrlModifier(ctrl_mod) => { - let ctrls = self.extract_controls_from_modifier(ctrl_mod.paren_expr()); - GateModifier::Ctrl(ctrls, span) - } - Modifier::NegCtrlModifier(neg_ctrl_mod) => { - let ctrls = self.extract_controls_from_modifier(neg_ctrl_mod.paren_expr()); - GateModifier::NegCtrl(ctrls, span) - } - } - }) - .collect::>(); - - if let Some(gate_call_expr) = modified_gate_call_expr.gate_call_expr() { - self.compile_gate_call_expr_impl(&gate_call_expr, expr_span, &modifiers) - } else { - let Some(gphase_call_expr) = modified_gate_call_expr.g_phase_call_expr() else { - // error - return None; - }; - self.compile_gphase_call_expr_impl(&gphase_call_expr, expr_span, &modifiers) - } - } - - /// Extracts the literal int from `ctrl(value)` and `negctrl(value)` modifiers. - /// Returns None if the modifier is invalid and pushes an error. - /// Returns Some(1) if the modifier is empty. - /// Returns Some(value) if the modifier is valid. - fn extract_controls_from_modifier(&mut self, paren_expr: Option) -> Option { - if let Some(paren_expr) = paren_expr { - if let Some((ctrl, sign)) = compile_paren_lit_int_expr(&paren_expr) { - if sign { - let kind = SemanticErrorKind::NegativeControlCount(span_for_syntax_node( - paren_expr.syntax(), - )); - self.push_semantic_error(kind); - } - Some(ctrl) - } else { - let kind = SemanticErrorKind::InvalidControlCount(span_for_syntax_node( - paren_expr.syntax(), - )); - self.push_semantic_error(kind); - None - } - } else { - Some(1) - } - } - - fn compile_paren_expr( - &mut self, - paren_expr: &oq3_syntax::ast::ParenExpr, - ) -> Option { - let span = span_for_syntax_node(paren_expr.syntax()); - let expr = paren_expr.expr()?; - let texpr = self.compile_expr(&expr)?; - let pexpr = ast_builder::wrap_expr_in_parens(texpr.expr, span); - Some(QasmTypedExpr { - ty: texpr.ty.clone(), - expr: pexpr, - }) - } - - fn compile_negated_literal_as_ty( - &mut self, - literal: &Literal, - ty: Option<&Type>, - ) -> Option { - let span = span_for_syntax_node(literal.syntax()); - match literal.kind() { - LiteralKind::IntNumber(value) => match ty { - Some(Type::Float(..)) => { - let expr = self.compile_int_to_double_literal(&value, true, span)?; - Some(QasmTypedExpr { - ty: ty.expect("Expected type").clone(), - expr, - }) - } - _ => Some(compile_intnumber_as_negated_int(&value, span)), - }, - LiteralKind::FloatNumber(value) => match ty { - Some(Type::Int(..) | Type::UInt(..)) => { - let value = value.value().expect("FloatNumber must have a value"); - #[allow(clippy::cast_possible_truncation)] - let converted_value = value.trunc() as i64; - #[allow(clippy::cast_precision_loss)] - if (converted_value as f64 - value).abs() > f64::EPSILON { - let span = span_for_syntax_node(literal.syntax()); - let kind = SemanticErrorKind::CastWouldCauseTruncation( - "Float".to_string(), - format!("{:?}", ty.expect("Expected type")), - span, - ); - self.push_semantic_error(kind); - None - } else { - let expr = build_lit_int_expr(-converted_value, span); - let ty = ty.expect("Expected type").clone(); - Some(QasmTypedExpr { ty, expr }) - } - } - _ => Some(compile_floatnumber_as_negated_double(&value, span)), - }, - _ => { - self.push_unimplemented_error_message( - "negated literal expressions", - literal.syntax(), - ); - None - } - } - } - - fn compile_prefix_expr( - &mut self, - prefix_expr: &oq3_syntax::ast::PrefixExpr, - ) -> Option { - let prefix_span = span_for_syntax_node(prefix_expr.syntax()); - match prefix_expr.op_kind() { - Some(UnaryOp::Neg) => match prefix_expr.expr() { - Some(Expr::Literal(lit)) => self.compile_negated_literal_as_ty(&lit, None), - Some(Expr::TimingLiteral(lit)) => { - let expr = prefix_expr - .expr() - .expect("TimingLiteral must have an expression"); - self.compile_timing_literal_as_complex(&lit, &expr, true) - } - Some(expr) => { - let texpr = self.compile_expr(&expr)?; - let expr = build_unary_op_expr(ast::UnOp::Neg, texpr.expr, prefix_span); - let ty = texpr.ty; - Some(QasmTypedExpr { ty, expr }) - } - None => { - self.push_unimplemented_error_message( - "negated empty expressions", - prefix_expr.syntax(), - ); - None - } - }, - Some(UnaryOp::LogicNot) => { - // bug in QASM parser, logical not and bitwise not are backwards - if let Some(prefix) = prefix_expr.expr() { - let texpr = self.compile_expr(&prefix)?; - let expr = build_unary_op_expr(ast::UnOp::NotB, texpr.expr, prefix_span); - let ty = texpr.ty; - Some(QasmTypedExpr { ty, expr }) - } else { - self.push_unimplemented_error_message( - "bitwise not empty expressions", - prefix_expr.syntax(), - ); - None - } - } - Some(UnaryOp::Not) => { - // bug in QASM parser, logical not and bitwise not are backwards - // THIS CODE IS FOR LOGICAL NOT - let ty = Type::Bool(IsConst::False); - let expr = self.compile_expr_to_ty_with_casts( - prefix_expr.expr(), - &ty, - prefix_expr.syntax(), - )?; - let expr = build_unary_op_expr(ast::UnOp::NotL, expr, prefix_span); - Some(QasmTypedExpr { ty, expr }) - } - None => None, - } - } - - #[allow(clippy::similar_names)] - fn compile_range_expr( - &mut self, - range_expr: &oq3_syntax::ast::RangeExpr, - node: &SyntaxNode, - ) -> Option { - let (start, step, stop) = range_expr.start_step_stop(); - let Some(start) = start else { - let span = span_for_syntax_node(range_expr.syntax()); - let kind = SemanticErrorKind::RangeExpressionsMustHaveStart(span); - self.push_semantic_error(kind); - return None; - }; - let Some(stop) = stop else { - let span = span_for_syntax_node(range_expr.syntax()); - let kind = SemanticErrorKind::RangeExpressionsMustHaveStop(span); - self.push_semantic_error(kind); - return None; - }; - let start_texpr = self.compile_expr(&start)?; - let stop_texpr = self.compile_expr(&stop)?; - let step_texpr = if let Some(step) = step { - Some(self.compile_expr(&step)?.expr) - } else { - None - }; - Some(QasmTypedExpr { - ty: Type::Range, - expr: build_range_expr( - start_texpr.expr, - stop_texpr.expr, - step_texpr, - span_for_syntax_node(node), - ), - }) - } - - fn compile_return_expr( - &mut self, - return_expr: &oq3_syntax::ast::ReturnExpr, - ) -> Option { - let stmt_span = span_for_syntax_node(return_expr.syntax()); - if !self.symbols.is_scope_rooted_in_subroutine() { - let kind = SemanticErrorKind::ReturnNotInSubroutine(stmt_span); - self.push_semantic_error(kind); - } - // the containing function will have an explicit return type - // or default of Void. We don't need to check the return type - // as that will be handled by Q# type checker. If there is no - // expression, we return Unit which Void maps to in Q#. - if let Some(expr) = return_expr.expr() { - let texpr = self.compile_expr(&expr)?; - let expr = ast_builder::build_return_expr(texpr.expr, stmt_span); - Some(QasmTypedExpr { ty: texpr.ty, expr }) - } else { - let expr = ast_builder::build_return_unit(stmt_span); - Some(QasmTypedExpr { - ty: Type::Void, - expr, - }) - } - } - - fn compile_for_stmt(&mut self, for_stmt: &oq3_syntax::ast::ForStmt) -> Option { - let loop_var = for_stmt - .loop_var() - .expect("For statement must have a loop variable"); - let loop_var_span = span_for_syntax_node(loop_var.syntax()); - let loop_var_scalar_ty = for_stmt - .scalar_type() - .expect("For statement must have a scalar type"); - let for_iterable = for_stmt - .for_iterable() - .expect("For statement must have an iterable"); - let stmt_span = span_for_syntax_node(for_stmt.syntax()); - let iterable = self.compile_for_iterable(&for_iterable)?; - let loop_var_sem_ty = - self.get_semantic_type_from_scalar_type(&loop_var_scalar_ty, false)?; - let qsharp_ty = - self.convert_semantic_type_to_qsharp_type(&loop_var_sem_ty, loop_var.syntax())?; - - let loop_var_symbol = Symbol { - name: loop_var.to_string(), - span: loop_var_span, - ty: loop_var_sem_ty.clone(), - qsharp_ty: qsharp_ty.clone(), - io_kind: IOKind::Default, - }; - - self.symbols.push_scope(crate::symbols::ScopeKind::Block); - if self.symbols.insert_symbol(loop_var_symbol.clone()).is_err() { - self.push_redefined_symbol_error(loop_var.to_string(), loop_var_span); - return None; - } - - let body = if let Some(stmt) = for_stmt.stmt() { - let stmt = self.compile_stmt(&stmt); - self.symbols.pop_scope(); - build_stmt_wrapped_block_expr(stmt?) - } else if let Some(block) = for_stmt.body() { - let block = self.compile_block_expr(&block); - self.symbols.pop_scope(); - block - } else { - let span = span_for_syntax_node(for_stmt.syntax()); - let kind = SemanticErrorKind::ForStatementsMustHaveABodyOrStatement(span); - self.push_semantic_error(kind); - None? - }; - - Some(ast_builder::build_for_stmt( - &loop_var_symbol, - iterable, - body, - stmt_span, - )) - } - - fn compile_for_iterable( - &mut self, - for_iterable: &oq3_syntax::ast::ForIterable, - ) -> Option { - if let Some(expr) = for_iterable.set_expression() { - let expr_list = expr.expression_list()?; - - let expression_list = self - .compile_expression_list(&expr_list)? - .into_iter() - .map(|e| e.expr) - .collect(); - - let expr = build_expr_array_expr(expression_list, span_for_syntax_node(expr.syntax())); - Some(QasmTypedExpr { - ty: Type::Set, - expr, - }) - } else if let Some(expr) = for_iterable.range_expr() { - self.compile_range_expr(&expr, for_iterable.syntax()) - } else if let Some(expr) = for_iterable.for_iterable_expr() { - // For iterating over something like bit[n] - self.compile_expr(&expr) - } else { - let span = span_for_syntax_node(for_iterable.syntax()); - let kind = SemanticErrorKind::ForIterableInvalidExpression(span); - self.push_semantic_error(kind); - None - } - } - - #[allow(clippy::too_many_lines)] - fn compile_gate_decl(&mut self, gate: &oq3_syntax::ast::Gate) -> Option { - let name = gate.name()?; - - // Once we support angle types, we will use this as the type for - // the angle parameters' coersion: - //let angle_ty = Type::Angle(None, IsConst::True); - let angle_ty = Type::Float(None, IsConst::True); - let qs_angle_ty = self.convert_semantic_type_to_qsharp_type(&angle_ty, gate.syntax())?; - let ast_angle_ty = map_qsharp_type_to_ast_ty(&qs_angle_ty); - let qubit_ty = Type::Qubit; - let qs_qubit_ty = self.convert_semantic_type_to_qsharp_type(&qubit_ty, gate.syntax())?; - let ast_qubit_ty = map_qsharp_type_to_ast_ty(&qs_qubit_ty); - - let gate_span = span_for_syntax_node(gate.syntax()); - if !self.symbols.is_current_scope_global() { - let kind = SemanticErrorKind::QuantumDeclarationInNonGlobalScope(gate_span); - self.push_semantic_error(kind); - return None; - } - // get the name of the args and their spans - let cargs = gate - .angle_params() - .iter() - .flat_map(ParamList::params) - .map(|e| { - ( - e.text().to_string(), - ast_angle_ty.clone(), - build_arg_pat( - e.text().to_string(), - span_for_named_item(&e), - ast_angle_ty.clone(), - ), - ) - }) - .collect::>(); - - let qargs = gate - .qubit_params() - .iter() - .flat_map(ParamList::params) - .map(|e| { - ( - e.text().to_string(), - ast_qubit_ty.clone(), - build_arg_pat( - e.text().to_string(), - span_for_named_item(&e), - ast_qubit_ty.clone(), - ), - ) - }) - .collect::>(); - - self.symbols.push_scope(crate::symbols::ScopeKind::Gate); - // bind the cargs, qargs and body - - for (name, _, pat) in &cargs { - let symbol = Symbol { - name: name.clone(), - span: pat.span, - ty: angle_ty.clone(), - qsharp_ty: qs_angle_ty.clone(), - io_kind: IOKind::Default, - }; - if self.symbols.insert_symbol(symbol).is_err() { - self.push_redefined_symbol_error(name, pat.span); - } - } - for (name, _, pat) in &qargs { - let symbol = Symbol { - name: name.clone(), - span: pat.span, - ty: qubit_ty.clone(), - qsharp_ty: qs_qubit_ty.clone(), - io_kind: IOKind::Default, - }; - if self.symbols.insert_symbol(symbol).is_err() { - self.push_redefined_symbol_error(name, pat.span); - } - } - let body = gate.body().map(|body| self.compile_block_expr(&body)); - let body_span = gate - .body() - .map(|body| span_for_syntax_node(body.syntax())) - .unwrap_or_default(); - self.symbols.pop_scope(); - // create a gate symbol with type information with num cargs and qargs - - let gate_ty = Type::Gate(cargs.len(), qargs.len()); - let qs_gate_ty = self.convert_semantic_type_to_qsharp_type(&gate_ty, gate.syntax())?; - let name_span = span_for_syntax_node(name.syntax()); - let symbol = Symbol { - name: name.to_string(), - span: name_span, - ty: gate_ty, - qsharp_ty: qs_gate_ty, - io_kind: IOKind::Default, - }; - if self.symbols.insert_symbol(symbol).is_err() { - self.push_redefined_symbol_error(name.to_string(), span_for_syntax_node(name.syntax())); - return None; - } - - if self.next_gate_as_item { - Some(ast_builder::build_gate_decl( - name.to_string(), - cargs, - qargs, - body, - name_span, - body_span, - gate_span, - )) - } else { - Some(build_gate_decl_lambda( - name.to_string(), - cargs, - qargs, - body, - name_span, - body_span, - gate_span, - )) - } - } - - fn compile_if_stmt(&mut self, if_stmt: &oq3_syntax::ast::IfStmt) -> Option { - let stmt_span = span_for_syntax_node(if_stmt.syntax()); - - let Some(condition) = if_stmt.condition() else { - let kind = - SemanticErrorKind::IfStmtMissingExpression("condition".to_string(), stmt_span); - self.push_semantic_error(kind); - return None; - }; - let node = condition.syntax(); - let cond_ty = Type::Bool(IsConst::False); - let cond = self - .compile_expr_to_ty_with_casts(Some(condition.clone()), &cond_ty, node) - .map(|expr| QasmTypedExpr { ty: cond_ty, expr }); - - let Some(block) = if_stmt.then_branch() else { - let kind = - SemanticErrorKind::IfStmtMissingExpression("then block".to_string(), stmt_span); - self.push_semantic_error(kind); - return None; - }; - - self.symbols.push_scope(crate::symbols::ScopeKind::Block); - let then_block = self.compile_block_expr(&block); - self.symbols.pop_scope(); - let else_block = if_stmt.else_branch().map(|block_expr| { - self.symbols.push_scope(crate::symbols::ScopeKind::Block); - let else_expr = self.compile_block_expr(&block_expr); - self.symbols.pop_scope(); - else_expr - }); - - // The cond may have failed to compile, in which case we return None - // we let it go this far so that we could accumulate any errors in - // the block. - let cond = cond?; - let if_expr = if let Some(else_block) = else_block { - build_if_expr_then_block_else_block(cond.expr, then_block, else_block, stmt_span) - } else { - build_if_expr_then_block(cond.expr, then_block, stmt_span) - }; - - Some(build_stmt_semi_from_expr(if_expr)) - } - - fn compile_io_decl_stmt( - &mut self, - decl: &oq3_syntax::ast::IODeclarationStatement, - ) -> Option { - if decl.array_type().is_some() { - self.push_unimplemented_error_message("array io declarations", decl.syntax()); - return None; - } - let name = decl.name().expect("io declaration must have a name"); - let scalar_ty = decl - .scalar_type() - .expect("io declaration must have a scalar type"); - let io_kind = match decl.input_token() { - Some(_) => IOKind::Input, - None => IOKind::Output, - }; - // if we can't convert the scalar type, we can't proceed, an error has been pushed - let ty = self.get_semantic_type_from_scalar_type(&scalar_ty, false)?; - let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty, name.syntax())?; - let symbol = Symbol { - name: name.to_string(), - span: span_for_syntax_node(name.syntax()), - ty: ty.clone(), - qsharp_ty: qsharp_ty.clone(), - io_kind: io_kind.clone(), - }; - - if self.symbols.insert_symbol(symbol).is_err() { - self.push_redefined_symbol_error(name.to_string(), span_for_syntax_node(name.syntax())); - return None; - } - - // if we have output, we need to assign a default value to declare the variable - // if we have input, we can keep return none as we would promote the variable - // to a parameter in the function signature once we generate the function - if io_kind == IOKind::Output { - let rhs = self.get_default_value(&ty, name.syntax())?; - let stmt = build_classical_decl( - name.to_string(), - false, - span_for_syntax_node(scalar_ty.syntax()), - span_for_syntax_node(decl.syntax()), - span_for_syntax_node(name.syntax()), - &qsharp_ty, - rhs, - ); - Some(stmt) - } else { - None - } - } - - /// Let statements shouldn't make it into parsing - /// Looking at the parser, this statement seems - /// anachronistic and should be removed from the parser - fn compile_let_stmt(&mut self, let_stmt: &oq3_syntax::ast::LetStmt) -> Option { - self.push_unsupported_error_message("let statements", let_stmt.syntax()); - None - } - - /// Measure statements shouldn't make it into parsing - /// Looking at the parser, this statement seems - /// anachronistic and should be removed from the parser - fn compile_measure_stmt(&mut self, measure: &oq3_syntax::ast::Measure) -> Option { - self.push_unsupported_error_message("measure statements", measure.syntax()); - None - } - - fn compile_quantum_decl( - &mut self, - decl: &oq3_syntax::ast::QuantumDeclarationStatement, - ) -> Option { - let decl_span = span_for_syntax_node(decl.syntax()); - if !self.symbols.is_current_scope_global() { - let kind = SemanticErrorKind::QuantumDeclarationInNonGlobalScope(decl_span); - self.push_semantic_error(kind); - return None; - } - let qubit_ty = decl - .qubit_type() - .expect("Quantum declaration must have a qubit type"); - let name = decl.name().expect("Quantum declaration must have a name"); - - let designator = match qubit_ty.designator() { - Some(designator) => { - let width_span = span_for_syntax_node(designator.syntax()); - let width = extract_dims_from_designator(Some(designator)) - .expect("Designator must be a literal integer"); - - Some((width, width_span)) - } - None => None, - }; - let ty = if let Some((width, _)) = designator { - Type::QubitArray(ArrayDims::D1(width as usize)) - } else { - Type::Qubit - }; - let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty, name.syntax())?; - let symbol = Symbol { - name: name.to_string(), - span: span_for_syntax_node(name.syntax()), - ty, - qsharp_ty, - io_kind: IOKind::Default, - }; - - if self.symbols.insert_symbol(symbol).is_err() { - self.push_redefined_symbol_error(name.to_string(), span_for_syntax_node(name.syntax())); - return None; - } - let name = name.to_string(); - let name_span = span_for_named_item(decl); - let stmt = match self.config.qubit_semantics { - QubitSemantics::QSharp => { - if let Some((width, designator_span)) = designator { - managed_qubit_alloc_array(name, width, decl_span, name_span, designator_span) - } else { - build_managed_qubit_alloc(name, decl_span, name_span) - } - } - QubitSemantics::Qiskit => { - if let Some((width, span)) = designator { - build_unmanaged_qubit_alloc_array(name, width, decl_span, name_span, span) - } else { - build_unmanaged_qubit_alloc(name, decl_span, name_span) - } - } - }; - Some(stmt) - } - - fn compile_reset_call(&mut self, expr: &oq3_syntax::ast::Reset) -> Option { - let Some(token) = expr.reset_token() else { - let span = span_for_syntax_node(expr.syntax()); - let kind = SemanticErrorKind::ResetExpressionMustHaveName(span); - self.push_semantic_error(kind); - return None; - }; - let name_span = span_for_syntax_token(&token); - - let Some(operand) = expr.gate_operand() else { - let span = span_for_syntax_node(expr.syntax()); - let kind = SemanticErrorKind::ResetExpressionMustHaveGateOperand(span); - self.push_semantic_error(kind); - return None; - }; - let args = self.compile_gate_operand(&operand)?; - let operand_span = span_for_syntax_node(operand.syntax()); - let expr = build_reset_call(args.expr, name_span, operand_span); - - Some(build_stmt_semi_from_expr(expr)) - } - - fn compile_switch_stmt( - &mut self, - switch_case: &oq3_syntax::ast::SwitchCaseStmt, - ) -> Option { - // The condition for the switch statement must be an integer type - // so instead of using `compile_expr` we use `resolve_rhs_expr_with_casts` - // forcing the type to be an integer type with implicit casts if necessary - let cond_ty = Type::Int(None, IsConst::False); - // We try to compile all expressions first to accumulate errors - let control = switch_case.control().and_then(|control| { - self.compile_expr_to_ty_with_casts(Some(control), &cond_ty, switch_case.syntax()) - }); - let cases: Vec<_> = switch_case - .case_exprs() - .map(|case| { - let lhs = case - .expression_list() - .and_then(|expr| self.compile_typed_expression_list(&expr, &cond_ty)); - self.symbols.push_scope(crate::symbols::ScopeKind::Block); - let rhs = case - .block_expr() - .map(|block| self.compile_block_expr(&block)); - self.symbols.pop_scope(); - (lhs, rhs) - }) - .collect(); - self.symbols.push_scope(crate::symbols::ScopeKind::Block); - let default_block = switch_case - .default_block() - .map(|block| self.compile_block_expr(&block)); - self.symbols.pop_scope(); - - // at this point we tried to compile everything, bail if we have any errors - if control.is_none() - || cases - .iter() - .any(|(lhs, rhs)| lhs.is_none() || rhs.is_none()) - || cases.is_empty() - { - // See tests, but it is a parse error to have a switch statement with - // no cases, even if the default block is present. Getting here means - // the parser is broken or they changed the grammar. - panic!("Switch statement must have a control expression and at least one case"); - } - - // update bindings based on what we checked above - let control = control?; - let cases: Vec<_> = cases - .into_iter() - .map(|(lhs, rhs)| { - let lhs = lhs.expect("Case must have a lhs"); - let rhs = rhs.expect("Case must have a rhs"); - (lhs, rhs) - }) - .collect(); - - // Semantics of switch case is that the outer block doesn't introduce - // a new scope but each case rhs does. - - // Can we add a new scope anyway to hold a temporary variable? - // if we do that, we can refer to a new variable instead of the control - // expr this would allow us to avoid the need to resolve the control - // expr multiple times in the case where we have to coerce the control - // expr to the correct type. Introducing a new variable without a new - // scope would effect output semantics. - - // For each case, convert the lhs into a sequence of equality checks - // and then fold them into a single expression of logical ors for - // the if expr - let cases: Vec<_> = cases - .into_iter() - .map(|(lhs, rhs)| { - let case = lhs - .iter() - .map(|texpr| { - ast_builder::build_binary_expr( - false, - ast::BinOp::Eq, - control.clone(), - texpr.expr.clone(), - texpr.expr.span, - ) - }) - .fold(None, |acc, expr| match acc { - None => Some(expr), - Some(acc) => { - let qsop = ast::BinOp::OrL; - let span = Span { - lo: acc.span.lo, - hi: expr.span.hi, - }; - Some(build_binary_expr(false, qsop, acc, expr, span)) - } - }); - // The type checker doesn't know that we have at least one case - // so we have to unwrap here since the accumulation is guaranteed - // to have Some(value) - let case = case.expect("Case must have at least one expression"); - (case, rhs) - }) - .collect(); - - // cond is resolved, cases are resolved, default is resolved - // we can now build the if expression backwards. The default block - // is the last else block, the last case is the then block, and the rest - // are built as if-else blocks with the last case as the else block - let default_expr = default_block.map(build_wrapped_block_expr); - let if_expr = cases - .into_iter() - .rev() - .fold(default_expr, |else_expr, (cond, block)| { - let span = Span { - lo: cond.span.lo, - hi: block.span.hi, - }; - Some(build_if_expr_then_block_else_expr( - cond, block, else_expr, span, - )) - }); - if_expr.map(build_stmt_semi_from_expr) - } - - // This is a no-op in Q# but we will save it for future use - fn compile_version_stmt( - &mut self, - version: &oq3_syntax::ast::VersionString, - ) -> Option { - if let Some(version) = version.version() { - let version_str = format!("{version}"); - if !version_str.starts_with("3.") { - self.push_unsupported_error_message( - "OpenQASM versions other than 3", - version.syntax(), - ); - } - self.version = Some(version_str); - } - None - } - - /// Note: From the ``OpenQASM`` 3.0 specification: - /// This clearly allows users to write code that does not terminate. - /// We do not discuss implementation details here, but one possibility - /// is to compile into target code that imposes iteration limits. - fn compile_while_stmt(&mut self, while_stmt: &oq3_syntax::ast::WhileStmt) -> Option { - let stmt_span = span_for_syntax_node(while_stmt.syntax()); - let Some(condition) = while_stmt.condition() else { - let kind = - SemanticErrorKind::WhileStmtMissingExpression("condition".to_string(), stmt_span); - self.push_semantic_error(kind); - return None; - }; - - let node = condition.syntax(); - let cond_ty = Type::Bool(IsConst::False); - let cond = self - .compile_expr_to_ty_with_casts(Some(condition.clone()), &cond_ty, node) - .map(|expr| QasmTypedExpr { ty: cond_ty, expr }); - - // if cond is none, an error was pushed - // or the expression couldn't be resolved - // We keep going to catch more errors but only if the condition - // expression can be compiled - let cond = match cond { - Some(cond) => cond, - None => self.compile_expr(&condition)?, - }; - - let Some(block) = while_stmt.body() else { - let kind = SemanticErrorKind::WhileStmtMissingExpression("body".to_string(), stmt_span); - self.push_semantic_error(kind); - return None; - }; - - self.symbols.push_scope(crate::symbols::ScopeKind::Block); - let block_body = self.compile_block_expr(&block); - self.symbols.pop_scope(); - Some(ast_builder::build_while_stmt( - cond.expr, block_body, stmt_span, - )) - } - - fn convert_semantic_type_to_qsharp_type( - &mut self, - ty: &Type, - node: &SyntaxNode, - ) -> Option { - let is_const = ty.is_const(); - match ty { - Type::Bit(_) => Some(crate::types::Type::Result(is_const)), - Type::Qubit => Some(crate::types::Type::Qubit), - Type::HardwareQubit => { - let message = "HardwareQubit to Q# type"; - self.push_unsupported_error_message(message, node); - None - } - Type::Int(width, _) | Type::UInt(width, _) => { - if let Some(width) = width { - if *width > 64 { - Some(crate::types::Type::BigInt(is_const)) - } else { - Some(crate::types::Type::Int(is_const)) - } - } else { - Some(crate::types::Type::Int(is_const)) - } - } - Type::Float(_, _) | Type::Angle(_, _) => Some(crate::types::Type::Double(is_const)), - Type::Complex(_, _) => Some(crate::types::Type::Complex(is_const)), - Type::Bool(_) => Some(crate::types::Type::Bool(is_const)), - Type::Duration(_) => { - self.push_unsupported_error_message("Duration type values", node); - None - } - Type::Stretch(_) => { - self.push_unsupported_error_message("Stretch type values", node); - None - } - Type::BitArray(dims, _) => Some(crate::types::Type::ResultArray(dims.into(), is_const)), - Type::QubitArray(dims) => Some(crate::types::Type::QubitArray(dims.into())), - Type::IntArray(dims) | Type::UIntArray(dims) => { - Some(crate::types::Type::IntArray(dims.into(), is_const)) - } - Type::FloatArray(dims) => Some(crate::types::Type::DoubleArray(dims.into())), - Type::AngleArray(_) => todo!("AngleArray to Q# type"), - Type::ComplexArray(_) => todo!("ComplexArray to Q# type"), - Type::BoolArray(dims) => Some(crate::types::Type::BoolArray(dims.into(), is_const)), - Type::Gate(cargs, qargs) => Some(crate::types::Type::Callable( - crate::types::CallableKind::Operation, - *cargs, - *qargs, - )), - Type::Range => Some(crate::types::Type::Range), - Type::Set => todo!("Set to Q# type"), - Type::Void => Some(crate::types::Type::Tuple(vec![])), - _ => { - let msg = format!("Converting {ty:?} to Q# type"); - self.push_unimplemented_error_message(msg, node); - None - } - } - } - - fn get_default_value(&mut self, ty: &Type, node: &SyntaxNode) -> Option { - let span = span_for_syntax_node(node); - match ty { - Type::Bit(_) => Some(build_lit_result_expr(ast::Result::Zero, span)), - Type::Qubit => { - let message = "Qubit default values"; - self.push_unsupported_error_message(message, node); - None - } - Type::HardwareQubit => { - let message = "HardwareQubit default values"; - self.push_unsupported_error_message(message, node); - None - } - Type::Int(width, _) | Type::UInt(width, _) => { - if let Some(width) = width { - if *width > 64 { - Some(build_lit_bigint_expr(BigInt::from(0), span)) - } else { - Some(build_lit_int_expr(0, span)) - } - } else { - Some(build_lit_int_expr(0, span)) - } - } - Type::Float(_, _) => Some(build_lit_double_expr(0.0, span)), - Type::Angle(_, _) => todo!("Angle default values"), - Type::Complex(_, _) => Some(build_lit_complex_expr( - crate::types::Complex::new(0.0, 0.0), - span, - )), - Type::Bool(_) => Some(build_lit_bool_expr(false, span)), - Type::Duration(_) => { - self.push_unsupported_error_message( - "Duration type values are not supported.", - node, - ); - None - } - Type::Stretch(_) => { - self.push_unsupported_error_message("Stretch type values are not supported.", node); - None - } - Type::BitArray(dims, _) => match dims { - ArrayDims::D1(len) => Some(build_default_result_array_expr(*len, span)), - ArrayDims::D2(_, _) => { - self.push_unsupported_error_message( - "2-dim Bit Arrays without default values", - node, - ); - None - } - ArrayDims::D3(_, _, _) => { - self.push_unsupported_error_message( - "3-dim Bit Arrays without default values", - node, - ); - None - } - }, - Type::QubitArray(_) => { - let message = "QubitArray default values"; - self.push_unsupported_error_message(message, node); - None - } - Type::IntArray(_) - | Type::UIntArray(_) - | Type::FloatArray(_) - | Type::AngleArray(_) - | Type::ComplexArray(_) - | Type::BoolArray(_) => { - self.push_unsupported_error_message("Arrays without default values", node); - None - } - Type::DurationArray(_) => { - self.push_unsupported_error_message( - "DurationArray type values are not supported.", - node, - ); - None - } - Type::Gate(_, _) - | Type::Range - | Type::Set - | Type::Void - | Type::ToDo - | Type::Undefined => { - let mut message = format!("Default values for {ty:?} are unsupported."); - message.push_str(" This is likely a bug in the compiler."); - self.push_unsupported_error_message(message, node); - None - } - } - } - - /// Define the standard gates in the symbol table. - /// The sdg, tdg, crx, cry, crz, and ch are defined - /// as their bare gates, and modifiers are applied - /// when calling them. - fn define_stdgates(&mut self, include: &oq3_syntax::ast::Include) { - fn gate_symbol(name: &str, cargs: usize, qargs: usize) -> Symbol { - Symbol { - name: name.to_string(), - ty: Type::Gate(cargs, qargs), - ..Default::default() - } - } - let gates = vec![ - gate_symbol("X", 0, 1), - gate_symbol("Y", 0, 1), - gate_symbol("Z", 0, 1), - gate_symbol("H", 0, 1), - gate_symbol("S", 0, 1), - gate_symbol("T", 0, 1), - gate_symbol("Rx", 1, 1), - gate_symbol("Rxx", 1, 2), - gate_symbol("Ry", 1, 1), - gate_symbol("Ryy", 1, 2), - gate_symbol("Rz", 1, 1), - gate_symbol("Rzz", 1, 2), - gate_symbol("CNOT", 0, 2), - gate_symbol("CY", 0, 2), - gate_symbol("CZ", 0, 2), - gate_symbol("I", 0, 1), - gate_symbol("SWAP", 0, 2), - gate_symbol("CCNOT", 0, 3), - ]; - for gate in gates { - let name = gate.name.clone(); - if self.symbols.insert_symbol(gate).is_err() { - self.push_redefined_symbol_error( - name.as_str(), - span_for_syntax_node(include.syntax()), - ); - } - } - } - - fn get_semantic_type_from_scalar_type( - &mut self, - scalar_ty: &oq3_syntax::ast::ScalarType, - is_const: bool, - ) -> Option { - let designator = get_designator_from_scalar_type(scalar_ty); - let is_const = is_const.into(); - let width = if let Some(designator) = designator { - match designator.expr() { - Some(oq3_syntax::ast::Expr::Literal(ref literal)) => match literal.kind() { - oq3_syntax::ast::LiteralKind::IntNumber(int_num) => { - let size: u32 = u32::try_from(int_num.value()?).ok()?; - Some(size) - } - _ => None, - }, - Some(expr) => { - let span = span_for_syntax_node(expr.syntax()); - let kind = SemanticErrorKind::DesignatorMustBeIntLiteral(span); - self.push_semantic_error(kind); - return None; - } - None => None, - } - } else { - None - }; - - let ty = match scalar_ty.kind() { - oq3_syntax::ast::ScalarTypeKind::Angle => { - oq3_semantics::types::Type::Angle(width, is_const) - } - oq3_syntax::ast::ScalarTypeKind::Bit => match width { - Some(width) => { - oq3_semantics::types::Type::BitArray(ArrayDims::D1(width as usize), is_const) - } - None => oq3_semantics::types::Type::Bit(is_const), - }, - oq3_syntax::ast::ScalarTypeKind::Bool => oq3_semantics::types::Type::Bool(is_const), - oq3_syntax::ast::ScalarTypeKind::Complex => { - oq3_semantics::types::Type::Complex(width, is_const) - } - oq3_syntax::ast::ScalarTypeKind::Duration => { - oq3_semantics::types::Type::Duration(is_const) - } - oq3_syntax::ast::ScalarTypeKind::Float => { - oq3_semantics::types::Type::Float(width, is_const) - } - oq3_syntax::ast::ScalarTypeKind::Int => { - oq3_semantics::types::Type::Int(width, is_const) - } - oq3_syntax::ast::ScalarTypeKind::Qubit => match width { - Some(width) => { - oq3_semantics::types::Type::QubitArray(ArrayDims::D1(width as usize)) - } - None => oq3_semantics::types::Type::Qubit, - }, - oq3_syntax::ast::ScalarTypeKind::Stretch => { - oq3_semantics::types::Type::Stretch(is_const) - } - oq3_syntax::ast::ScalarTypeKind::UInt => { - oq3_semantics::types::Type::UInt(width, is_const) - } - oq3_syntax::ast::ScalarTypeKind::None => { - let msg = "ScalarTypeKind::None should have been handled by the parser".to_string(); - let span = span_for_syntax_node(scalar_ty.syntax()); - let kind = SemanticErrorKind::UnexpectedParserError(msg, span); - self.push_semantic_error(kind); - return None; - } - }; - Some(ty) - } - - fn try_cast_expr_to_type( - &mut self, - ty: &Type, - rhs: &QasmTypedExpr, - node: &SyntaxNode, - ) -> Option { - if *ty == rhs.ty { - // Base case, we shouldn't have gotten here - // but if we did, we can just return the rhs - return Some(rhs.clone()); - } - if types_equal_except_const(ty, &rhs.ty) { - if rhs.ty.is_const() { - // lhs isn't const, but rhs is, we can just return the rhs - return Some(rhs.clone()); - } - // the lsh is supposed to be const but is being initialized - // to a non-const value, we can't allow this - return None; - } - // if the target type is wider, we can try to relax the rhs type - // We only do this for float and complex. Int types - // require extra handling for BigInts - match (ty, &rhs.ty) { - (Type::Float(w1, _), Type::Float(w2, _)) - | (Type::Complex(w1, _), Type::Complex(w2, _)) => { - if w1.is_none() && w2.is_some() { - return Some(QasmTypedExpr { - ty: ty.clone(), - expr: rhs.expr.clone(), - }); - } - - if *w1 >= *w2 { - return Some(QasmTypedExpr { - ty: ty.clone(), - expr: rhs.expr.clone(), - }); - } - } - _ => {} - } - // Casting of literals is handled elsewhere. This is for casting expressions - // which cannot be bypassed and must be handled by built-in Q# casts from - // the standard library. - match &rhs.ty { - Type::Angle(_, _) => self.cast_angle_expr_to_type(ty, rhs, node), - Type::Bit(_) => self.cast_bit_expr_to_type(ty, rhs, node), - Type::Bool(_) => self.cast_bool_expr_to_type(ty, rhs), - Type::Complex(_, _) => cast_complex_expr_to_type(ty, rhs), - Type::Float(_, _) => self.cast_float_expr_to_type(ty, rhs, node), - Type::Int(_, _) | Type::UInt(_, _) => self.cast_int_expr_to_type(ty, rhs), - Type::BitArray(dims, _) => self.cast_bitarray_expr_to_type(dims, ty, rhs), - _ => None, - } - } - - fn cast_expr_to_type( - &mut self, - ty: &Type, - rhs: &QasmTypedExpr, - node: &SyntaxNode, - ) -> Option { - let cast_expr = self.try_cast_expr_to_type(ty, rhs, node); - if cast_expr.is_none() { - let rhs_ty_name = format!("{:?}", rhs.ty); - let lhs_ty_name = format!("{ty:?}"); - let span = span_for_syntax_node(node); - let kind = SemanticErrorKind::CannotCast(rhs_ty_name, lhs_ty_name, span); - self.push_semantic_error(kind); - } - cast_expr - } - - #[allow(clippy::too_many_lines)] - fn cast_literal_expr_to_type( - &mut self, - ty: &Type, - rhs: &QasmTypedExpr, - literal: &Literal, - ) -> Option { - if *ty == rhs.ty { - // Base case, we shouldn't have gotten here - // but if we did, we can just return the rhs - return Some(rhs.clone()); - } - if types_equal_except_const(ty, &rhs.ty) { - if rhs.ty.is_const() { - // lhs isn't const, but rhs is, we can just return the rhs - return Some(rhs.clone()); - } - // the lsh is supposed to be const but is being initialized - // to a non-const value, we can't allow this - return None; - } - assert!( - can_cast_literal(ty, &rhs.ty) || can_cast_literal_with_value_knowledge(ty, literal) - ); - let lhs_ty = ty.clone(); - let rhs_ty = rhs.ty.clone(); - let span = rhs.expr.span; - - if matches!(lhs_ty, Type::Bit(..)) { - if let LiteralKind::IntNumber(value) = literal.kind() { - return compile_intnumber_as_bit(&value, span, ty); - } else if let LiteralKind::Bool(value) = literal.kind() { - let expr = build_lit_result_expr(value.into(), span); - return Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }); - } - } - // if lhs_ty is 1 dim bitarray and rhs is int/uint, we can cast - let (is_int_to_bit_array, size) = match &lhs_ty { - Type::BitArray(dims, _) => { - if matches!(rhs.ty, Type::Int(..) | Type::UInt(..)) { - match dims { - ArrayDims::D1(size) => (true, *size), - _ => (false, 0), - } - } else { - (false, 0) - } - } - _ => (false, 0), - }; - if is_int_to_bit_array { - if let LiteralKind::IntNumber(value) = literal.kind() { - // Value can't be negative as IntNumber is unsigned - // any sign would come from a prefix expression - if let Some(value) = value.value() { - if let Ok(value) = value.try_into() { - let value: i64 = value; - if value >= 0 && value < (1 << size) { - let bitstring = format!("{value:0size$b}"); - let expr = build_lit_result_array_expr_from_bitstring(bitstring, span); - return Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }); - } - return None; - } - } - } - } - if matches!(lhs_ty, Type::UInt(..)) { - if let LiteralKind::IntNumber(value) = literal.kind() { - // Value can't be negative as IntNumber is unsigned - // any sign would come from a prefix expression - if let Some(value) = value.value() { - if let Ok(value) = value.try_into() { - let value: i64 = value; - let expr = build_lit_int_expr(value, span); - let ty = Type::Int(None, IsConst::True); - return Some(QasmTypedExpr { ty, expr }); - } - } - } - } - let result = match (&lhs_ty, &rhs_ty) { - (Type::Float(..), Type::Int(..) | Type::UInt(..)) => { - // the qasm type is int/uint, but the expr will be q# int - if let LiteralKind::IntNumber(value) = literal.kind() { - let expr = self.compile_int_to_double_literal(&value, false, span)?; - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } else { - panic!("Literal must be an IntNumber") - } - } - (Type::Float(..), Type::Float(..)) => { - if let LiteralKind::FloatNumber(value) = literal.kind() { - let value = value.value().expect("FloatNumber must have a value"); - let expr = build_lit_double_expr(value, span); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } else { - panic!("Literal must be a FloatNumber") - } - } - (Type::Complex(..), Type::Float(..)) => { - let expr = build_complex_from_expr(rhs.expr.clone()); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } - (Type::Complex(..), Type::Int(..) | Type::UInt(..)) => { - // complex requires a double as input, so we need to - // convert the int to a double, then create the complex - if let LiteralKind::IntNumber(value) = literal.kind() { - if let Some(expr) = self.compile_int_to_double_literal_to_complex(&value, span) - { - return Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }); - } - } - panic!("Literal must be an IntNumber") - } - (Type::Bit(..), Type::Int(..) | Type::UInt(..)) => { - // we've already checked that the value is 0 or 1 - if let LiteralKind::IntNumber(value) = literal.kind() { - let value = value.value().expect("IntNumber must have a value"); - if value == 0 || value == 1 { - let expr = build_lit_result_expr((value == 1).into(), rhs.expr.span); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } else { - panic!("Value must be 0 or 1"); - } - } else { - panic!("Literal must be an IntNumber"); - } - } - (Type::Int(..), Type::Int(..) | Type::UInt(..)) => { - // we've already checked that this conversion can happen - if let LiteralKind::IntNumber(value) = literal.kind() { - let value = value.value().expect("IntNumber must have a value"); - let expr = if let Ok(value) = value.try_into() { - let value: i64 = value; - build_lit_int_expr(value, span) - } else { - build_lit_bigint_expr(BigInt::from(value), span) - }; - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } else { - panic!("Literal must be an IntNumber"); - } - } - _ => None, - }; - if result.is_none() { - // we assert that the type can be casted - // but we didn't cast it, so this is a bug - panic!("Literal type cast failed lhs: {:?}, rhs: {:?}", ty, rhs.ty); - } else { - result - } - } - - fn create_entry_operation>( - &mut self, - name: S, - whole_span: Span, - ) -> (ast::Item, OperationSignature) { - let stmts = self.stmts.drain(..).collect::>(); - let input = self.symbols.get_input(); - let output = self.symbols.get_output(); - self.create_entry_item( - name, - stmts, - input, - output, - whole_span, - self.config.output_semantics, - ) - } - - /// +----------------+-------------------------------------------------------------+ - /// | Allowed casts | Casting To | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - /// | angle | Yes | No | No | No | - | Yes | No | No | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - fn cast_angle_expr_to_type( - &mut self, - ty: &Type, - rhs: &QasmTypedExpr, - node: &SyntaxNode, - ) -> Option { - assert!(matches!(rhs.ty, Type::Bit(..))); - match ty { - Type::Bit(..) => { - let msg = "Cast angle to bit"; - self.push_unimplemented_error_message(msg, node); - None - } - Type::Bool(..) => { - let msg = "Cast angle to bool"; - self.push_unimplemented_error_message(msg, node); - None - } - - _ => None, - } - } - - /// +----------------+-------------------------------------------------------------+ - /// | Allowed casts | Casting To | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - /// | bit | Yes | Yes | Yes | No | Yes | - | No | No | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - fn cast_bit_expr_to_type( - &mut self, - ty: &Type, - rhs: &QasmTypedExpr, - node: &SyntaxNode, - ) -> Option { - assert!(matches!(rhs.ty, Type::Bit(..))); - // There is no operand, choosing the span of the node - // but we could use the expr span as well. - let operand_span = span_for_syntax_node(node); - let name_span = rhs.expr.span; - match ty { - &Type::Angle(..) => { - let msg = "Cast bit to angle"; - self.push_unimplemented_error_message(msg, node); - None - } - &Type::Bool(..) => { - self.runtime |= RuntimeFunctions::ResultAsBool; - Some(QasmTypedExpr { - ty: ty.clone(), - expr: build_cast_call( - RuntimeFunctions::ResultAsBool, - rhs.expr.clone(), - name_span, - operand_span, - ), - }) - } - &Type::Float(..) => { - // The spec says that this cast isn't supported, but it - // casts to other types that case to float. For now, we'll - // say it is invalid like the spec. - None - } - &Type::Int(w, _) | &Type::UInt(w, _) => { - let function = if let Some(width) = w { - if width > 64 { - RuntimeFunctions::ResultAsBigInt - } else { - RuntimeFunctions::ResultAsInt - } - } else { - RuntimeFunctions::ResultAsInt - }; - self.runtime |= function; - let expr = build_cast_call(function, rhs.expr.clone(), name_span, operand_span); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } - - _ => None, - } - } - - /// +----------------+-------------------------------------------------------------+ - /// | Allowed casts | Casting To | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - /// | float | Yes | Yes | Yes | - | Yes | No | No | No | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - /// - /// Additional cast to complex - fn cast_float_expr_to_type( - &mut self, - ty: &Type, - rhs: &QasmTypedExpr, - node: &SyntaxNode, - ) -> Option { - assert!(matches!(rhs.ty, Type::Float(..))); - match ty { - &Type::Complex(..) => { - let expr = build_complex_from_expr(rhs.expr.clone()); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } - &Type::Angle(..) => { - let msg = "Cast float to angle"; - self.push_unimplemented_error_message(msg, node); - None - } - &Type::Int(w, _) | &Type::UInt(w, _) => { - let span = span_for_syntax_node(node); - let expr = ast_builder::build_math_call_from_exprs( - "Truncate", - vec![rhs.expr.clone()], - span, - ); - let expr = if let Some(w) = w { - if w > 64 { - build_convert_call_expr(expr, "IntAsBigInt") - } else { - expr - } - } else { - expr - }; - - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } - &Type::Bool(..) => { - let span = span_for_syntax_node(node); - let expr = ast_builder::build_math_call_from_exprs( - "Truncate", - vec![rhs.expr.clone()], - span, - ); - let const_int_zero_expr = build_lit_int_expr(0, rhs.expr.span); - let qsop = ast::BinOp::Eq; - let cond = ast_builder::build_binary_expr( - false, - qsop, - expr, - const_int_zero_expr, - rhs.expr.span, - ); - let coerce_expr = build_if_expr_then_expr_else_expr( - cond, - build_lit_bool_expr(false, rhs.expr.span), - build_lit_bool_expr(true, rhs.expr.span), - rhs.expr.span, - ); - Some(QasmTypedExpr { - ty: ty.clone(), - expr: coerce_expr, - }) - } - _ => None, - } - } - - fn create_entry_item>( - &mut self, - name: S, - stmts: Vec, - input: Option>, - output: Option>, - whole_span: Span, - output_semantics: OutputSemantics, - ) -> (ast::Item, OperationSignature) { - let mut stmts = stmts; - let is_qiskit = matches!(output_semantics, OutputSemantics::Qiskit); - let mut signature = OperationSignature { - input: vec![], - output: String::new(), - name: name.as_ref().to_string(), - ns: None, - }; - let output_ty = if matches!(output_semantics, OutputSemantics::ResourceEstimation) { - // we have no output, but need to set the entry point return type - crate::types::Type::Tuple(vec![]) - } else if let Some(output) = output { - let output_exprs = if is_qiskit { - output - .iter() - .rev() - .filter(|symbol| matches!(symbol.ty, Type::BitArray(..))) - .map(|symbol| { - let ident = - build_path_ident_expr(symbol.name.as_str(), symbol.span, symbol.span); - - build_array_reverse_expr(ident) - }) - .collect::>() - } else { - output - .iter() - .map(|symbol| { - build_path_ident_expr(symbol.name.as_str(), symbol.span, symbol.span) - }) - .collect::>() - }; - // this is the output whether it is inferred or explicitly defined - // map the output symbols into a return statement, add it to the nodes list, - // and get the entry point return type - let output_types = if is_qiskit { - output - .iter() - .rev() - .filter(|symbol| matches!(symbol.ty, Type::BitArray(..))) - .map(|symbol| symbol.qsharp_ty.clone()) - .collect::>() - } else { - output - .iter() - .map(|symbol| symbol.qsharp_ty.clone()) - .collect::>() - }; - - let (output_ty, output_expr) = if output_types.len() == 1 { - (output_types[0].clone(), output_exprs[0].clone()) - } else { - let output_ty = crate::types::Type::Tuple(output_types); - let output_expr = build_tuple_expr(output_exprs); - (output_ty, output_expr) - }; - - let return_stmt = build_implicit_return_stmt(output_expr); - stmts.push(return_stmt); - output_ty - } else { - if is_qiskit { - let kind = SemanticErrorKind::QiskitEntryPointMissingOutput(whole_span); - self.push_semantic_error(kind); - } - crate::types::Type::Tuple(vec![]) - }; - - let ast_ty = map_qsharp_type_to_ast_ty(&output_ty); - signature.output = format!("{output_ty}"); - // TODO: This can create a collision on multiple compiles when interactive - // We also have issues with the new entry point inference logic - let input_desc = input - .iter() - .flat_map(|s| { - s.iter() - .map(|s| (s.name.to_string(), format!("{}", s.qsharp_ty))) - }) - .collect::>(); - signature.input = input_desc; - let input_pats = input - .into_iter() - .flat_map(|s| { - s.into_iter() - .map(|s| build_arg_pat(s.name, s.span, map_qsharp_type_to_ast_ty(&s.qsharp_ty))) - }) - .collect::>(); - - ( - build_operation_with_stmts(name, input_pats, ast_ty, stmts, whole_span), - signature, - ) - } - - /// +----------------+-------------------------------------------------------------+ - /// | Allowed casts | Casting To | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - /// | bool | - | Yes | Yes | Yes | No | Yes | No | No | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - fn cast_bool_expr_to_type(&mut self, ty: &Type, rhs: &QasmTypedExpr) -> Option { - assert!(matches!(rhs.ty, Type::Bool(..))); - let name_span = rhs.expr.span; - let operand_span = rhs.expr.span; - match ty { - &Type::Bit(..) => { - self.runtime |= RuntimeFunctions::BoolAsResult; - let expr = build_cast_call( - RuntimeFunctions::BoolAsResult, - rhs.expr.clone(), - name_span, - operand_span, - ); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } - &Type::Float(..) => { - self.runtime |= RuntimeFunctions::BoolAsDouble; - let expr = build_cast_call( - RuntimeFunctions::BoolAsDouble, - rhs.expr.clone(), - name_span, - operand_span, - ); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } - &Type::Int(w, _) | &Type::UInt(w, _) => { - let function = if let Some(width) = w { - if width > 64 { - RuntimeFunctions::BoolAsBigInt - } else { - RuntimeFunctions::BoolAsInt - } - } else { - RuntimeFunctions::BoolAsInt - }; - self.runtime |= function; - let expr = build_cast_call(function, rhs.expr.clone(), name_span, operand_span); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } - _ => None, - } - } - - /// +----------------+-------------------------------------------------------------+ - /// | Allowed casts | Casting To | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - /// | int | Yes | - | Yes | Yes | No | Yes | No | No | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - /// | uint | Yes | Yes | - | Yes | No | Yes | No | No | - /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - /// - /// Additional cast to ``BigInt`` - #[allow(clippy::too_many_lines)] - fn cast_int_expr_to_type(&mut self, ty: &Type, rhs: &QasmTypedExpr) -> Option { - assert!(matches!(rhs.ty, Type::Int(..) | Type::UInt(..))); - let name_span = rhs.expr.span; - let operand_span = rhs.expr.span; - match ty { - Type::BitArray(dims, _) => { - self.runtime |= RuntimeFunctions::IntAsResultArrayBE; - let size = match dims { - ArrayDims::D1(size) => i64::try_from(*size).ok()?, - _ => 0, - }; - - let size_expr = build_lit_int_expr(size, Span::default()); - let expr = build_cast_call_two_params( - RuntimeFunctions::IntAsResultArrayBE, - rhs.expr.clone(), - size_expr, - name_span, - operand_span, - ); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } - Type::Float(..) => { - let expr = build_convert_call_expr(rhs.expr.clone(), "IntAsDouble"); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } - Type::Int(tw, _) | Type::UInt(tw, _) => { - // uint to int, or int/uint to BigInt - if let Some(tw) = tw { - if *tw > 64 { - let expr = build_convert_call_expr(rhs.expr.clone(), "IntAsBigInt"); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } else { - Some(QasmTypedExpr { - ty: ty.clone(), - expr: rhs.expr.clone(), - }) - } - } else { - Some(QasmTypedExpr { - ty: ty.clone(), - expr: rhs.expr.clone(), - }) - } - } - Type::Bool(..) => { - let const_int_zero_expr = build_lit_int_expr(0, rhs.expr.span); - let qsop = ast::BinOp::Eq; - let cond = ast_builder::build_binary_expr( - false, - qsop, - rhs.expr.clone(), - const_int_zero_expr, - rhs.expr.span, - ); - let coerce_expr = build_if_expr_then_expr_else_expr( - cond, - build_lit_bool_expr(false, rhs.expr.span), - build_lit_bool_expr(true, rhs.expr.span), - rhs.expr.span, - ); - Some(QasmTypedExpr { - ty: ty.clone(), - expr: coerce_expr, - }) - } - Type::Bit(..) => { - let const_int_zero_expr = build_lit_int_expr(0, rhs.expr.span); - let qsop = ast::BinOp::Eq; - let cond = ast_builder::build_binary_expr( - false, - qsop, - rhs.expr.clone(), - const_int_zero_expr, - rhs.expr.span, - ); - let coerce_expr = build_if_expr_then_expr_else_expr( - cond, - build_lit_result_expr(ast::Result::One, rhs.expr.span), - build_lit_result_expr(ast::Result::Zero, rhs.expr.span), - rhs.expr.span, - ); - Some(QasmTypedExpr { - ty: ty.clone(), - expr: coerce_expr, - }) - } - Type::Complex(..) => { - let expr = build_convert_call_expr(rhs.expr.clone(), "IntAsDouble"); - let expr = build_complex_from_expr(expr); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } - _ => None, - } - } - - fn cast_bitarray_expr_to_type( - &mut self, - dims: &ArrayDims, - ty: &Type, - rhs: &QasmTypedExpr, - ) -> Option { - let ArrayDims::D1(array_width) = dims else { - return None; - }; - if !matches!(ty, Type::Int(..) | Type::UInt(..)) { - return None; - } - // we know we have a bit array being cast to an int/uint - // verfiy widths - let int_width = ty.width(); - - if int_width.is_none() || (int_width == Some(u32::try_from(*array_width).ok()?)) { - let name_span = rhs.expr.span; - let operand_span = rhs.expr.span; - self.runtime |= RuntimeFunctions::ResultArrayAsIntBE; - let expr = build_cast_call( - RuntimeFunctions::ResultArrayAsIntBE, - rhs.expr.clone(), - name_span, - operand_span, - ); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } else { - None - } - } - - /// Pushes an unimplemented error with the supplied message. - pub fn push_unimplemented_error_message>( - &mut self, - message: S, - node: &SyntaxNode, - ) { - let span = span_for_syntax_node(node); - let kind = crate::ErrorKind::Unimplemented(message.as_ref().to_string(), span); - let error = self.create_err(kind); - self.errors.push(error); - } - - /// Pushes a missing symbol error with the given name - /// This is a convenience method for pushing a `SemanticErrorKind::UndefinedSymbol` error. - pub fn push_missing_symbol_error>(&mut self, name: S, node: &SyntaxNode) { - let span = span_for_syntax_node(node); - let kind = SemanticErrorKind::UndefinedSymbol(name.as_ref().to_string(), span); - let kind = crate::ErrorKind::Semantic(SemanticError(kind)); - let error = self.create_err(kind); - self.errors.push(error); - } - - /// Pushes a redefined symbol error with the given name and span. - /// This is a convenience method for pushing a `SemanticErrorKind::RedefinedSymbol` error. - pub fn push_redefined_symbol_error>(&mut self, name: S, span: Span) { - let kind = SemanticErrorKind::RedefinedSymbol(name.as_ref().to_string(), span); - self.push_semantic_error(kind); - } - - /// Pushes a semantic error with the given kind. - pub fn push_semantic_error(&mut self, kind: SemanticErrorKind) { - let kind = crate::ErrorKind::Semantic(SemanticError(kind)); - let error = self.create_err(kind); - self.errors.push(error); - } - - /// Pushes an unsupported error with the supplied message. - pub fn push_unsupported_error_message>(&mut self, message: S, node: &SyntaxNode) { - let span = span_for_syntax_node(node); - let kind = crate::ErrorKind::NotSupported(message.as_ref().to_string(), span); - let error = self.create_err(kind); - self.errors.push(error); - } - - /// Pushes an error for a gate not being supported. - pub fn push_calibration_error(&mut self, node: &SyntaxNode) { - let span = span_for_syntax_node(node); - let text = node.text().to_string(); - let kind = crate::ErrorKind::CalibrationsNotSupported(text, span); - let error = self.create_err(kind); - self.errors.push(error); - } - - /// Creates an error from the given kind with the current source mapping. - fn create_err(&self, kind: crate::ErrorKind) -> WithSource { - let error = crate::Error(kind); - let path = self.file_stack.last().map_or("", |p| { - p.to_str().expect("expected source mapping to exist.") - }); - let source = self.source_map.find_by_name(path); - let offset = source.map_or(0, |x| x.offset); - let offset_error = error.with_offset(offset); - WithSource::from_map(&self.source_map, offset_error) - } -} - -fn compile_end_stmt(end: &oq3_syntax::ast::EndStmt) -> ast::Stmt { - ast_builder::build_end_stmt(span_for_syntax_node(end.syntax())) -} - -/// This is missing bitwise negation, but it is impossible to test -/// as the parser doesn't support it. -fn binop_requires_bitwise_conversion(op: BinaryOp, left_type: &Type) -> bool { - match op { - BinaryOp::ArithOp(arith) => match arith { - ArithOp::BitAnd | ArithOp::BitOr | ArithOp::BitXor => matches!( - left_type, - Type::Bit(..) - | Type::UInt(..) - | Type::Angle(..) - | Type::BitArray(ArrayDims::D1(_), _) - ), - ArithOp::Shl | ArithOp::Shr => matches!( - left_type, - Type::Bit(..) - | Type::UInt(..) - | Type::Angle(..) - | Type::BitArray(ArrayDims::D1(_), _) - ), - _ => false, - }, - _ => false, - } -} - -fn binop_requires_bitwise_symmetric_conversion(op: BinaryOp) -> bool { - match op { - BinaryOp::ArithOp(arith) => { - matches!(arith, ArithOp::BitAnd | ArithOp::BitOr | ArithOp::BitXor) - } - _ => false, - } -} - -fn calculate_num_ctrls(modifiers: &[&GateModifier]) -> u64 { - let num_ctrls: u64 = modifiers - .iter() - .map(|m| match m { - GateModifier::Inv(_) | GateModifier::Pow(_, _) => 0, - GateModifier::Ctrl(ctls, _) | GateModifier::NegCtrl(ctls, _) => { - TryInto::::try_into(ctls.unwrap_or(1)) - .ok() - .unwrap_or(0) - } - }) - .sum(); - num_ctrls -} - -fn get_implicit_modifiers>( - gate_name: S, - name_span: Span, -) -> (String, Vec) { - // ch, crx, cry, crz, sdg, and tdg - match gate_name.as_ref() { - "ch" => ("H".to_string(), vec![GateModifier::Ctrl(None, name_span)]), - "crx" => ("Rx".to_string(), vec![GateModifier::Ctrl(None, name_span)]), - "cry" => ("Ry".to_string(), vec![GateModifier::Ctrl(None, name_span)]), - "crz" => ("Rz".to_string(), vec![GateModifier::Ctrl(None, name_span)]), - "sdg" => ("S".to_string(), vec![GateModifier::Inv(name_span)]), - "tdg" => ("T".to_string(), vec![GateModifier::Inv(name_span)]), - _ => (gate_name.as_ref().to_string(), vec![]), - } -} - -/// Bit arrays can be compared, but need to be converted to int first -fn binop_requires_int_conversion_for_type(op: BinaryOp, ty_1: &Type, ty_2: &Type) -> bool { - match op { - BinaryOp::CmpOp(_) => match (ty_1, ty_2) { - (Type::BitArray(ArrayDims::D1(d1), _), Type::BitArray(ArrayDims::D1(d2), _)) => { - d1 == d2 - } - _ => false, - }, - _ => false, - } -} - -fn binop_requires_bool_conversion_for_type(op: BinaryOp) -> bool { - matches!(op, BinaryOp::LogicOp(..)) -} - -fn compile_intnumber_as_bit( - value: &oq3_syntax::ast::IntNumber, - span: Span, - ty: &Type, -) -> Option { - let value = value.value().expect("IntNumber must have a value"); - if value == 0 || value == 1 { - let expr = build_lit_result_expr((value == 1).into(), span); - Some(QasmTypedExpr { - ty: ty.clone(), - expr, - }) - } else { - None - } -} - -fn compile_floatnumber_as_negated_double( - value: &oq3_syntax::ast::FloatNumber, - span: Span, -) -> QasmTypedExpr { - let expr = build_lit_double_expr(-value.value().expect("FloatNumber must have a value"), span); - let ty = Type::Float(None, IsConst::True); - QasmTypedExpr { ty, expr } -} - -fn compile_intnumber_as_negated_int( - value: &oq3_syntax::ast::IntNumber, - span: Span, -) -> QasmTypedExpr { - let value = value.value().expect("IntNumber must have a value"); - if let Ok(value) = value.try_into() { - let value: i64 = value; - let expr = build_lit_int_expr(-value, span); - let ty = Type::Int(None, IsConst::True); - QasmTypedExpr { ty, expr } - } else { - let expr = build_lit_bigint_expr(BigInt::from(-1) * BigInt::from(value), span); - let ty = Type::Int(Some(128), IsConst::True); - QasmTypedExpr { ty, expr } - } -} - -/// +----------------+-------------------------------------------------------------+ -/// | Allowed casts | Casting To | -/// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ -/// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | -/// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ -/// | complex | ?? | ?? | ?? | ?? | No | ?? | No | No | -/// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ -fn cast_complex_expr_to_type(ty: &Type, rhs: &QasmTypedExpr) -> Option { - assert!(matches!(rhs.ty, Type::Complex(..))); - - if matches!((ty, &rhs.ty), (Type::Complex(..), Type::Complex(..))) { - // we are both complex, but our widths are different. If both - // had implicit widths, we would have already matched for the - // (None, None). If the rhs width is bigger, we will return - // None to let the cast fail - - // Here, we can safely cast the rhs to the lhs type if the - // lhs width can hold the rhs's width - if ty.width().is_none() && rhs.ty.width().is_some() { - return Some(QasmTypedExpr { - ty: ty.clone(), - expr: rhs.expr.clone(), - }); - } - if ty.width() >= rhs.ty.width() { - return Some(QasmTypedExpr { - ty: ty.clone(), - expr: rhs.expr.clone(), - }); - } - } - None -} - -fn try_promote_with_casting(left_type: &Type, right_type: &Type) -> Type { - let promoted_type = promote_types(left_type, right_type); - - if promoted_type != Type::Void { - return promoted_type; - } - if let Some(value) = try_promote_bitarray_to_int(left_type, right_type) { - return value; - } - // simple promotion failed, try a lossless cast - // each side to double - let promoted_rhs = promote_types(&Type::Float(None, IsConst::False), right_type); - let promoted_lhs = promote_types(left_type, &Type::Float(None, IsConst::False)); - - match (promoted_lhs, promoted_rhs) { - (Type::Void, Type::Void) => Type::Float(None, IsConst::False), - (Type::Void, promoted_rhs) => promoted_rhs, - (promoted_lhs, Type::Void) => promoted_lhs, - (promoted_lhs, promoted_rhs) => { - // return the greater of the two promoted types - if matches!(promoted_lhs, Type::Complex(..)) { - promoted_lhs - } else if matches!(promoted_rhs, Type::Complex(..)) { - promoted_rhs - } else if matches!(promoted_lhs, Type::Float(..)) { - promoted_lhs - } else if matches!(promoted_rhs, Type::Float(..)) { - promoted_rhs - } else { - Type::Float(None, IsConst::False) - } - } - } -} - -fn try_promote_bitarray_to_int(left_type: &Type, right_type: &Type) -> Option { - if matches!( - (left_type, right_type), - (Type::Int(..) | Type::UInt(..), Type::BitArray(..)) - ) { - let ty = left_type; - let r = right_type.dims().expect("")[0]; - - if ty.dims().is_none() || (ty.num_dims() == 1 && ty.dims().expect("")[0] == r) { - return Some(left_type.clone()); - } - } - if matches!( - (left_type, right_type), - (Type::BitArray(..), Type::Int(..) | Type::UInt(..)) - ) { - let ty = right_type; - let r = left_type.dims().expect("")[0]; - - if ty.dims().is_none() || (ty.num_dims() == 1 && ty.dims().expect("")[0] == r) { - return Some(right_type.clone()); - } - } - None -} - -fn compile_bitstring(bitstring: &BitString, span: Span) -> Option { - let width = bitstring - .to_string() - .chars() - .filter(|c| *c == '0' || *c == '1') - .count(); - let expr = bitstring - .value() - .map(|value| build_lit_result_array_expr_from_bitstring(value, span))?; - let ty = Type::BitArray(ArrayDims::D1(width), IsConst::True); - Some(QasmTypedExpr { ty, expr }) -} - -pub fn can_cast_literal(lhs_ty: &Type, ty_lit: &Type) -> bool { - if matches!(lhs_ty, Type::Int(..)) && matches!(ty_lit, Type::UInt(..)) { - return true; - } - if matches!(lhs_ty, Type::UInt(..)) { - return matches!(ty_lit, Type::Complex(..)); - } - oq3_semantics::types::can_cast_literal(lhs_ty, ty_lit) - || { - matches!(lhs_ty, Type::Bit(..) | Type::Bool(..)) - && matches!(ty_lit, Type::Bit(..) | Type::Bool(..)) - } - || { - match lhs_ty { - Type::BitArray(dims, _) => { - matches!(dims, ArrayDims::D1(_)) - && matches!(ty_lit, Type::Int(..) | Type::UInt(..)) - } - _ => false, - } - } -} - -fn extract_pow_exponent(expr: &oq3_syntax::ast::ParenExpr, span: Span) -> GateModifier { - let lit = compile_paren_lit_int_expr(expr); - if let Some((exponent, sign)) = lit { - let exponent = i64::try_from(exponent).ok(); - let Some(exponent) = exponent else { - return GateModifier::Pow(None, span); - }; - if sign { - GateModifier::Pow(Some(-exponent), span) - } else { - GateModifier::Pow(Some(exponent), span) - } - } else { - GateModifier::Pow(None, span) - } -} - -fn compile_paren_lit_int_expr(paren_expr: &oq3_syntax::ast::ParenExpr) -> Option<(usize, bool)> { - let expr = paren_expr.expr()?; - match expr { - Expr::Literal(lit) => match lit.kind() { - LiteralKind::IntNumber(value) => { - let size: usize = usize::try_from(value.value()?).ok()?; - Some((size, false)) - } - _ => None, - }, - Expr::PrefixExpr(prefix) => match prefix.op_kind() { - Some(UnaryOp::Neg) => { - let expr = prefix.expr()?; - match expr { - Expr::Literal(lit) => match lit.kind() { - LiteralKind::IntNumber(value) => { - let size: usize = usize::try_from(value.value()?).ok()?; - Some((size, true)) - } - _ => None, - }, - _ => None, - } - } - _ => None, - }, - _ => None, - } -} diff --git a/compiler/qsc_qasm3/src/compile/tests.rs b/compiler/qsc_qasm3/src/compile/tests.rs deleted file mode 100644 index 33c51c242e..0000000000 --- a/compiler/qsc_qasm3/src/compile/tests.rs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::tests::{parse_all, print_compilation_errors, qasm_to_program_fragments}; -use miette::Report; - -#[test] -fn programs_with_includes_with_includes_can_be_compiled() -> miette::Result<(), Vec> { - let source0 = r#"include "stdgates.inc"; - include "source1.qasm"; - "#; - let source1 = r#"include "source2.qasm"; - "#; - let source2 = ""; - let all_sources = [ - ("source0.qasm".into(), source0.into()), - ("source1.qasm".into(), source1.into()), - ("source2.qasm".into(), source2.into()), - ]; - - let res = parse_all("source0.qasm", all_sources)?; - assert!(!res.has_errors()); - let unit = qasm_to_program_fragments(res.source, res.source_map); - print_compilation_errors(&unit); - assert!(!unit.has_errors()); - Ok(()) -} - -#[test] -fn including_stdgates_multiple_times_causes_symbol_redifintion_errors( -) -> miette::Result<(), Vec> { - let source0 = r#"include "stdgates.inc"; - include "source1.qasm"; - "#; - let source1 = r#"include "source2.qasm"; - "#; - let source2 = r#"include "stdgates.inc";"#; - let all_sources = [ - ("source0.qasm".into(), source0.into()), - ("source1.qasm".into(), source1.into()), - ("source2.qasm".into(), source2.into()), - ]; - - let res = parse_all("source0.qasm", all_sources)?; - assert!(!res.has_errors()); - let unit = qasm_to_program_fragments(res.source, res.source_map); - - assert!(unit.has_errors()); - for error in unit.errors() { - assert!(error.to_string().contains("Redefined symbol: ")); - } - Ok(()) -} diff --git a/compiler/qsc_qasm3/src/io.rs b/compiler/qsc_qasm3/src/io.rs deleted file mode 100644 index 79df8d5608..0000000000 --- a/compiler/qsc_qasm3/src/io.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use std::{ - path::{Path, PathBuf}, - sync::Arc, -}; - -use rustc_hash::FxHashMap; - -use miette::IntoDiagnostic; - -/// A trait for resolving include file paths to their contents. -/// This is used by the parser to resolve `include` directives. -/// Implementations of this trait can be provided to the parser -/// to customize how include files are resolved. -pub trait SourceResolver { - #[cfg(feature = "fs")] - fn resolve

(&self, path: P) -> miette::Result<(PathBuf, String)> - where - P: AsRef, - { - let path = std::fs::canonicalize(path).map_err(|e| { - crate::Error(crate::ErrorKind::IO(format!( - "Could not resolve include file path: {e}" - ))) - })?; - match std::fs::read_to_string(&path) { - Ok(source) => Ok((path, source)), - Err(_) => Err(crate::Error(crate::ErrorKind::NotFound(format!( - "Could not resolve include file: {}", - path.display() - )))) - .into_diagnostic(), - } - } - #[cfg(not(feature = "fs"))] - fn resolve

(&self, path: P) -> miette::Result<(PathBuf, String)> - where - P: AsRef; -} - -/// A source resolver that resolves include files from an in-memory map. -/// This is useful for testing or environments in which file system access -/// is not available. -/// -/// This requires users to build up a map of include file paths to their -/// contents prior to parsing. -pub struct InMemorySourceResolver { - sources: FxHashMap, -} - -impl FromIterator<(Arc, Arc)> for InMemorySourceResolver { - fn from_iter, Arc)>>(iter: T) -> Self { - let mut map = FxHashMap::default(); - for (path, source) in iter { - map.insert(PathBuf::from(path.to_string()), source.to_string()); - } - - InMemorySourceResolver { sources: map } - } -} - -impl SourceResolver for InMemorySourceResolver { - fn resolve

(&self, path: P) -> miette::Result<(PathBuf, String)> - where - P: AsRef, - { - let path = path.as_ref(); - match self.sources.get(path) { - Some(source) => Ok((path.to_owned(), source.clone())), - None => Err(crate::Error(crate::ErrorKind::NotFound(format!( - "Could not resolve include file: {}", - path.display() - )))) - .into_diagnostic(), - } - } -} diff --git a/compiler/qsc_qasm3/src/lib.rs b/compiler/qsc_qasm3/src/lib.rs deleted file mode 100644 index b94e18abb4..0000000000 --- a/compiler/qsc_qasm3/src/lib.rs +++ /dev/null @@ -1,606 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -mod angle; -mod ast_builder; -mod compile; -pub use compile::qasm_to_program; -pub mod io; -mod oqasm_helpers; -mod oqasm_types; -pub mod parse; -mod runtime; -mod symbols; -mod types; - -#[cfg(test)] -pub(crate) mod tests; - -use std::{fmt::Write, sync::Arc}; - -use miette::Diagnostic; -use qsc_ast::ast::Package; -use qsc_data_structures::span::Span; -use qsc_frontend::{compile::SourceMap, error::WithSource}; -use thiserror::Error; - -#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] -#[diagnostic(transparent)] -#[error(transparent)] -pub struct Error(pub ErrorKind); - -impl Error { - #[must_use] - pub fn with_offset(self, offset: u32) -> Self { - Self(self.0.with_offset(offset)) - } - - #[must_use] - pub fn is_syntax_error(&self) -> bool { - matches!(self.0, ErrorKind::Parse(_, _)) - } - - #[must_use] - pub fn is_semantic_error(&self) -> bool { - matches!(self.0, ErrorKind::Semantic(..)) - } -} - -/// Represents the kind of error that occurred during compilation of a QASM file(s). -/// The errors fall into a few categories: -/// - Unimplemented features -/// - Not supported features -/// - Parsing errors (converted from the parser) -/// - Semantic errors -/// - IO errors -#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] -pub enum ErrorKind { - #[error("this statement is not yet handled during OpenQASM 3 import: {0}")] - Unimplemented(String, #[label] Span), - #[error("calibration statements are not supported: {0}")] - CalibrationsNotSupported(String, #[label] Span), - #[error("{0} are not supported.")] - NotSupported(String, #[label] Span), - #[error("QASM3 Parse Error: {0}")] - Parse(String, #[label] Span), - #[error(transparent)] - #[diagnostic(transparent)] - Semantic(#[from] crate::SemanticError), - #[error("QASM3 Parse Error: Not Found {0}")] - NotFound(String), - #[error("IO Error: {0}")] - IO(String), -} - -impl ErrorKind { - fn with_offset(self, offset: u32) -> Self { - match self { - ErrorKind::Unimplemented(error, span) => Self::Unimplemented(error, span + offset), - ErrorKind::CalibrationsNotSupported(error, span) => { - Self::CalibrationsNotSupported(error, span + offset) - } - ErrorKind::NotSupported(error, span) => Self::NotSupported(error, span + offset), - ErrorKind::Parse(error, span) => Self::Parse(error, span + offset), - ErrorKind::Semantic(error) => ErrorKind::Semantic(error.with_offset(offset)), - ErrorKind::NotFound(error) => Self::NotFound(error), - ErrorKind::IO(error) => Self::IO(error), - } - } -} - -#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] -#[error(transparent)] -#[diagnostic(transparent)] -pub struct SemanticError(SemanticErrorKind); - -impl SemanticError { - #[must_use] - pub fn with_offset(self, offset: u32) -> Self { - Self(self.0.with_offset(offset)) - } -} - -/// Represents the kind of semantic error that occurred during compilation of a QASM file(s). -/// For the most part, these errors are fatal and prevent compilation and are -/// safety checks to ensure that the QASM code is valid. -/// -/// We can't use the semantics library for this: -/// - it is unsafe to use (heavy use of panic and unwrap) -/// - it is missing many language features -#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] -enum SemanticErrorKind { - #[error("Annotation missing target statement.")] - #[diagnostic(code("Qsc.Qasm3.Compile.AnnotationWithoutStatement"))] - AnnotationWithoutStatement(#[label] Span), - #[error("Cannot alias type {0}. Only qubit and qubit[] can be aliased.")] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotAliasType"))] - CannotAliasType(String, Span), - #[error("Cannot apply operator {0} to types {1} and {2}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotApplyOperatorToTypes"))] - CannotApplyOperatorToTypes(String, String, String, #[label] Span), - #[error("Cannot assign a value of {0} type to a classical variable of {1} type.")] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotAssignToType"))] - CannotAssignToType(String, String, #[label] Span), - #[error("Cannot call a gate that is not a gate.")] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotCallNonGate"))] - CannotCallNonGate(#[label] Span), - #[error("Cannot cast expression of type {0} to type {1}")] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotCast"))] - CannotCast(String, String, #[label] Span), - #[error("Cannot index variables of type {0}")] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotIndexType"))] - CannotIndexType(String, #[label] Span), - #[error("Cannot update const variable {0}")] - #[diagnostic(help("mutable variables must be declared without the keyword `const`."))] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotUpdateConstVariable"))] - CannotUpdateConstVariable(String, #[label] Span), - #[error("Cannot cast expression of type {0} to type {1} as it would cause truncation.")] - #[diagnostic(code("Qsc.Qasm3.Compile.CastWouldCauseTruncation"))] - CastWouldCauseTruncation(String, String, #[label] Span), - #[error("Complex numbers in assignment binary expressions are not yet supported.")] - #[diagnostic(code("Qsc.Qasm3.Compile.ComplexBinaryAssignment"))] - ComplexBinaryAssignment(#[label] Span), - #[error("Designator must be a literal integer.")] - #[diagnostic(code("Qsc.Qasm3.Compile.DesignatorMustBeIntLiteral"))] - DesignatorMustBeIntLiteral(#[label] Span), - #[error("Failed to compile all expressions in expression list.")] - #[diagnostic(code("Qsc.Qasm3.Compile.FailedToCompileExpressionList"))] - FailedToCompileExpressionList(#[label] Span), - #[error("For iterable must have a set expression, range expression, or iterable expression.")] - #[diagnostic(code("Qsc.Qasm3.Compile.ForIterableInvalidExpression"))] - ForIterableInvalidExpression(#[label] Span), - #[error("For statements must have a body or statement.")] - #[diagnostic(code("Qsc.Qasm3.Compile.ForStatementsMustHaveABodyOrStatement"))] - ForStatementsMustHaveABodyOrStatement(#[label] Span), - #[error("If statement missing {0} expression.")] - #[diagnostic(code("Qsc.Qasm3.Compile.IfStmtMissingExpression"))] - IfStmtMissingExpression(String, #[label] Span), - #[error("include {0} could not be found.")] - #[diagnostic(code("Qsc.Qasm3.Compile.IncludeNotFound"))] - IncludeNotFound(String, #[label] Span), - #[error("include {0} must be declared in global scope.")] - #[diagnostic(code("Qsc.Qasm3.Compile.IncludeNotInGlobalScope"))] - IncludeNotInGlobalScope(String, #[label] Span), - #[error("include {0} must be declared in global scope.")] - #[diagnostic(code("Qsc.Qasm3.Compile.IncludeStatementMissingPath"))] - IncludeStatementMissingPath(#[label] Span), - #[error("Indexed must be a single expression.")] - #[diagnostic(code("Qsc.Qasm3.Compile.IndexMustBeSingleExpr"))] - IndexMustBeSingleExpr(#[label] Span), - #[error("Annotations only valid on gate definitions.")] - #[diagnostic(code("Qsc.Qasm3.Compile.InvalidAnnotationTarget"))] - InvalidAnnotationTarget(Span), - #[error("Assigning {0} values to {1} must be in a range that be converted to {1}.")] - InvalidCastValueRange(String, String, #[label] Span), - #[error("Gate operands other than qubits or qubit arrays are not supported.")] - #[diagnostic(code("Qsc.Qasm3.Compile.InvalidGateOperand"))] - InvalidGateOperand(#[label] Span), - #[error("Control counts must be integer literals.")] - #[diagnostic(code("Qsc.Qasm3.Compile.InvalidControlCount"))] - InvalidControlCount(#[label] Span), - #[error("Gate operands other than qubit arrays are not supported.")] - #[diagnostic(code("Qsc.Qasm3.Compile.InvalidIndexedGateOperand"))] - InvalidIndexedGateOperand(#[label] Span), - #[error("Gate expects {0} classical arguments, but {1} were provided.")] - #[diagnostic(code("Qsc.Qasm3.Compile.InvalidNumberOfClassicalArgs"))] - InvalidNumberOfClassicalArgs(usize, usize, #[label] Span), - #[error("Gate expects {0} qubit arguments, but {1} were provided.")] - #[diagnostic(code("Qsc.Qasm3.Compile.InvalidNumberOfQubitArgs"))] - InvalidNumberOfQubitArgs(usize, usize, #[label] Span), - #[error("Measure statements must have a name.")] - #[diagnostic(code("Qsc.Qasm3.Compile.MeasureExpressionsMustHaveName"))] - MeasureExpressionsMustHaveName(#[label] Span), - #[error("Measure statements must have a gate operand name.")] - #[diagnostic(code("Qsc.Qasm3.Compile.MeasureExpressionsMustHaveGateOperand"))] - MeasureExpressionsMustHaveGateOperand(#[label] Span), - #[error("Control counts must be postitive integers.")] - #[diagnostic(code("Qsc.Qasm3.Compile.NegativeControlCount"))] - NegativeControlCount(#[label] Span), - #[error("The operator {0} is not valid with lhs {1} and rhs {2}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.OperatorNotSupportedForTypes"))] - OperatorNotSupportedForTypes(String, String, String, #[label] Span), - #[error("Pow gate modifiers must have an exponent.")] - #[diagnostic(code("Qsc.Qasm3.Compile.PowModifierMustHaveExponent"))] - PowModifierMustHaveExponent(#[label] Span), - #[error("Qiskit circuits must have output registers.")] - #[diagnostic(code("Qsc.Qasm3.Compile.QiskitEntryPointMissingOutput"))] - QiskitEntryPointMissingOutput(#[label] Span), - #[error("Quantum declarations must be done in global scope.")] - #[diagnostic(code("Qsc.Qasm3.Compile.QuantumDeclarationInNonGlobalScope"))] - QuantumDeclarationInNonGlobalScope(#[label] Span), - #[error("Quantum typed values cannot be used in binary expressions.")] - #[diagnostic(code("Qsc.Qasm3.Compile.QuantumTypesInBinaryExpression"))] - QuantumTypesInBinaryExpression(#[label] Span), - #[error("Range expressions must have a start.")] - #[diagnostic(code("Qsc.Qasm3.Compile.RangeExpressionsMustHaveStart"))] - RangeExpressionsMustHaveStart(#[label] Span), - #[error("Range expressions must have a stop.")] - #[diagnostic(code("Qsc.Qasm3.Compile.RangeExpressionsMustHaveStop"))] - RangeExpressionsMustHaveStop(#[label] Span), - #[error("Redefined symbol: {0}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.RedefinedSymbol"))] - RedefinedSymbol(String, #[label] Span), - #[error("Reset expression must have a gate operand.")] - #[diagnostic(code("Qsc.Qasm3.Compile.ResetExpressionMustHaveGateOperand"))] - ResetExpressionMustHaveGateOperand(#[label] Span), - #[error("Reset expression must have a name.")] - #[diagnostic(code("Qsc.Qasm3.Compile.ResetExpressionMustHaveName"))] - ResetExpressionMustHaveName(#[label] Span), - #[error("Return statements are only allowed within subroutines.")] - #[diagnostic(code("Qsc.Qasm3.Compile.ReturnNotInSubroutine"))] - ReturnNotInSubroutine(#[label] Span), - #[error("Too many controls specified.")] - #[diagnostic(code("Qsc.Qasm3.Compile.TooManyControls"))] - TooManyControls(#[label] Span), - #[error("Types differ by dimensions and are incompatible.")] - #[diagnostic(code("Qsc.Qasm3.Compile.TypeRankError"))] - TypeRankError(#[label] Span), - #[error("Undefined symbol: {0}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.UndefinedSymbol"))] - UndefinedSymbol(String, #[label] Span), - #[error("Unexpected parser error: {0}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.UnexpectedParserError"))] - UnexpectedParserError(String, #[label] Span), - #[error("Unexpected annotation: {0}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.UnknownAnnotation"))] - UnknownAnnotation(String, #[label] Span), - #[error("Undefined symbol: {0}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.UnknownIndexedOperatorKind"))] - UnknownIndexedOperatorKind(#[label] Span), - #[error("While statement missing {0} expression.")] - #[diagnostic(code("Qsc.Qasm3.Compile.WhileStmtMissingExpression"))] - WhileStmtMissingExpression(String, Span), -} - -impl SemanticErrorKind { - /// The semantic errors are reported with the span of the syntax that caused the error. - /// This offset is relative to the start of the file in which the error occurred. - /// This method is used to adjust the span of the error to be relative to where the - /// error was reported in the entire compilation unit as part of the source map. - #[allow(clippy::too_many_lines)] - fn with_offset(self, offset: u32) -> Self { - match self { - Self::AnnotationWithoutStatement(span) => { - Self::AnnotationWithoutStatement(span + offset) - } - Self::CannotCast(lhs, rhs, span) => Self::CannotCast(lhs, rhs, span + offset), - Self::CastWouldCauseTruncation(lhs, rhs, span) => { - Self::CastWouldCauseTruncation(lhs, rhs, span + offset) - } - Self::CannotAliasType(name, span) => Self::CannotAliasType(name, span + offset), - Self::CannotApplyOperatorToTypes(op, lhs, rhs, span) => { - Self::CannotApplyOperatorToTypes(op, lhs, rhs, span + offset) - } - Self::CannotAssignToType(lhs, rhs, span) => { - Self::CannotAssignToType(lhs, rhs, span + offset) - } - Self::CannotCallNonGate(span) => Self::CannotCallNonGate(span + offset), - Self::CannotIndexType(name, span) => Self::CannotIndexType(name, span + offset), - Self::CannotUpdateConstVariable(name, span) => { - Self::CannotUpdateConstVariable(name, span + offset) - } - Self::ComplexBinaryAssignment(span) => Self::ComplexBinaryAssignment(span + offset), - Self::DesignatorMustBeIntLiteral(span) => { - Self::DesignatorMustBeIntLiteral(span + offset) - } - Self::FailedToCompileExpressionList(span) => { - Self::FailedToCompileExpressionList(span + offset) - } - Self::ForIterableInvalidExpression(span) => { - Self::ForIterableInvalidExpression(span + offset) - } - Self::ForStatementsMustHaveABodyOrStatement(span) => { - Self::ForStatementsMustHaveABodyOrStatement(span + offset) - } - Self::IfStmtMissingExpression(name, span) => { - Self::IfStmtMissingExpression(name, span + offset) - } - Self::IncludeNotFound(name, span) => Self::IncludeNotFound(name, span + offset), - Self::IncludeNotInGlobalScope(name, span) => { - Self::IncludeNotInGlobalScope(name, span + offset) - } - Self::IncludeStatementMissingPath(span) => { - Self::IncludeStatementMissingPath(span + offset) - } - Self::IndexMustBeSingleExpr(span) => Self::IndexMustBeSingleExpr(span + offset), - Self::InvalidAnnotationTarget(span) => Self::InvalidAnnotationTarget(span + offset), - Self::InvalidControlCount(span) => Self::InvalidControlCount(span + offset), - Self::InvalidNumberOfClassicalArgs(expected, actual, span) => { - Self::InvalidNumberOfClassicalArgs(expected, actual, span + offset) - } - Self::InvalidNumberOfQubitArgs(expected, actual, span) => { - Self::InvalidNumberOfQubitArgs(expected, actual, span + offset) - } - Self::InvalidCastValueRange(lhs, rhs, span) => { - Self::InvalidCastValueRange(lhs, rhs, span + offset) - } - Self::InvalidGateOperand(span) => Self::InvalidGateOperand(span + offset), - Self::InvalidIndexedGateOperand(span) => Self::InvalidIndexedGateOperand(span + offset), - Self::MeasureExpressionsMustHaveGateOperand(span) => { - Self::MeasureExpressionsMustHaveGateOperand(span + offset) - } - Self::MeasureExpressionsMustHaveName(span) => { - Self::MeasureExpressionsMustHaveName(span + offset) - } - Self::NegativeControlCount(span) => Self::NegativeControlCount(span + offset), - Self::OperatorNotSupportedForTypes(op, lhs, rhs, span) => { - Self::OperatorNotSupportedForTypes(op, lhs, rhs, span + offset) - } - Self::PowModifierMustHaveExponent(span) => { - Self::PowModifierMustHaveExponent(span + offset) - } - Self::QiskitEntryPointMissingOutput(span) => { - Self::QiskitEntryPointMissingOutput(span + offset) - } - Self::QuantumDeclarationInNonGlobalScope(span) => { - Self::QuantumDeclarationInNonGlobalScope(span + offset) - } - Self::QuantumTypesInBinaryExpression(span) => { - Self::QuantumTypesInBinaryExpression(span + offset) - } - Self::RangeExpressionsMustHaveStart(span) => { - Self::RangeExpressionsMustHaveStart(span + offset) - } - Self::RangeExpressionsMustHaveStop(span) => { - Self::RangeExpressionsMustHaveStop(span + offset) - } - Self::RedefinedSymbol(name, span) => Self::RedefinedSymbol(name, span + offset), - Self::ResetExpressionMustHaveGateOperand(span) => { - Self::ResetExpressionMustHaveGateOperand(span + offset) - } - Self::ResetExpressionMustHaveName(span) => { - Self::ResetExpressionMustHaveName(span + offset) - } - Self::ReturnNotInSubroutine(span) => Self::ReturnNotInSubroutine(span + offset), - Self::TooManyControls(span) => Self::TooManyControls(span + offset), - Self::TypeRankError(span) => Self::TypeRankError(span + offset), - Self::UndefinedSymbol(name, span) => Self::UndefinedSymbol(name, span + offset), - Self::UnexpectedParserError(error, span) => { - Self::UnexpectedParserError(error, span + offset) - } - Self::UnknownAnnotation(name, span) => Self::UnknownAnnotation(name, span + offset), - Self::UnknownIndexedOperatorKind(span) => { - Self::UnknownIndexedOperatorKind(span + offset) - } - Self::WhileStmtMissingExpression(name, span) => { - Self::WhileStmtMissingExpression(name, span + offset) - } - } - } -} - -/// Qubit semantics differ between Q# and Qiskit. This enum is used to -/// specify which semantics to use when compiling QASM to Q#. -/// -/// Q# requires qubits to be in the 0 state before and after use. -/// Qiskit makes no assumptions about the state of qubits before or after use. -/// -/// During compliation, if Qiskit semantics are used, the compiler will insert -/// calls to create qubits instead of `use` bindings. This means that later -/// compiler passes won't generate the Q# code that would check the qubits. -/// -/// If Q# semantics are used, the compiler will insert `use` bindings. -/// -/// The Qiskit semantics can also be useful if we ever want to do state -/// vector simulation as it will allow us to get the simulator state at -/// the end of the program. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum QubitSemantics { - QSharp, - Qiskit, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CompilerConfig { - pub qubit_semantics: QubitSemantics, - pub output_semantics: OutputSemantics, - pub program_ty: ProgramType, - operation_name: Option>, - namespace: Option>, -} - -impl CompilerConfig { - #[must_use] - pub fn new( - qubit_semantics: QubitSemantics, - output_semantics: OutputSemantics, - program_ty: ProgramType, - operation_name: Option>, - namespace: Option>, - ) -> Self { - Self { - qubit_semantics, - output_semantics, - program_ty, - operation_name, - namespace, - } - } - - fn operation_name(&self) -> Arc { - self.operation_name - .clone() - .unwrap_or_else(|| Arc::from("program")) - } - - fn namespace(&self) -> Arc { - self.namespace - .clone() - .unwrap_or_else(|| Arc::from("qasm3_import")) - } -} - -impl Default for CompilerConfig { - fn default() -> Self { - Self { - qubit_semantics: QubitSemantics::Qiskit, - output_semantics: OutputSemantics::Qiskit, - program_ty: ProgramType::Fragments, - operation_name: None, - namespace: None, - } - } -} - -/// Represents the type of compilation output to create -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ProgramType { - /// Creates an operation in a namespace as if the program is a standalone - /// file. Inputs are lifted to the operation params. Output are lifted to - /// the operation return type. The operation is marked as `@EntryPoint` - /// as long as there are no input parameters. - File, - /// Programs are compiled to a standalone function. Inputs are lifted to - /// the operation params. Output are lifted to the operation return type. - Operation, - /// Creates a list of statements from the program. This is useful for - /// interactive environments where the program is a list of statements - /// imported into the current scope. - /// This is also useful for testing individual statements compilation. - Fragments, -} - -/// Represents the signature of an operation. -/// This is used to create a function signature for the -/// operation that is created from the QASM source code. -/// This is the human readable form of the operation. -pub struct OperationSignature { - pub name: String, - pub ns: Option, - pub input: Vec<(String, String)>, - pub output: String, -} - -impl OperationSignature { - /// Creates a human readable operation signature of the form: - /// `ns.name(input)` - /// which is required to call the operation from other code. - #[must_use] - pub fn create_entry_expr_from_params>(&self, params: S) -> String { - let mut expr = String::new(); - if let Some(ns) = &self.ns { - write!(expr, "{ns}.").unwrap(); - } - write!(expr, "{}(", self.name).unwrap(); - write!(expr, "{}", params.as_ref()).unwrap(); - write!(expr, ")").unwrap(); - - expr - } - - /// Renders the input parameters as a string of comma separated - /// pairs. - #[must_use] - pub fn input_params(&self) -> String { - let mut expr = String::new(); - for (i, (name, ty)) in self.input.iter().enumerate() { - if i > 0 { - write!(expr, ", ").unwrap(); - } - write!(expr, "{name}: {ty}").unwrap(); - } - expr - } -} - -impl std::fmt::Display for OperationSignature { - /// Renders the operation signature as a human readable string. - /// The signature is of the form: - /// `ns.name(input) -> output` - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(ns) = &self.ns { - write!(f, "{ns}.")?; - } - write!(f, "{}(", self.name)?; - for (i, (name, ty)) in self.input.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{name}: {ty}")?; - } - write!(f, ") -> {}", self.output) - } -} - -/// A unit of compilation for QASM source code. -/// This is the result of parsing and compiling a QASM source file. -pub struct QasmCompileUnit { - /// Source map created from the accumulated source files, - source_map: SourceMap, - /// Semantic errors encountered during compilation. - /// These are always fatal errors that prevent compilation. - errors: Vec>, - /// The compiled AST package, if compilation was successful. - /// There is no guarantee that this package is valid unless - /// there are no errors. - package: Option, - /// The signature of the operation created from the QASM source code. - /// None if the program type is `ProgramType::Fragments`. - signature: Option, -} - -/// Represents a QASM compilation unit. -/// This is the result of parsing and compiling a QASM source file. -/// The result contains the source map, errors, and the compiled package. -/// The package is only valid if there are no errors. -impl QasmCompileUnit { - #[must_use] - pub fn new( - source_map: SourceMap, - errors: Vec>, - package: Option, - signature: Option, - ) -> Self { - Self { - source_map, - errors, - package, - signature, - } - } - - /// Returns true if there are errors in the compilation unit. - #[must_use] - pub fn has_errors(&self) -> bool { - !self.errors.is_empty() - } - - /// Returns a list of errors in the compilation unit. - #[must_use] - pub fn errors(&self) -> Vec> { - self.errors.clone() - } - - /// Deconstructs the compilation unit into its owned parts. - #[must_use] - pub fn into_tuple( - self, - ) -> ( - SourceMap, - Vec>, - Option, - Option, - ) { - (self.source_map, self.errors, self.package, self.signature) - } -} - -/// Represents the output semantics of the compilation. -/// Each has implications on the output of the compilation -/// and the semantic checks that are performed. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum OutputSemantics { - /// The output is in Qiskit format meaning that the output - /// is all of the classical registers, in reverse order - /// in which they were added to the circuit with each - /// bit within each register in reverse order. - Qiskit, - /// [OpenQASM 3 has two output modes](https://openqasm.com/language/directives.html#input-output) - /// - If the programmer provides one or more `output` declarations, then - /// variables described as outputs will be returned as output. - /// The spec make no mention of endianness or order of the output. - /// - Otherwise, assume all of the declared variables are returned as output. - OpenQasm, - /// No output semantics are applied. The entry point returns `Unit`. - ResourceEstimation, -} diff --git a/compiler/qsc_qasm3/src/oqasm_helpers.rs b/compiler/qsc_qasm3/src/oqasm_helpers.rs deleted file mode 100644 index bbac93ad6e..0000000000 --- a/compiler/qsc_qasm3/src/oqasm_helpers.rs +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use oq3_semantics::types::Type; -use oq3_syntax::ast::{ArithOp, BinaryOp, Designator, Expr, Literal, LiteralKind}; -use qsc_data_structures::span::Span; - -/// Extracts a Q# ```Span``` from the QASM3 syntax named element -pub(crate) fn span_for_named_item(value: &T) -> Span { - let Some(name) = value.name() else { - return Span::default(); - }; - let Some(ident) = name.ident_token() else { - return Span::default(); - }; - text_range_to_span(ident.text_range()) -} - -/// Converts the QASM3 syntax ```TextRange``` to a Q# ```Span``` -pub(crate) fn text_range_to_span(range: oq3_syntax::TextRange) -> Span { - Span { - lo: range.start().into(), - hi: range.end().into(), - } -} - -/// Extracts a Q# ```Span``` from the QASM3 syntax node -pub(crate) fn span_for_syntax_node(node: &oq3_syntax::SyntaxNode) -> Span { - text_range_to_span(node.text_range()) -} - -/// Extracts a Q# ```Span``` from the QASM3 syntax token -pub(crate) fn span_for_syntax_token(token: &oq3_syntax::SyntaxToken) -> Span { - text_range_to_span(token.text_range()) -} - -/// The QASM3 parser stores integers as u128, any conversion we do -/// must not crash if the value is too large to fit in the target type -/// and instead return None. -/// Safe in the following functions means that the conversion will not -/// panic if the value is too large to fit in the target type. -/// -/// Values may be truncated or rounded as necessary. -pub(crate) fn safe_u128_to_f64(value: u128) -> Option { - if value <= u128::from(i64::MAX as u64) { - let value = i64::try_from(value).ok()?; - safe_i64_to_f64(value) - } else { - None - } -} - -pub(crate) fn safe_i64_to_f64(value: i64) -> Option { - #[allow(clippy::cast_possible_truncation)] - if value <= f64::MAX as i64 { - #[allow(clippy::cast_precision_loss)] - Some(value as f64) - } else { - None - } -} - -pub(crate) fn safe_u64_to_f64(value: u64) -> Option { - #[allow(clippy::cast_possible_truncation)] - #[allow(clippy::cast_sign_loss)] - if value <= f64::MAX as u64 { - #[allow(clippy::cast_precision_loss)] - Some(value as f64) - } else { - None - } -} - -/// The spec defines a designator as ```designator: LBRACKET expression RBRACKET;``` -/// However, in every use case, the expression is a literal integer. -/// This isn't enforced by the parser/grammar, but we can assume it here. -/// For example, when describing qubit arrays, the spec says: -/// - The label ```name[j]``` refers to a qubit of this register, where -/// ```j element_of {0, 1, ... size(name)-1}``` is an integer. -pub(crate) fn extract_dims_from_designator(designator: Option) -> Option { - let designator = designator?; - match designator.expr() { - Some(Expr::Literal(lit)) => match lit.kind() { - LiteralKind::IntNumber(int) => { - // qasm parser stores ints as u128 - let value = int.value().expect("Designator must be a literal integer"); - let value: u32 = u32::try_from(value).expect("Designator must fit in u32"); - Some(value) - } - _ => { - unreachable!("designator must be a literal integer") - } - }, - None => None, - _ => { - unreachable!("designator must be a literal integer") - } - } -} - -/// The designator must be accessed differently depending on the type. -/// For complex types, the designator is stored in the scalar type. -/// For other types, the designator is stored in the type itself. -pub(crate) fn get_designator_from_scalar_type( - ty: &oq3_syntax::ast::ScalarType, -) -> Option { - if let Some(scalar_ty) = ty.scalar_type() { - // we have a complex type, need to grab the inner - // designator for the width - scalar_ty.designator() - } else { - ty.designator() - } -} - -/// Symmetric arithmetic conversions are applied to: -/// binary arithmetic *, /, %, +, - -/// relational operators <, >, <=, >=, ==, != -/// binary bitwise arithmetic &, ^, |, -pub(crate) fn requires_symmetric_conversion(op: BinaryOp) -> bool { - match op { - BinaryOp::LogicOp(_) | BinaryOp::CmpOp(_) => true, - BinaryOp::ArithOp(arith_op) => match arith_op { - ArithOp::Mul - | ArithOp::Div - | ArithOp::Rem - | ArithOp::Add - | ArithOp::Sub - | ArithOp::BitAnd - | ArithOp::BitXor - | ArithOp::BitOr => true, - ArithOp::Shl | ArithOp::Shr => false, - }, - #[allow(clippy::match_same_arms)] - BinaryOp::ConcatenationOp => { - // concatenation is a special case where we can't have a symmetric conversion - // as the lhs and rhs must be of the same type - false - } - BinaryOp::Assignment { op: _ } => false, - } -} - -pub(crate) fn requires_types_already_match_conversion(op: BinaryOp) -> bool { - match op { - BinaryOp::ConcatenationOp => { - // concatenation is a special case where we can't have a symmetric conversion - // as the lhs and rhs must be of the same type - true - } - _ => false, - } -} - -// integer promotions are applied only to both operands of -// the shift operators << and >> -pub(crate) fn binop_requires_symmetric_int_conversion(op: BinaryOp) -> bool { - match op { - BinaryOp::ArithOp(arith_op) => matches!(arith_op, ArithOp::Shl | ArithOp::Shr), - BinaryOp::Assignment { op } => matches!(op, Some(ArithOp::Shl | ArithOp::Shr)), - _ => false, - } -} - -/// some literals can be cast to a specific type if the value is known -/// This is useful to avoid generating a cast expression in the AST -pub(crate) fn can_cast_literal_with_value_knowledge(lhs_ty: &Type, literal: &Literal) -> bool { - if matches!(lhs_ty, &Type::Bit(..)) { - if let LiteralKind::IntNumber(value) = literal.kind() { - let value = value.value().expect("IntNumber must have a value"); - return value == 0 || value == 1; - } - } - if matches!(lhs_ty, &Type::UInt(..)) { - if let LiteralKind::IntNumber(_) = literal.kind() { - // Value can't be negative as IntNumber is unsigned - // any sign would come from a prefix expression - return true; - } - } - false -} diff --git a/compiler/qsc_qasm3/src/oqasm_types.rs b/compiler/qsc_qasm3/src/oqasm_types.rs deleted file mode 100644 index a2475b246d..0000000000 --- a/compiler/qsc_qasm3/src/oqasm_types.rs +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use std::cmp::max; - -use oq3_semantics::types::{ArrayDims, IsConst, Type}; - -/// When two types are combined, the result is a type that can represent both. -/// For constness, the result is const iff both types are const. -fn relax_constness(lhs_ty: &Type, rhs_ty: &Type) -> IsConst { - IsConst::from(lhs_ty.is_const() && rhs_ty.is_const()) -} - -/// Having no width means that the type is not a fixed-width type -/// and can hold any explicit width. If both types have a width, -/// the result is the maximum of the two. Otherwise, the result -/// is a type without a width. -fn promote_width(lhs_ty: &Type, rhs_ty: &Type) -> Option { - match (lhs_ty.width(), rhs_ty.width()) { - (Some(w1), Some(w2)) => Some(max(w1, w2)), - (Some(_) | None, None) | (None, Some(_)) => None, - } -} - -fn get_effective_width(lhs_ty: &Type, rhs_ty: &Type) -> Option { - match (lhs_ty.width(), rhs_ty.width()) { - (Some(w1), Some(w2)) => Some(max(w1, w2)), - (Some(w), None) | (None, Some(w)) => Some(w), - (None, None) => None, - } -} - -/// If both can be promoted to a common type, the result is that type. -/// If the types are not compatible, the result is `Type::Void`. -pub fn promote_types(lhs_ty: &Type, rhs_ty: &Type) -> Type { - if types_equal_except_const(lhs_ty, rhs_ty) { - return lhs_ty.clone(); - } - let ty = promote_types_symmetric(lhs_ty, rhs_ty); - if ty != Type::Void { - return ty; - } - let ty = promote_types_asymmetric(lhs_ty, rhs_ty); - if ty == Type::Void { - return promote_types_asymmetric(rhs_ty, lhs_ty); - } - ty -} - -pub(crate) fn promote_to_uint_ty( - lhs_ty: &Type, - rhs_ty: &Type, -) -> (Option, Option, Option) { - let is_const = relax_constness(lhs_ty, rhs_ty); - let lhs_ty = get_uint_ty(lhs_ty); - let rhs_ty = get_uint_ty(rhs_ty); - match (lhs_ty, rhs_ty) { - (Some(lhs_ty), Some(rhs_ty)) => { - let width = get_effective_width(&lhs_ty, &rhs_ty); - ( - Some(Type::UInt(width, is_const)), - Some(lhs_ty), - Some(rhs_ty), - ) - } - (Some(lhs_ty), None) => (None, Some(lhs_ty), None), - (None, Some(rhs_ty)) => (None, None, Some(rhs_ty)), - (None, None) => (None, None, None), - } -} - -fn get_uint_ty(ty: &Type) -> Option { - if matches!(ty, Type::UInt(..) | Type::Angle(..)) { - Some(Type::UInt(ty.width(), ty.is_const().into())) - } else if let Type::BitArray(dims, _) = ty { - match dims { - ArrayDims::D1(d) => Some(Type::UInt( - Some(u32::try_from(*d).ok()?), - ty.is_const().into(), - )), - _ => None, - } - } else { - None - } -} - -/// Promotes two types if they share a common base type with -/// their constness relaxed, and their width promoted. -/// If the types are not compatible, the result is `Type::Void`. -fn promote_types_symmetric(lhs_ty: &Type, rhs_ty: &Type) -> Type { - let is_const = relax_constness(lhs_ty, rhs_ty); - match (lhs_ty, rhs_ty) { - (Type::Bit(..), Type::Bit(..)) => Type::Bit(is_const), - (Type::Bool(..), Type::Bool(..)) => Type::Bool(is_const), - (Type::Int(..), Type::Int(..)) => Type::Int(promote_width(lhs_ty, rhs_ty), is_const), - (Type::UInt(..), Type::UInt(..)) => Type::UInt(promote_width(lhs_ty, rhs_ty), is_const), - (Type::Angle(..), Type::Angle(..)) => Type::Angle(promote_width(lhs_ty, rhs_ty), is_const), - (Type::Float(..), Type::Float(..)) => Type::Float(promote_width(lhs_ty, rhs_ty), is_const), - (Type::Complex(..), Type::Complex(..)) => { - Type::Complex(promote_width(lhs_ty, rhs_ty), is_const) - } - _ => Type::Void, - } -} - -/// Promotion follows casting rules. We only match one way, as the -/// both combinations are covered by calling this function twice -/// with the arguments swapped. -/// -/// If the types are not compatible, the result is `Type::Void`. -/// -/// The left-hand side is the type to promote from, and the right-hand -/// side is the type to promote to. So any promotion goes from lesser -/// type to greater type. -/// -/// This is more complicated as we have C99 promotion for simple types, -/// but complex types like `Complex`, and `Angle` don't follow those rules. -fn promote_types_asymmetric(lhs_ty: &Type, rhs_ty: &Type) -> Type { - let is_const = relax_constness(lhs_ty, rhs_ty); - #[allow(clippy::match_same_arms)] - match (lhs_ty, rhs_ty) { - (Type::Bit(..), Type::Bool(..)) => Type::Bool(is_const), - (Type::Bit(..), Type::Int(w, _)) => Type::Int(*w, is_const), - (Type::Bit(..), Type::UInt(w, _)) => Type::UInt(*w, is_const), - - (Type::Bit(..), Type::Angle(w, _)) => Type::Angle(*w, is_const), - - (Type::Bool(..), Type::Int(w, _)) => Type::Int(*w, is_const), - (Type::Bool(..), Type::UInt(w, _)) => Type::UInt(*w, is_const), - (Type::Bool(..), Type::Float(w, _)) => Type::Float(*w, is_const), - (Type::Bool(..), Type::Complex(w, _)) => Type::Complex(*w, is_const), - - (Type::UInt(..), Type::Int(..)) => Type::Int(promote_width(lhs_ty, rhs_ty), is_const), - (Type::UInt(..), Type::Float(..)) => Type::Float(promote_width(lhs_ty, rhs_ty), is_const), - (Type::UInt(..), Type::Complex(..)) => { - Type::Complex(promote_width(lhs_ty, rhs_ty), is_const) - } - - (Type::Int(..), Type::Float(..)) => Type::Float(promote_width(lhs_ty, rhs_ty), is_const), - (Type::Int(..), Type::Complex(..)) => { - Type::Complex(promote_width(lhs_ty, rhs_ty), is_const) - } - (Type::Angle(..), Type::Float(..)) => Type::Float(promote_width(lhs_ty, rhs_ty), is_const), - (Type::Float(..), Type::Complex(..)) => { - Type::Complex(promote_width(lhs_ty, rhs_ty), is_const) - } - _ => Type::Void, - } -} - -/// Compares two types for equality, ignoring constness. -pub(crate) fn types_equal_except_const(lhs: &Type, rhs: &Type) -> bool { - match (lhs, rhs) { - (Type::Bit(_), Type::Bit(_)) - | (Type::Qubit, Type::Qubit) - | (Type::HardwareQubit, Type::HardwareQubit) - | (Type::Bool(_), Type::Bool(_)) - | (Type::Duration(_), Type::Duration(_)) - | (Type::Stretch(_), Type::Stretch(_)) - | (Type::Range, Type::Range) - | (Type::Set, Type::Set) - | (Type::Void, Type::Void) - | (Type::ToDo, Type::ToDo) - | (Type::Undefined, Type::Undefined) => true, - (Type::Int(lhs_width, _), Type::Int(rhs_width, _)) - | (Type::UInt(lhs_width, _), Type::UInt(rhs_width, _)) - | (Type::Float(lhs_width, _), Type::Float(rhs_width, _)) - | (Type::Angle(lhs_width, _), Type::Angle(rhs_width, _)) - | (Type::Complex(lhs_width, _), Type::Complex(rhs_width, _)) => lhs_width == rhs_width, - (Type::BitArray(lhs_dims, _), Type::BitArray(rhs_dims, _)) - | (Type::QubitArray(lhs_dims), Type::QubitArray(rhs_dims)) - | (Type::IntArray(lhs_dims), Type::IntArray(rhs_dims)) - | (Type::UIntArray(lhs_dims), Type::UIntArray(rhs_dims)) - | (Type::FloatArray(lhs_dims), Type::FloatArray(rhs_dims)) - | (Type::AngleArray(lhs_dims), Type::AngleArray(rhs_dims)) - | (Type::ComplexArray(lhs_dims), Type::ComplexArray(rhs_dims)) - | (Type::BoolArray(lhs_dims), Type::BoolArray(rhs_dims)) => lhs_dims == rhs_dims, - (Type::Gate(lhs_cargs, lhs_qargs), Type::Gate(rhs_cargs, rhs_qargs)) => { - lhs_cargs == rhs_cargs && lhs_qargs == rhs_qargs - } - _ => false, - } -} diff --git a/compiler/qsc_qasm3/src/parse.rs b/compiler/qsc_qasm3/src/parse.rs deleted file mode 100644 index be6c74ff1d..0000000000 --- a/compiler/qsc_qasm3/src/parse.rs +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::io::SourceResolver; -use crate::oqasm_helpers::text_range_to_span; -use oq3_syntax::SyntaxNode; -use oq3_syntax::{ast::Stmt, ParseOrErrors, SourceFile}; -use qsc_frontend::compile::SourceMap; -use qsc_frontend::error::WithSource; -use std::path::{Path, PathBuf}; -use std::sync::Arc; - -#[cfg(test)] -pub(crate) mod tests; - -pub struct QasmParseResult { - pub source: QasmSource, - pub source_map: SourceMap, -} - -impl QasmParseResult { - #[must_use] - pub fn new(source: QasmSource) -> QasmParseResult { - let source_map = create_source_map(&source); - QasmParseResult { source, source_map } - } - - #[must_use] - pub fn has_errors(&self) -> bool { - self.source.has_errors() - } - - pub fn all_errors(&self) -> Vec> { - let mut self_errors = self.errors(); - let include_errors = self - .source - .includes() - .iter() - .flat_map(QasmSource::all_errors) - .map(|e| self.map_error(e)); - - self_errors.extend(include_errors); - self_errors - } - - #[must_use] - pub fn errors(&self) -> Vec> { - self.source - .errors() - .iter() - .map(|e| self.map_error(e.clone())) - .collect() - } - - fn map_error(&self, error: crate::Error) -> WithSource { - let path = self.source.path().display().to_string(); - let source = self.source_map.find_by_name(&path); - let offset = source.map_or(0, |source| source.offset); - - let offset_error = error.with_offset(offset); - - WithSource::from_map(&self.source_map, offset_error) - } -} - -/// Parse a QASM file and return the parse result. -/// This function will resolve includes using the provided resolver. -/// If an include file cannot be resolved, an error will be returned. -/// If a file is included recursively, a stack overflow occurs. -pub fn parse_source(source: S, path: P, resolver: &R) -> miette::Result -where - S: AsRef, - P: AsRef, - R: SourceResolver, -{ - let res = parse_qasm_source(source, path, resolver)?; - Ok(QasmParseResult::new(res)) -} - -/// Creates a Q# source map from a QASM parse output. The `QasmSource` -/// has all of the recursive includes resolved with their own source -/// and parse results. -fn create_source_map(source: &QasmSource) -> SourceMap { - let mut files: Vec<(Arc, Arc)> = Vec::new(); - files.push(( - Arc::from(source.path().to_string_lossy().to_string()), - Arc::from(source.source()), - )); - // Collect all source files from the includes, this - // begins the recursive process of collecting all source files. - for include in source.includes() { - collect_source_files(include, &mut files); - } - // Map the main source file to the entry point expression - // This may be incorrect, but it's the best we can do for now. - SourceMap::new(files, Some(Arc::from(source.source()))) -} - -/// Recursively collect all source files from the includes -fn collect_source_files(source: &QasmSource, files: &mut Vec<(Arc, Arc)>) { - files.push(( - Arc::from(source.path().to_string_lossy().to_string()), - Arc::from(source.source()), - )); - for include in source.includes() { - collect_source_files(include, files); - } -} - -/// Represents a QASM source file that has been parsed. -#[derive(Clone, Debug)] -pub struct QasmSource { - /// The path to the source file. This is used for error reporting. - /// This path is just a name, it does not have to exist on disk. - path: PathBuf, - /// The source code of the file. - source: Arc, - /// The parsed AST of the source file or any parse errors. - ast: ParseOrErrors, - /// Any included files that were resolved. - /// Note that this is a recursive structure. - included: Vec, -} - -impl QasmSource { - pub fn new, P: AsRef>( - source: T, - file_path: P, - ast: ParseOrErrors, - included: Vec, - ) -> QasmSource { - QasmSource { - source: source.as_ref().into(), - path: file_path.as_ref().to_owned(), - ast, - included, - } - } - - #[must_use] - pub fn has_errors(&self) -> bool { - if !self.parse_result().errors().is_empty() { - return true; - } - self.includes().iter().any(QasmSource::has_errors) - } - - #[must_use] - pub fn all_errors(&self) -> Vec { - let mut self_errors = self.errors(); - let include_errors = self.includes().iter().flat_map(QasmSource::all_errors); - self_errors.extend(include_errors); - self_errors - } - - #[must_use] - pub fn tree(&self) -> oq3_syntax::SourceFile { - self.parse_result().tree() - } - - #[must_use] - pub fn syntax_node(&self) -> SyntaxNode { - self.parse_result().syntax_node() - } - - #[must_use] - pub fn includes(&self) -> &Vec { - self.included.as_ref() - } - - #[must_use] - pub fn parse_result(&self) -> &ParseOrErrors { - &self.ast - } - - #[must_use] - pub fn path(&self) -> PathBuf { - self.path.clone() - } - - #[must_use] - pub fn errors(&self) -> Vec { - self.parse_result() - .errors() - .iter() - .map(|e| { - crate::Error(crate::ErrorKind::Parse( - e.message().to_string(), - text_range_to_span(e.range()), - )) - }) - .collect() - } - - #[must_use] - pub fn source(&self) -> &str { - self.source.as_ref() - } -} - -/// Parse a QASM file and return the parse result using the provided resolver. -/// Returns `Err` if the resolver cannot resolve the file. -/// Returns `Ok` otherwise. Any parse errors will be included in the result. -/// -/// This function is the start of a recursive process that will resolve all -/// includes in the QASM file. Any includes are parsed as if their contents -/// were defined where the include statement is. -fn parse_qasm_file(path: P, resolver: &R) -> miette::Result -where - P: AsRef, - R: SourceResolver, -{ - let (path, source) = resolver.resolve(&path)?; - parse_qasm_source(source, path, resolver) -} - -fn parse_qasm_source(source: S, path: P, resolver: &R) -> miette::Result -where - S: AsRef, - P: AsRef, - R: SourceResolver, -{ - let (parse_result, includes) = parse_source_and_includes(source.as_ref(), resolver)?; - Ok(QasmSource::new(source, path, parse_result, includes)) -} - -fn parse_source_and_includes, R>( - source: P, - resolver: &R, -) -> miette::Result<(ParseOrErrors, Vec)> -where - R: SourceResolver, -{ - let parse_result = oq3_syntax::SourceFile::parse_check_lex(source.as_ref()); - if parse_result.have_parse() { - let included = parse_includes(&parse_result, resolver)?; - Ok((parse_result, included)) - } else { - Ok((parse_result, vec![])) - } -} - -fn parse_includes( - syntax_ast: &ParseOrErrors, - resolver: &R, -) -> miette::Result> -where - R: SourceResolver, -{ - let mut includes = vec![]; - for stmt in syntax_ast.tree().statements() { - if let Stmt::Include(include) = stmt { - if let Some(file) = include.file() { - if let Some(file_path) = file.to_string() { - // Skip the standard gates include file. - // Handling of this file is done by the compiler. - if file_path.to_lowercase() == "stdgates.inc" { - continue; - } - let source = parse_qasm_file(file_path, resolver)?; - includes.push(source); - } - } - } - } - - Ok(includes) -} diff --git a/compiler/qsc_qasm3/src/parse/tests.rs b/compiler/qsc_qasm3/src/parse/tests.rs deleted file mode 100644 index 7b22082e6e..0000000000 --- a/compiler/qsc_qasm3/src/parse/tests.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::tests::{parse, parse_all}; -use miette::Report; - -#[test] -fn simple_programs_can_be_parsed() -> miette::Result<(), Vec> { - let source = r#"OPENQASM 3.0; - include "stdgates.inc"; - qubit q; - "#; - let _ = parse(source)?; - Ok(()) -} - -#[test] -fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { - let source0 = r#"OPENQASM 3.0; - include "stdgates.inc"; - include "source1.qasm"; - qubit q1; - "#; - let source1 = "qubit q2; - "; - let all_sources = [ - ("source0.qasm".into(), source0.into()), - ("source1.qasm".into(), source1.into()), - ]; - - let res = parse_all("source0.qasm", all_sources)?; - assert!(res.source.includes().len() == 1); - Ok(()) -} - -#[test] -fn programs_with_includes_with_includes_can_be_parsed() -> miette::Result<(), Vec> { - let source0 = r#"OPENQASM 3.0; - include "stdgates.inc"; - include "source1.qasm"; - qubit q1; - "#; - let source1 = r#"include "source2.qasm"; - qubit q2; - "#; - let source2 = "qubit q3; - "; - let all_sources = [ - ("source0.qasm".into(), source0.into()), - ("source1.qasm".into(), source1.into()), - ("source2.qasm".into(), source2.into()), - ]; - - let res = parse_all("source0.qasm", all_sources)?; - assert!(res.source.includes().len() == 1); - assert!(res.source.includes()[0].includes().len() == 1); - Ok(()) -} diff --git a/compiler/qsc_qasm3/src/runtime.rs b/compiler/qsc_qasm3/src/runtime.rs deleted file mode 100644 index 2b0080d5fd..0000000000 --- a/compiler/qsc_qasm3/src/runtime.rs +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use bitflags::bitflags; - -use qsc_ast::ast::{Stmt, TopLevelNode}; -use qsc_data_structures::language_features::LanguageFeatures; - -/// Runtime functions that are used in the generated AST. -/// These functions are not part of the QASM3 standard, but are used to implement -/// utility fuctions that would be cumbersome to implement building the AST -/// directly. -/// -/// The POW function is used to implement the `pow` modifier in QASM3 for integers. -const POW: &str = " -operation __Pow__<'T>(N: Int, op: ('T => Unit is Adj), target : 'T) : Unit is Adj { - let op = if N > 0 { () => op(target) } else { () => Adjoint op(target) }; - for _ in 1..Microsoft.Quantum.Math.AbsI(N) { - op() - } -} -"; - -/// The ``BARRIER`` function is used to implement the `barrier` statement in QASM3. -/// The `@SimulatableIntrinsic` attribute is used to mark the operation for QIR -/// generation. -/// Q# doesn't support barriers, so this is a no-op. We need to figure out what -/// barriers mean in the context of QIR in the future for better support. -const BARRIER: &str = " -@SimulatableIntrinsic() -operation __quantum__qis__barrier__body() : Unit {} -"; - -/// The ``BOOL_AS_RESULT`` function is used to implement the cast expr in QASM3 for bool to bit. -/// This already exists in the Q# library, but is defined as a marker for casts from QASM3. -const BOOL_AS_RESULT: &str = " -function __BoolAsResult__(input: Bool) : Result { - Microsoft.Quantum.Convert.BoolAsResult(input) -} -"; - -/// The ``BOOL_AS_INT`` function is used to implement the cast expr in QASM3 for bool to int. -const BOOL_AS_INT: &str = " -function __BoolAsInt__(value: Bool) : Int { - if value { - 1 - } else { - 0 - } -} -"; - -/// The ``BOOL_AS_BIGINT`` function is used to implement the cast expr in QASM3 for bool to big int. -const BOOL_AS_BIGINT: &str = " -function __BoolAsBigInt__(value: Bool) : BigInt { - if value { - 1L - } else { - 0L - } -} -"; - -/// The ``BOOL_AS_DOUBLE`` function is used to implement the cast expr in QASM3 for bool to int. -const BOOL_AS_DOUBLE: &str = " -function __BoolAsDouble__(value: Bool) : Double { - if value { - 1. - } else { - 0. - } -} -"; - -/// The ``RESULT_AS_BOOL`` function is used to implement the cast expr in QASM3 for bit to bool. -/// This already exists in the Q# library, but is defined as a marker for casts from QASM3. -const RESULT_AS_BOOL: &str = " -function __ResultAsBool__(input: Result) : Bool { - Microsoft.Quantum.Convert.ResultAsBool(input) -} -"; - -/// The ``RESULT_AS_INT`` function is used to implement the cast expr in QASM3 for bit to bool. -const RESULT_AS_INT: &str = " -function __ResultAsInt__(input: Result) : Int { - if Microsoft.Quantum.Convert.ResultAsBool(input) { - 1 - } else { - 0 - } -} -"; - -/// The ``RESULT_AS_BIGINT`` function is used to implement the cast expr in QASM3 for bit to bool. -const RESULT_AS_BIGINT: &str = " -function __ResultAsBigInt__(input: Result) : BigInt { - if Microsoft.Quantum.Convert.ResultAsBool(input) { - 1L - } else { - 0L - } -} -"; - -/// The ``INT_AS_RESULT_ARRAY_BE`` function is used to implement the cast expr in QASM3 for int to bit[]. -/// with big-endian order. This is needed for round-trip conversion for bin ops. -const INT_AS_RESULT_ARRAY_BE: &str = " -function __IntAsResultArrayBE__(number : Int, bits : Int) : Result[] { - mutable runningValue = number; - mutable result = []; - for _ in 1..bits { - set result += [__BoolAsResult__((runningValue &&& 1) != 0)]; - set runningValue >>>= 1; - } - Microsoft.Quantum.Arrays.Reversed(result) -} -"; - -/// The ``RESULT_ARRAY_AS_INT_BE`` function is used to implement the cast expr in QASM3 for bit[] to uint. -/// with big-endian order. This is needed for round-trip conversion for bin ops. -const RESULT_ARRAY_AS_INT_BE: &str = " -function __ResultArrayAsIntBE__(results : Result[]) : Int { - Microsoft.Quantum.Convert.ResultArrayAsInt(Microsoft.Quantum.Arrays.Reversed(results)) -} -"; - -/// Runtime functions that are used in the generated AST. -/// Once compilation is complete, we can use this to determine -/// which runtime functions need to be included in the AST. -#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Copy)] -pub struct RuntimeFunctions(u16); - -bitflags! { - impl RuntimeFunctions: u16 { - const Pow = 0b1; - const Barrier = 0b10; - const BoolAsResult = 0b100; - const BoolAsInt = 0b1_000; - const BoolAsBigInt = 0b10_000; - const BoolAsDouble = 0b100_000; - const ResultAsBool = 0b1_000_000; - const ResultAsInt = 0b10_000_000; - const ResultAsBigInt = 0b100_000_000; - /// IntAsResultArray requires BoolAsResult to be included. - const IntAsResultArrayBE = 0b1_000_000_000 | 0b100; - const ResultArrayAsIntBE = 0b10_000_000_000; - } -} - -impl Default for RuntimeFunctions { - fn default() -> Self { - RuntimeFunctions::empty() - } -} - -pub(crate) fn get_pow_decl() -> Stmt { - parse_stmt(POW) -} - -pub(crate) fn get_barrier_decl() -> Stmt { - parse_stmt(BARRIER) -} - -pub(crate) fn get_bool_as_result_decl() -> Stmt { - parse_stmt(BOOL_AS_RESULT) -} - -pub(crate) fn get_bool_as_int_decl() -> Stmt { - parse_stmt(BOOL_AS_INT) -} - -pub(crate) fn get_bool_as_bigint_decl() -> Stmt { - parse_stmt(BOOL_AS_BIGINT) -} - -pub(crate) fn get_bool_as_double_decl() -> Stmt { - parse_stmt(BOOL_AS_DOUBLE) -} - -pub(crate) fn get_int_as_result_array_be_decl() -> Stmt { - parse_stmt(INT_AS_RESULT_ARRAY_BE) -} - -pub(crate) fn get_result_as_bool_decl() -> Stmt { - parse_stmt(RESULT_AS_BOOL) -} - -pub(crate) fn get_result_as_bigint_decl() -> Stmt { - parse_stmt(RESULT_AS_BIGINT) -} - -pub(crate) fn get_result_as_int_decl() -> Stmt { - parse_stmt(RESULT_AS_INT) -} - -pub(crate) fn get_result_array_as_int_be_decl() -> Stmt { - parse_stmt(RESULT_ARRAY_AS_INT_BE) -} - -fn parse_stmt(name: &str) -> Stmt { - let (nodes, errors) = qsc_parse::top_level_nodes(name, LanguageFeatures::default()); - assert!(errors.is_empty(), "Failed to parse POW: {errors:?}"); - assert!( - nodes.len() == 1, - "Expected one top-level node, found {:?}", - nodes.len() - ); - match nodes.into_iter().next().expect("no top-level nodes found") { - TopLevelNode::Namespace(..) => { - panic!("Expected operation, got Namespace") - } - TopLevelNode::Stmt(stmt) => *stmt, - } -} - -pub(crate) fn get_runtime_function_decls(runtime: RuntimeFunctions) -> Vec { - let mut stmts = vec![]; - if runtime.contains(RuntimeFunctions::Pow) { - let stmt = crate::runtime::get_pow_decl(); - stmts.push(stmt); - } - if runtime.contains(RuntimeFunctions::Barrier) { - let stmt = crate::runtime::get_barrier_decl(); - stmts.push(stmt); - } - if runtime.contains(RuntimeFunctions::BoolAsBigInt) { - let stmt = crate::runtime::get_bool_as_bigint_decl(); - stmts.push(stmt); - } - if runtime.contains(RuntimeFunctions::BoolAsDouble) { - let stmt = crate::runtime::get_bool_as_double_decl(); - stmts.push(stmt); - } - if runtime.contains(RuntimeFunctions::BoolAsInt) { - let stmt = crate::runtime::get_bool_as_int_decl(); - stmts.push(stmt); - } - if runtime.contains(RuntimeFunctions::BoolAsResult) { - let stmt = crate::runtime::get_bool_as_result_decl(); - stmts.push(stmt); - } - if runtime.contains(RuntimeFunctions::IntAsResultArrayBE) { - let stmt = crate::runtime::get_int_as_result_array_be_decl(); - stmts.push(stmt); - } - if runtime.contains(RuntimeFunctions::ResultAsBool) { - let stmt = crate::runtime::get_result_as_bool_decl(); - stmts.push(stmt); - } - if runtime.contains(RuntimeFunctions::ResultAsBigInt) { - let stmt = crate::runtime::get_result_as_bigint_decl(); - stmts.push(stmt); - } - if runtime.contains(RuntimeFunctions::ResultAsInt) { - let stmt = crate::runtime::get_result_as_int_decl(); - stmts.push(stmt); - } - if runtime.contains(RuntimeFunctions::ResultArrayAsIntBE) { - let stmt = crate::runtime::get_result_array_as_int_be_decl(); - stmts.push(stmt); - } - stmts -} diff --git a/compiler/qsc_qasm3/src/symbols.rs b/compiler/qsc_qasm3/src/symbols.rs deleted file mode 100644 index 679fe0b1a6..0000000000 --- a/compiler/qsc_qasm3/src/symbols.rs +++ /dev/null @@ -1,435 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use oq3_semantics::types::{IsConst, Type}; -use qsc_data_structures::span::Span; -use rustc_hash::FxHashMap; - -/// We need a symbol table to keep track of the symbols in the program. -/// The scoping rules for QASM3 are slightly different from Q#. This also -/// means that we need to keep track of the input and output symbols in the -/// program. Additionally, we need to keep track of the types of the symbols -/// in the program for type checking. -/// Q# and QASM have different type systems, so we track the QASM semantic. -/// -/// A symbol ID is a unique identifier for a symbol in the symbol table. -/// This is used to look up symbols in the symbol table. -/// Every symbol in the symbol table has a unique ID. -#[derive(Debug, Default, Clone, Copy)] -pub struct SymbolId(pub u32); - -impl SymbolId { - /// The successor of this ID. - #[must_use] - pub fn successor(self) -> Self { - Self(self.0 + 1) - } -} - -impl From for SymbolId { - fn from(val: u32) -> Self { - SymbolId(val) - } -} - -impl From for u32 { - fn from(id: SymbolId) -> Self { - id.0 - } -} - -impl From for usize { - fn from(value: SymbolId) -> Self { - value.0 as usize - } -} - -impl From for SymbolId { - fn from(value: usize) -> Self { - SymbolId( - value - .try_into() - .unwrap_or_else(|_| panic!("Value, {value}, does not fit into SymbolId")), - ) - } -} - -impl PartialEq for SymbolId { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl Eq for SymbolId {} - -impl PartialOrd for SymbolId { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for SymbolId { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl std::hash::Hash for SymbolId { - fn hash(&self, state: &mut H) { - self.0.hash(state); - } -} - -impl std::fmt::Display for SymbolId { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Display::fmt(&self.0, f) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct Symbol { - pub name: String, - pub span: Span, - pub ty: Type, - pub qsharp_ty: crate::types::Type, - pub io_kind: IOKind, -} - -/// A symbol in the symbol table. -/// Default Q# type is Unit -impl Default for Symbol { - fn default() -> Self { - Self { - name: String::default(), - span: Span::default(), - ty: Type::Undefined, - qsharp_ty: crate::types::Type::Tuple(vec![]), - io_kind: IOKind::default(), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum SymbolError { - /// The symbol already exists in the symbol table, at the current scope. - AlreadyExists, -} - -/// Symbols have a an I/O kind that determines if they are input or output, or unspecified. -/// The default I/O kind means no explicit kind was part of the decl. -/// There is a specific statement for io decls which sets the I/O kind appropriately. -/// This is used to determine the input and output symbols in the program. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum IOKind { - Default, - Input, - Output, -} - -impl Default for IOKind { - fn default() -> Self { - Self::Default - } -} - -/// A scope is a collection of symbols and a kind. The kind determines semantic -/// rules for compliation as shadowing and decl rules vary by scope kind. -pub struct Scope { - /// A map from symbol name to symbol ID. - name_to_id: FxHashMap, - /// A map from symbol ID to symbol. - id_to_symbol: FxHashMap, - /// The order in which symbols were inserted into the scope. - /// This is used to determine the order of symbols in the output. - order: Vec, - /// The kind of the scope. - kind: ScopeKind, -} - -impl Scope { - pub fn new(kind: ScopeKind) -> Self { - Self { - name_to_id: FxHashMap::default(), - id_to_symbol: FxHashMap::default(), - order: vec![], - kind, - } - } - - /// Inserts the symbol into the current scope. - /// Returns the ID of the symbol. - /// - /// # Errors - /// - /// This function will return an error if a symbol of the same name has already - /// been declared in this scope. - pub fn insert_symbol(&mut self, id: SymbolId, symbol: Symbol) -> Result<(), SymbolError> { - if self.name_to_id.contains_key(&symbol.name) { - return Err(SymbolError::AlreadyExists); - } - self.name_to_id.insert(symbol.name.clone(), id); - self.id_to_symbol.insert(id, symbol); - self.order.push(id); - Ok(()) - } - - pub fn get_symbol_by_name(&self, name: &str) -> Option<&Symbol> { - self.name_to_id - .get(name) - .and_then(|id| self.id_to_symbol.get(id)) - } - - fn get_ordered_symbols(&self) -> Vec { - self.order - .iter() - .map(|id| self.id_to_symbol.get(id).expect("ID should exist").clone()) - .collect() - } -} - -/// A symbol table is a collection of scopes and manages the symbol ids. -pub struct SymbolTable { - scopes: Vec, - current_id: SymbolId, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ScopeKind { - /// Global scope, which is the current scope only when no other scopes are active. - /// This is the only scope where gates, qubits, and arrays can be declared. - Global, - Function, - Gate, - Block, -} - -const BUILTIN_SYMBOLS: [&str; 6] = ["pi", "π", "tau", "τ", "euler", "ℇ"]; - -impl SymbolTable { - pub fn new() -> Self { - let global = Scope::new(ScopeKind::Global); - - let mut slf = Self { - scopes: vec![global], - current_id: SymbolId::default(), - }; - - // Define global constants - for symbol in BUILTIN_SYMBOLS { - slf.insert_symbol(Symbol { - name: symbol.to_string(), - span: Span::default(), - ty: Type::Float(None, IsConst::True), - qsharp_ty: crate::types::Type::Double(true), - io_kind: IOKind::Default, - }) - .unwrap_or_else(|_| panic!("Failed to insert symbol: {symbol}")); - } - - slf - } - - pub fn push_scope(&mut self, kind: ScopeKind) { - assert!(kind != ScopeKind::Global, "Cannot push a global scope"); - self.scopes.push(Scope::new(kind)); - } - - pub fn pop_scope(&mut self) { - assert!(self.scopes.len() != 1, "Cannot pop the global scope"); - self.scopes.pop(); - } - - pub fn insert_symbol(&mut self, symbol: Symbol) -> Result { - let id = self.current_id; - self.current_id = self.current_id.successor(); - self.scopes - .last_mut() - .expect("At least one scope should be available") - .insert_symbol(id, symbol)?; - - Ok(id) - } - - pub fn get_symbol_by_name(&self, name: &str) -> Option<&Symbol> { - let scopes = self.scopes.iter().rev(); - let predicate = |x: &Scope| { - x.kind == ScopeKind::Block || x.kind == ScopeKind::Function || x.kind == ScopeKind::Gate - }; - - // Use scan to track the last item that returned false - let mut last_false = None; - let _ = scopes - .scan(&mut last_false, |state, item| { - if !predicate(item) { - **state = Some(item); - } - Some(predicate(item)) - }) - .take_while(|&x| x) - .last(); - let mut scopes = self.scopes.iter().rev(); - while let Some(scope) = scopes - .by_ref() - .take_while(|arg0: &&Scope| predicate(arg0)) - .next() - { - if let Some(symbol) = scope.get_symbol_by_name(name) { - return Some(symbol); - } - } - - if let Some(scope) = last_false { - if let Some(symbol) = scope.get_symbol_by_name(name) { - if symbol.ty.is_const() - || matches!(symbol.ty, Type::Gate(..) | Type::Void) - || self.is_scope_rooted_in_global() - { - return Some(symbol); - } - } - } - // we should be at the global, function, or gate scope now - for scope in scopes { - if let Some(symbol) = scope.get_symbol_by_name(name) { - if symbol.ty.is_const() || matches!(symbol.ty, Type::Gate(..) | Type::Void) { - return Some(symbol); - } - } - } - - None - } - - pub fn is_current_scope_global(&self) -> bool { - matches!(self.scopes.last(), Some(scope) if scope.kind == ScopeKind::Global) - } - - pub fn is_scope_rooted_in_subroutine(&self) -> bool { - self.scopes - .iter() - .rev() - .any(|scope| scope.kind == ScopeKind::Function) - } - - pub fn is_scope_rooted_in_global(&self) -> bool { - for scope in self.scopes.iter().rev() { - if scope.kind == ScopeKind::Function { - return false; - } - if scope.kind == ScopeKind::Gate { - return false; - } - } - true - } - - /// Get the input symbols in the program. - pub(crate) fn get_input(&self) -> Option> { - let io_input = self.get_io_input(); - if io_input.is_empty() { - None - } else { - Some(io_input) - } - } - - /// Get the output symbols in the program. - /// Output symbols are either inferred or explicitly declared. - /// If there are no explicitly declared output symbols, then the inferred - /// output symbols are returned. - pub(crate) fn get_output(&self) -> Option> { - let io_ouput = self.get_io_output(); - if io_ouput.is_some() { - io_ouput - } else { - self.get_inferred_output() - } - } - - /// Get all symbols in the global scope that are inferred output symbols. - /// Any global symbol that is not a built-in symbol and has a type that is - /// inferred to be an output type is considered an inferred output symbol. - fn get_inferred_output(&self) -> Option> { - let mut symbols = vec![]; - self.scopes - .iter() - .filter(|scope| scope.kind == ScopeKind::Global) - .for_each(|scope| { - for symbol in scope - .get_ordered_symbols() - .into_iter() - .filter(|symbol| !BUILTIN_SYMBOLS.contains(&symbol.name.as_str())) - .filter(|symbol| symbol.io_kind == IOKind::Default) - { - if is_inferred_output_type(&symbol.ty) { - symbols.push(symbol); - } - } - }); - if symbols.is_empty() { - None - } else { - Some(symbols) - } - } - - /// Get all symbols in the global scope that are output symbols. - fn get_io_output(&self) -> Option> { - let mut symbols = vec![]; - for scope in self - .scopes - .iter() - .filter(|scope| scope.kind == ScopeKind::Global) - { - for symbol in scope.get_ordered_symbols() { - if symbol.io_kind == IOKind::Output { - symbols.push(symbol); - } - } - } - if symbols.is_empty() { - None - } else { - Some(symbols) - } - } - - /// Get all symbols in the global scope that are input symbols. - fn get_io_input(&self) -> Vec { - let mut symbols = vec![]; - for scope in self - .scopes - .iter() - .filter(|scope| scope.kind == ScopeKind::Global) - { - for symbol in scope.get_ordered_symbols() { - if symbol.io_kind == IOKind::Input { - symbols.push(symbol); - } - } - } - symbols - } -} - -fn is_inferred_output_type(ty: &Type) -> bool { - matches!( - ty, - Type::Bit(_) - | Type::Int(_, _) - | Type::UInt(_, _) - | Type::Float(_, _) - | Type::Angle(_, _) - | Type::Complex(_, _) - | Type::Bool(_) - | Type::BitArray(_, _) - | Type::IntArray(_) - | Type::UIntArray(_) - | Type::FloatArray(_) - | Type::AngleArray(_) - | Type::ComplexArray(_) - | Type::BoolArray(_) - | Type::Range - | Type::Set - ) -} diff --git a/compiler/qsc_qasm3/src/tests.rs b/compiler/qsc_qasm3/src/tests.rs deleted file mode 100644 index 66c8c67e60..0000000000 --- a/compiler/qsc_qasm3/src/tests.rs +++ /dev/null @@ -1,327 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::{ - parse::{QasmParseResult, QasmSource}, - qasm_to_program, CompilerConfig, OutputSemantics, ProgramType, QasmCompileUnit, QubitSemantics, -}; -use miette::Report; -use qsc::interpret::Error; -use qsc::{ - ast::{mut_visit::MutVisitor, Package, Stmt, TopLevelNode}, - target::Profile, - PackageStore, SourceMap, Span, -}; -use std::{path::Path, sync::Arc}; - -use crate::{ - io::{InMemorySourceResolver, SourceResolver}, - parse::parse_source, -}; - -pub(crate) mod assignment; -pub(crate) mod declaration; -pub(crate) mod expression; -pub(crate) mod output; -pub(crate) mod sample_circuits; -pub(crate) mod scopes; -pub(crate) mod statement; - -pub(crate) fn fail_on_compilation_errors(unit: &QasmCompileUnit) { - if unit.has_errors() { - print_compilation_errors(unit); - panic!("Errors found in compilation"); - } -} - -pub(crate) fn print_compilation_errors(unit: &QasmCompileUnit) { - if unit.has_errors() { - for e in unit.errors() { - println!("{:?}", Report::new(e.clone())); - } - } -} - -pub(crate) fn gen_qsharp(package: &Package) -> String { - qsc::codegen::qsharp::write_package_string(package) -} - -/// Generates QIR from an AST package. -/// This function is used for testing purposes only. -/// The interactive environment uses a different mechanism to generate QIR. -/// As we need an entry expression to generate QIR in those cases. -/// -/// This function assumes that the AST package was designed as an entry point. -pub(crate) fn generate_qir_from_ast( - ast_package: Package, - source_map: SourceMap, - profile: Profile, -) -> Result> { - let mut store = PackageStore::new(qsc::compile::core()); - let mut dependencies = Vec::new(); - let capabilities = profile.into(); - dependencies.push((store.insert(qsc::compile::std(&store, capabilities)), None)); - - qsc::codegen::qir::get_qir_from_ast( - &mut store, - &dependencies, - ast_package, - source_map, - capabilities, - ) -} - -fn compile_qasm_to_qir(source: &str, profile: Profile) -> Result> { - let res = parse(source)?; - assert!(!res.has_errors()); - - let unit = qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::Qiskit, - ProgramType::File, - Some("Test".into()), - None, - ), - ); - fail_on_compilation_errors(&unit); - let package = unit.package.expect("no package found"); - let qir = generate_qir_from_ast(package, unit.source_map, profile).map_err(|errors| { - errors - .iter() - .map(|e| Report::new(e.clone())) - .collect::>() - })?; - Ok(qir) -} - -pub(crate) fn gen_qsharp_stmt(stmt: &Stmt) -> String { - qsc::codegen::qsharp::write_stmt_string(stmt) -} - -#[allow(dead_code)] -pub(crate) fn compare_compilation_to_qsharp(unit: &QasmCompileUnit, expected: &str) { - let package = unit.package.as_ref().expect("package must exist"); - let despanned_ast = AstDespanner.despan(package); - let qsharp = gen_qsharp(&despanned_ast); - difference::assert_diff!(&qsharp, expected, "\n", 0); -} - -pub(crate) fn parse(source: S) -> miette::Result> -where - S: AsRef, -{ - let resolver = InMemorySourceResolver::from_iter([("test".into(), source.as_ref().into())]); - let res = parse_source(source, "test", &resolver).map_err(|e| vec![e])?; - if res.source.has_errors() { - let errors = res - .errors() - .into_iter() - .map(|e| Report::new(e.clone())) - .collect(); - return Err(errors); - } - Ok(res) -} - -pub(crate) fn parse_all

( - path: P, - sources: impl IntoIterator, Arc)>, -) -> miette::Result> -where - P: AsRef, -{ - let resolver = InMemorySourceResolver::from_iter(sources); - let source = resolver.resolve(path.as_ref()).map_err(|e| vec![e])?.1; - let res = parse_source(source, path, &resolver).map_err(|e| vec![e])?; - if res.source.has_errors() { - let errors = res - .errors() - .into_iter() - .map(|e| Report::new(e.clone())) - .collect(); - Err(errors) - } else { - Ok(res) - } -} - -pub fn qasm_to_program_fragments(source: QasmSource, source_map: SourceMap) -> QasmCompileUnit { - qasm_to_program( - source, - source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::OpenQasm, - ProgramType::Fragments, - None, - None, - ), - ) -} - -pub fn compile_qasm_to_qsharp_file(source: &str) -> miette::Result> { - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::OpenQasm, - ProgramType::File, - Some("Test".into()), - None, - ), - ); - if unit.has_errors() { - let errors = unit.errors.into_iter().map(Report::new).collect(); - return Err(errors); - } - let Some(package) = unit.package else { - panic!("Expected package, got None"); - }; - let qsharp = gen_qsharp(&package); - Ok(qsharp) -} - -pub fn compile_qasm_to_qsharp_operation(source: &str) -> miette::Result> { - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::OpenQasm, - ProgramType::Operation, - Some("Test".into()), - None, - ), - ); - if unit.has_errors() { - let errors = unit.errors.into_iter().map(Report::new).collect(); - return Err(errors); - } - let Some(package) = unit.package else { - panic!("Expected package, got None"); - }; - let qsharp = gen_qsharp(&package); - Ok(qsharp) -} - -pub fn compile_qasm_to_qsharp(source: &str) -> miette::Result> { - compile_qasm_to_qsharp_with_semantics(source, QubitSemantics::Qiskit) -} - -pub fn compile_qasm_to_qsharp_with_semantics( - source: &str, - qubit_semantics: QubitSemantics, -) -> miette::Result> { - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - qubit_semantics, - OutputSemantics::Qiskit, - ProgramType::Fragments, - None, - None, - ), - ); - qsharp_from_qasm_compilation(unit) -} - -pub fn qsharp_from_qasm_compilation(unit: QasmCompileUnit) -> miette::Result> { - if unit.has_errors() { - let errors = unit.errors.into_iter().map(Report::new).collect(); - return Err(errors); - } - let Some(package) = unit.package else { - panic!("Expected package, got None"); - }; - let qsharp = gen_qsharp(&package); - Ok(qsharp) -} - -pub fn compile_qasm_stmt_to_qsharp(source: &str) -> miette::Result> { - compile_qasm_stmt_to_qsharp_with_semantics(source, QubitSemantics::Qiskit) -} - -pub fn compile_qasm_stmt_to_qsharp_with_semantics( - source: &str, - qubit_semantics: QubitSemantics, -) -> miette::Result> { - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - qubit_semantics, - OutputSemantics::Qiskit, - ProgramType::Fragments, - None, - None, - ), - ); - if unit.has_errors() { - let errors = unit.errors.into_iter().map(Report::new).collect(); - return Err(errors); - } - let Some(package) = unit.package else { - panic!("Expected package, got None"); - }; - let qsharp = get_first_statement_as_qsharp(&package); - Ok(qsharp) -} - -fn get_first_statement_as_qsharp(package: &Package) -> String { - let qsharp = match package.nodes.first() { - Some(i) => match i { - TopLevelNode::Namespace(_) => panic!("Expected Stmt, got Namespace"), - TopLevelNode::Stmt(stmt) => gen_qsharp_stmt(stmt.as_ref()), - }, - None => panic!("Expected Stmt, got None"), - }; - qsharp -} - -pub struct AstDespanner; -impl AstDespanner { - #[allow(dead_code)] // false positive lint - pub fn despan(&mut self, package: &Package) -> Package { - let mut p = package.clone(); - self.visit_package(&mut p); - p - } -} - -impl MutVisitor for AstDespanner { - fn visit_span(&mut self, span: &mut Span) { - span.hi = 0; - span.lo = 0; - } -} - -#[allow(dead_code)] -struct HirDespanner; -impl HirDespanner { - #[allow(dead_code)] - fn despan(&mut self, package: &qsc::hir::Package) -> qsc::hir::Package { - let mut p = package.clone(); - qsc::hir::mut_visit::MutVisitor::visit_package(self, &mut p); - p - } -} - -impl qsc::hir::mut_visit::MutVisitor for HirDespanner { - fn visit_span(&mut self, span: &mut Span) { - span.hi = 0; - span.lo = 0; - } -} diff --git a/compiler/qsc_qasm3/src/tests/declaration.rs b/compiler/qsc_qasm3/src/tests/declaration.rs deleted file mode 100644 index bd4136c229..0000000000 --- a/compiler/qsc_qasm3/src/tests/declaration.rs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -mod array; -mod bit; -mod bool; -mod complex; -mod float; -mod gate; -mod integer; -mod io; -mod qubit; -mod unsigned_integer; - -use crate::{ - tests::{fail_on_compilation_errors, parse, qasm_to_program_fragments}, - CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, -}; - -use miette::Report; - -#[test] -#[ignore = "oq3 parser bug, can't read float with leading dot"] -fn classical() -> miette::Result<(), Vec> { - let source = r#" - int[10] a; - int[10] b; - uint[32] c = 0xFa_1F; - uint[32] d = 0XFa_1F; - uint[16] e = 0o12_34; - uint[16] f = 0b1001_1001; - uint[16] g = 0B1001_1001; - uint h; - qubit[6] q1; - qubit q2; - bit[4] b1 = "0100"; - bit[8] b2 = "1001_0100"; - bit b3 = "1"; - bool i = true; - bool j = false; - const float[64] k = 5.5e3; - const float[64] l = 5; - float[32] m = .1e+3; - "#; - - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = qasm_to_program_fragments(res.source, res.source_map); - fail_on_compilation_errors(&unit); - Ok(()) -} - -#[test] -fn duration_literal() -> miette::Result<(), Vec> { - let source = " - duration dur0; - duration dur1 = 1000dt; - duration dur2 = 10 ms; - duration dur3 = 8 us; - duration dur4 = 1s; - "; - - let res = parse(source)?; - assert!(!res.has_errors()); - let unit = crate::qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::OpenQasm, - ProgramType::Fragments, - None, - None, - ), - ); - println!("{:?}", unit.errors); - assert!(unit.errors.len() == 5); - for error in &unit.errors { - assert!( - error - .to_string() - .contains("Duration type values are not supported.") - || error - .to_string() - .contains("Timing literal expressions are not supported.") - ); - } - - Ok(()) -} - -#[test] -fn stretch() { - let source = " - stretch s; - "; - - let res = parse(source).expect("should parse"); - assert!(!res.has_errors()); - let unit = crate::compile::qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::OpenQasm, - ProgramType::Fragments, - None, - None, - ), - ); - assert!(unit.has_errors()); - println!("{:?}", unit.errors); - assert!(unit.errors.len() == 1); - assert!(unit.errors[0] - .to_string() - .contains("Stretch type values are not supported."),); -} diff --git a/compiler/qsc_qasm3/src/tests/declaration/gate.rs b/compiler/qsc_qasm3/src/tests/declaration/gate.rs deleted file mode 100644 index de4614d2d8..0000000000 --- a/compiler/qsc_qasm3/src/tests/declaration/gate.rs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::tests::compile_qasm_stmt_to_qsharp; -use expect_test::expect; -use miette::Report; - -#[test] -fn single_qubit() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - gate my_h q { - h q; - } - "#; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let my_h : (Qubit) => Unit = (q) => { - H(q); - }; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn two_qubits() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - gate my_h q, q2 { - h q2; - h q; - } - "#; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let my_h : (Qubit, Qubit) => Unit = (q, q2) => { - H(q2); - H(q); - }; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn single_angle_single_qubit() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - gate my_h(θ) q { - rx(θ) q; - } - "#; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let my_h : (Double, Qubit) => Unit = (θ, q) => { - Rx(θ, q); - }; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn two_angles_two_qubits() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - gate my_h(θ, φ) q, q2 { - rx(θ) q2; - ry(φ) q; - } - "#; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let my_h : (Double, Double, Qubit, Qubit) => Unit = (θ, φ, q, q2) => { - Rx(θ, q2); - Ry(φ, q); - }; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} diff --git a/compiler/qsc_qasm3/src/tests/declaration/io/explicit_output.rs b/compiler/qsc_qasm3/src/tests/declaration/io/explicit_output.rs deleted file mode 100644 index 92e186465e..0000000000 --- a/compiler/qsc_qasm3/src/tests/declaration/io/explicit_output.rs +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::tests::compile_qasm_to_qsharp_operation; -use expect_test::expect; -use miette::Report; - -#[test] -fn bit_array_is_returned() -> miette::Result<(), Vec> { - let source = r#" -output bit[2] c; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Result[] { - mutable c = [Zero, Zero]; - c -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn bit_is_returned() -> miette::Result<(), Vec> { - let source = r#" -output bit c; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Result { - mutable c = Zero; - c -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn bool_is_returned() -> miette::Result<(), Vec> { - let source = r#" -output bool c; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Bool { - mutable c = false; - c -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn complex_is_returned() -> miette::Result<(), Vec> { - let source = r#" -output complex[float] c; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Microsoft.Quantum.Math.Complex { - mutable c = Microsoft.Quantum.Math.Complex(0., 0.); - c -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn float_implicit_width_is_returned() -> miette::Result<(), Vec> { - let source = r#" -output float f; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Double { - mutable f = 0.; - f -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn float_explicit_width_is_returned() -> miette::Result<(), Vec> { - let source = r#" -output float[42] f; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Double { - mutable f = 0.; - f -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn int_explicit_width_is_returned() -> miette::Result<(), Vec> { - let source = r#" -output int[42] i; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn int_implicit_width_is_returned() -> miette::Result<(), Vec> { - let source = r#" -output int i; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn uint_implicit_width_is_returned() -> miette::Result<(), Vec> { - let source = r#" -output uint i; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn uint_explicit_width_is_returned() -> miette::Result<(), Vec> { - let source = r#" -output uint[42] i; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn bigint_explicit_width_is_returned() -> miette::Result<(), Vec> { - let source = r#" -output int[65] i; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : BigInt { - mutable i = 0L; - i -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn qubit_explicit_output_raises_parse_error() { - let source = r#" -output qubit q; -"#; - - let Err(error) = compile_qasm_to_qsharp_operation(source) else { - panic!("Expected error") - }; - - assert!(error[0] - .to_string() - .contains("QASM3 Parse Error: Quantum type found in input/output declaration.")); -} - -#[test] -fn order_is_preserved_with_multiple_inputs() -> miette::Result<(), Vec> { - let source = r#" -output int[65] bi; -output int[6] i; -output uint[60] ui; -output uint u; -output float f; -output bool b; -output bit c; -output complex[float] cf; -output bit[2] b2; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : (BigInt, Int, Int, Int, Double, Bool, Result, Microsoft.Quantum.Math.Complex, Result[]) { - mutable bi = 0L; - mutable i = 0; - mutable ui = 0; - mutable u = 0; - mutable f = 0.; - mutable b = false; - mutable c = Zero; - mutable cf = Microsoft.Quantum.Math.Complex(0., 0.); - mutable b2 = [Zero, Zero]; - (bi, i, ui, u, f, b, c, cf, b2) -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} diff --git a/compiler/qsc_qasm3/src/tests/declaration/io/implicit_output.rs b/compiler/qsc_qasm3/src/tests/declaration/io/implicit_output.rs deleted file mode 100644 index 2a927e45e4..0000000000 --- a/compiler/qsc_qasm3/src/tests/declaration/io/implicit_output.rs +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::tests::compile_qasm_to_qsharp_operation; -use expect_test::expect; -use miette::Report; - -#[test] -fn bit_array_is_inferred_and_returned() -> miette::Result<(), Vec> { - let source = r#" -bit[2] c; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Result[] { - mutable c = [Zero, Zero]; - c -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn bit_is_inferred_and_returned() -> miette::Result<(), Vec> { - let source = r#" -bit c; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Result { - mutable c = Zero; - c -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn bool_is_inferred_and_returned() -> miette::Result<(), Vec> { - let source = r#" -bool c; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Bool { - mutable c = false; - c -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn complex_is_inferred_and_returned() -> miette::Result<(), Vec> { - let source = r#" -complex[float] c; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Microsoft.Quantum.Math.Complex { - mutable c = Microsoft.Quantum.Math.Complex(0., 0.); - c -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn float_implicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { - let source = r#" -float f; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Double { - mutable f = 0.; - f -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn float_explicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { - let source = r#" -float[42] f; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Double { - mutable f = 0.; - f -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn int_explicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { - let source = r#" -int[42] i; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn int_implicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { - let source = r#" -int i; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn uint_implicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { - let source = r#" -uint i; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn uint_explicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { - let source = r#" -uint[42] i; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn bigint_explicit_width_is_inferred_and_returned() -> miette::Result<(), Vec> { - let source = r#" -int[65] i; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : BigInt { - mutable i = 0L; - i -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn order_is_preserved_with_multiple_inputs() -> miette::Result<(), Vec> { - let source = r#" -int[65] bi; -int[6] i; -uint[60] ui; -uint u; -float f; -bool b; -bit c; -complex[float] cf; -bit[2] b2; -"#; - - let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : (BigInt, Int, Int, Int, Double, Bool, Result, Microsoft.Quantum.Math.Complex, Result[]) { - mutable bi = 0L; - mutable i = 0; - mutable ui = 0; - mutable u = 0; - mutable f = 0.; - mutable b = false; - mutable c = Zero; - mutable cf = Microsoft.Quantum.Math.Complex(0., 0.); - mutable b2 = [Zero, Zero]; - (bi, i, ui, u, f, b, c, cf, b2) -} -"# - ] - .assert_eq(&qsharp); - Ok(()) -} diff --git a/compiler/qsc_qasm3/src/tests/expression/binary/complex.rs b/compiler/qsc_qasm3/src/tests/expression/binary/complex.rs deleted file mode 100644 index 869e58d936..0000000000 --- a/compiler/qsc_qasm3/src/tests/expression/binary/complex.rs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::tests::compile_qasm_stmt_to_qsharp; - -use expect_test::expect; -use miette::Report; - -#[test] -fn subtraction() -> miette::Result<(), Vec> { - let source = " - input complex[float] a; - input complex[float] b; - complex x = (a - b); - "; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - mutable x = (Microsoft.Quantum.Math.MinusC(a, b)); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn addition() -> miette::Result<(), Vec> { - let source = " - input complex[float] a; - input complex[float] b; - complex x = (a + b); - "; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - mutable x = (Microsoft.Quantum.Math.PlusC(a, b)); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn multiplication() -> miette::Result<(), Vec> { - let source = " - input complex[float] a; - input complex[float] b; - complex x = (a * b); - "; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - mutable x = (Microsoft.Quantum.Math.TimesC(a, b)); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn division() -> miette::Result<(), Vec> { - let source = " - input complex[float] a; - input complex[float] b; - complex x = (a / b); - "; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - mutable x = (Microsoft.Quantum.Math.DividedByC(a, b)); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -#[ignore = "QASM3 parser bug"] -fn power() -> miette::Result<(), Vec> { - let source = " - input complex[float] a; - input complex[float] b; - complex x = (a ** b); - "; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - mutable x = (Microsoft.Quantum.Math.PowC(a, b)); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} diff --git a/compiler/qsc_qasm3/src/tests/expression/unary.rs b/compiler/qsc_qasm3/src/tests/expression/unary.rs deleted file mode 100644 index f4accf3eaf..0000000000 --- a/compiler/qsc_qasm3/src/tests/expression/unary.rs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use expect_test::expect; -use miette::Report; - -use crate::tests::compile_qasm_to_qsharp; - -#[test] -#[ignore = "OPENQASM 3.0 parser bug"] -fn bitwise_not_int() -> miette::Result<(), Vec> { - let source = " - int x = 5; - int y = ~x; - "; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - mutable x = 5; - mutable y = ~~~x; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn not_bool() -> miette::Result<(), Vec> { - let source = " - bool x = true; - bool y = !x; - "; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - mutable x = true; - mutable y = not x; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn not_result() -> miette::Result<(), Vec> { - let source = " - bit x = 1; - bit y = !x; - "; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __BoolAsResult__(input : Bool) : Result { - Microsoft.Quantum.Convert.BoolAsResult(input) - } - function __ResultAsBool__(input : Result) : Bool { - Microsoft.Quantum.Convert.ResultAsBool(input) - } - mutable x = One; - mutable y = __BoolAsResult__(not __ResultAsBool__(x)); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn logical_not_int() -> miette::Result<(), Vec> { - let source = " - int x = 159; - bool y = !x; - "; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - mutable x = 159; - mutable y = not if x == 0 { - false - } else { - true - }; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -#[ignore = "OPENQASM 3.0 parser bug"] -fn bitwise_not_result() -> miette::Result<(), Vec> { - let source = " - bit[1] x; - bool success = ~x[0]; - "; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn logical_not_indexed_bit_array_in_if_cond() -> miette::Result<(), Vec> { - let source = " - bit[10] Classical; - if (!Classical[1]) { - Classical[0] = 1; - } - "; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - function __ResultAsBool__(input : Result) : Bool { - Microsoft.Quantum.Convert.ResultAsBool(input) - } - mutable Classical = [Zero, Zero, Zero, Zero, Zero, Zero, Zero, Zero, Zero, Zero]; - if not __ResultAsBool__(Classical[1]) { - set Classical w/= 0 <- One; - }; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} diff --git a/compiler/qsc_qasm3/src/tests/scopes.rs b/compiler/qsc_qasm3/src/tests/scopes.rs deleted file mode 100644 index 5480fca9f1..0000000000 --- a/compiler/qsc_qasm3/src/tests/scopes.rs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::tests::compile_qasm_to_qsharp; -use expect_test::expect; -use miette::Report; - -#[test] -fn can_access_const_decls_from_global_scope() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - const int i = 7; - gate my_h q { - if (i == 0) { - h q; - } - } - qubit q; - my_h q; - "#; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - let i = 7; - let my_h : (Qubit) => Unit = (q) => { - if i == 0 { - H(q); - }; - }; - let q = QIR.Runtime.__quantum__rt__qubit_allocate(); - my_h(q); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn cannot_access_mutable_decls_from_global_scope() { - let source = r#" - include "stdgates.inc"; - int i; - gate my_h q { - if (i == 0) { - h q; - } - } - "#; - - let Err(errors) = compile_qasm_to_qsharp(source) else { - panic!("Expected an error"); - }; - expect![r#"Undefined symbol: i."#].assert_eq(&errors[0].to_string()); -} diff --git a/compiler/qsc_qasm3/src/tests/statement/annotation.rs b/compiler/qsc_qasm3/src/tests/statement/annotation.rs deleted file mode 100644 index 64155978ba..0000000000 --- a/compiler/qsc_qasm3/src/tests/statement/annotation.rs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::tests::compile_qasm_to_qsharp; -use expect_test::expect; -use miette::Report; - -#[test] -fn simulatable_intrinsic_can_be_applied_to_gate() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - @SimulatableIntrinsic - gate my_h q { - h q; - } - "#; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - @SimulatableIntrinsic() - operation my_h(q : Qubit) : Unit { - H(q); - } - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn unknown_annotation_raises_error() { - let source = r#" - include "stdgates.inc"; - @SomeUnknownAnnotation - gate my_h q { - h q; - } - "#; - - let Err(errors) = compile_qasm_to_qsharp(source) else { - panic!("Expected an error"); - }; - expect![r#"Unexpected annotation: @SomeUnknownAnnotation."#].assert_eq(&errors[0].to_string()); -} - -#[test] -fn annotation_without_target_in_global_scope_raises_error() { - let source = r#" - int i; - @SimulatableIntrinsic - "#; - - let Err(errors) = compile_qasm_to_qsharp(source) else { - panic!("Expected an error"); - }; - expect![r#"Annotation missing target statement."#].assert_eq(&errors[0].to_string()); -} - -#[test] -fn annotation_without_target_in_block_scope_raises_error() { - let source = r#" - int i; - if (0 == 1) { - @SimulatableIntrinsic - } - "#; - - let Err(errors) = compile_qasm_to_qsharp(source) else { - panic!("Expected an error"); - }; - expect![r#"Annotation missing target statement."#].assert_eq(&errors[0].to_string()); -} diff --git a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs deleted file mode 100644 index 3165ccd847..0000000000 --- a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::tests::{compile_qasm_to_qir, compile_qasm_to_qsharp}; -use expect_test::expect; -use miette::Report; -use qsc::target::Profile; - -#[test] -fn x_gate_can_be_called() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - qubit q; - x q; - "#; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - let q = QIR.Runtime.__quantum__rt__qubit_allocate(); - X(q); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn barrier_can_be_called_on_single_qubit() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - qubit q; - barrier q; - "#; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - @SimulatableIntrinsic() - operation __quantum__qis__barrier__body() : Unit {} - let q = QIR.Runtime.__quantum__rt__qubit_allocate(); - __quantum__qis__barrier__body(); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn barrier_can_be_called_without_qubits() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - qubit q; - barrier; - "#; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - @SimulatableIntrinsic() - operation __quantum__qis__barrier__body() : Unit {} - let q = QIR.Runtime.__quantum__rt__qubit_allocate(); - __quantum__qis__barrier__body(); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn barrier_generates_qir() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - bit[1] c; - qubit[2] q; - barrier q[0], q[1]; - barrier q[0]; - barrier; - barrier q[0], q[1], q[0]; - c[0] = measure q[0]; - "#; - - let qsharp = compile_qasm_to_qir(source, Profile::AdaptiveRI)?; - expect![ - r#" - %Result = type opaque - %Qubit = type opaque - - define void @ENTRYPOINT__main() #0 { - block_0: - call void @__quantum__qis__barrier__body() - call void @__quantum__qis__barrier__body() - call void @__quantum__qis__barrier__body() - call void @__quantum__qis__barrier__body() - call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) - call void @__quantum__rt__array_record_output(i64 1, i8* null) - call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) - ret void - } - - declare void @__quantum__qis__barrier__body() - - declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 - - declare void @__quantum__rt__array_record_output(i64, i8*) - - declare void @__quantum__rt__result_record_output(%Result*, i8*) - - attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="2" "required_num_results"="1" } - attributes #1 = { "irreversible" } - - ; module flags - - !llvm.module.flags = !{!0, !1, !2, !3, !4} - - !0 = !{i32 1, !"qir_major_version", i32 1} - !1 = !{i32 7, !"qir_minor_version", i32 0} - !2 = !{i32 1, !"dynamic_qubit_management", i1 false} - !3 = !{i32 1, !"dynamic_result_management", i1 false} - !4 = !{i32 1, !"int_computations", !"i64"} - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn barrier_can_be_called_on_two_qubit() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - qubit[2] q; - barrier q[0], q[1]; - "#; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - @SimulatableIntrinsic() - operation __quantum__qis__barrier__body() : Unit {} - let q = QIR.Runtime.AllocateQubitArray(2); - __quantum__qis__barrier__body(); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} diff --git a/compiler/qsc_qasm3/src/tests/statement/implicit_modified_gate_call.rs b/compiler/qsc_qasm3/src/tests/statement/implicit_modified_gate_call.rs deleted file mode 100644 index 0163ac470e..0000000000 --- a/compiler/qsc_qasm3/src/tests/statement/implicit_modified_gate_call.rs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::tests::compile_qasm_to_qsharp; -use expect_test::expect; -use miette::Report; - -#[test] -fn sdg_gate_can_be_called() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - qubit q; - sdg q; - "#; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - let q = QIR.Runtime.__quantum__rt__qubit_allocate(); - Adjoint S(q); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn tdg_gate_can_be_called() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - qubit q; - tdg q; - "#; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - let q = QIR.Runtime.__quantum__rt__qubit_allocate(); - Adjoint T(q); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn crx_gate_can_be_called() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - qubit[2] q; - crx(0.5) q[1], q[0]; - "#; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - let q = QIR.Runtime.AllocateQubitArray(2); - Controlled Rx([q[1]], (0.5, q[0])); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn cry_gate_can_be_called() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - qubit[2] q; - cry(0.5) q[1], q[0]; - "#; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - let q = QIR.Runtime.AllocateQubitArray(2); - Controlled Ry([q[1]], (0.5, q[0])); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn crz_gate_can_be_called() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - qubit[2] q; - crz(0.5) q[1], q[0]; - "#; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - let q = QIR.Runtime.AllocateQubitArray(2); - Controlled Rz([q[1]], (0.5, q[0])); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - -#[test] -fn ch_gate_can_be_called() -> miette::Result<(), Vec> { - let source = r#" - include "stdgates.inc"; - qubit[2] q; - ch q[1], q[0]; - "#; - - let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - let q = QIR.Runtime.AllocateQubitArray(2); - Controlled H([q[1]], q[0]); - "# - ] - .assert_eq(&qsharp); - Ok(()) -} diff --git a/compiler/qsc_qasm3/src/tests/statement/include.rs b/compiler/qsc_qasm3/src/tests/statement/include.rs deleted file mode 100644 index 7d096298a4..0000000000 --- a/compiler/qsc_qasm3/src/tests/statement/include.rs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::{ - qasm_to_program, - tests::{parse_all, qsharp_from_qasm_compilation}, - CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, -}; -use expect_test::expect; -use miette::Report; - -#[test] -fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { - let source = r#" - OPENQASM 3.0; - include "stdgates.inc"; - include "custom_intrinsics.inc"; - bit[1] c; - qubit[1] q; - my_gate q[0]; - c[0] = measure q[0]; - "#; - let custom_intrinsics = r#" - @SimulatableIntrinsic - gate my_gate q { - x q; - } - "#; - let all_sources = [ - ("source0.qasm".into(), source.into()), - ("custom_intrinsics.inc".into(), custom_intrinsics.into()), - ]; - - let res = parse_all("source0.qasm", all_sources)?; - let r = qasm_to_program( - res.source, - res.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - OutputSemantics::Qiskit, - ProgramType::File, - Some("Test".into()), - None, - ), - ); - let qsharp = qsharp_from_qasm_compilation(r)?; - expect![[r#" - namespace qasm3_import { - @EntryPoint() - operation Test() : Result[] { - @SimulatableIntrinsic() - operation my_gate(q : Qubit) : Unit { - X(q); - } - mutable c = [Zero]; - let q = QIR.Runtime.AllocateQubitArray(1); - my_gate(q[0]); - set c w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[0]); - Microsoft.Quantum.Arrays.Reversed(c) - } - }"#]] - .assert_eq(&qsharp); - Ok(()) -} diff --git a/compiler/qsc_qasm3/src/types.rs b/compiler/qsc_qasm3/src/types.rs deleted file mode 100644 index 6b24bfd83a..0000000000 --- a/compiler/qsc_qasm3/src/types.rs +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use std::fmt::{self, Display, Formatter}; - -use oq3_semantics::types::ArrayDims; -use qsc_data_structures::span::Span; -use rustc_hash::FxHashMap; - -thread_local! { - /// - pub static GATE_MAP: FxHashMap<&'static str, &'static str> = { - let mut m = FxHashMap::default(); - // p is rz, should have been replaced by rz by transpile - - m.insert("x", "X"); - m.insert("y", "Y"); - m.insert("z", "Z"); - - m.insert("h", "H"); - - m.insert("s", "S"); - m.insert("sdg", "sdg"); - - m.insert("t", "T"); - m.insert("tdg", "tdg"); - - // sx q is Rx(pi/2, q), should have been replaced by Rx by transpile - - m.insert("crx", "crx"); - m.insert("cry", "cry"); - m.insert("crz", "crz"); - - m.insert("rx", "Rx"); - m.insert("ry", "Ry"); - m.insert("rz", "Rz"); - - m.insert("rxx", "Rxx"); - m.insert("ryy", "Ryy"); - m.insert("rzz", "Rzz"); - - m.insert("cx", "CNOT"); - m.insert("cy", "CY"); - m.insert("cz", "CZ"); - - // cp (controlled-phase), should have been replaced by transpile - - m.insert("ch", "ch"); - - m.insert("id", "I"); - - m.insert("swap", "SWAP"); - - m.insert("ccx", "CCNOT"); - - // cswap (controlled-swap), should have been replaced by transpile - - // cu (controlled-U), should have been replaced by transpile - - // openqasm 2.0 gates should have been replaced by transpile - // CX, phase, cphase, id, u1, u2, u3 - m - }; -} - -pub(crate) fn get_qsharp_gate_name>(gate_name: S) -> Option<&'static str> { - GATE_MAP.with(|map| map.get(gate_name.as_ref()).copied()) -} - -/// When compiling QASM3 expressions, we need to keep track of the sematic QASM -/// type of the expression. This allows us to perform type checking and casting -/// when necessary. -#[derive(Debug, Clone, PartialEq)] -pub struct QasmTypedExpr { - pub ty: oq3_semantics::types::Type, - pub expr: qsc_ast::ast::Expr, -} - -#[derive(Clone, Debug, PartialEq)] -pub enum GateModifier { - /// The `adjoint` modifier. - Inv(Span), - Pow(Option, Span), - Ctrl(Option, Span), - NegCtrl(Option, Span), -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Complex { - pub real: f64, - pub imaginary: f64, -} - -impl Complex { - pub fn new(real: f64, imaginary: f64) -> Self { - Self { real, imaginary } - } -} - -#[allow(dead_code)] -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) enum Type { - Bool(bool), - BigInt(bool), - Complex(bool), - Int(bool), - Double(bool), - Qubit, - Result(bool), - Tuple(Vec), - Range, - BoolArray(ArrayDimensions, bool), - BigIntArray(ArrayDimensions, bool), - IntArray(ArrayDimensions, bool), - DoubleArray(ArrayDimensions), - QubitArray(ArrayDimensions), - ResultArray(ArrayDimensions, bool), - TupleArray(ArrayDimensions, Vec), - /// Function or operation, with the number of classical parameters and qubits. - Callable(CallableKind, usize, usize), -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum CallableKind { - /// A function. - #[allow(dead_code)] - Function, - /// An operation. - Operation, -} - -impl Display for CallableKind { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - CallableKind::Function => write!(f, "Function"), - CallableKind::Operation => write!(f, "Operation"), - } - } -} - -/// QASM supports up to seven dimensions, but we are going to limit it to three. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ArrayDimensions { - One(usize), - Two(usize, usize), - Three(usize, usize, usize), -} - -impl From<&ArrayDims> for ArrayDimensions { - fn from(value: &ArrayDims) -> Self { - match value { - ArrayDims::D1(dim) => ArrayDimensions::One(*dim), - ArrayDims::D2(dim1, dim2) => ArrayDimensions::Two(*dim1, *dim2), - ArrayDims::D3(dim1, dim2, dim3) => ArrayDimensions::Three(*dim1, *dim2, *dim3), - } - } -} - -impl Display for Type { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Type::Bool(_) => write!(f, "bool"), - Type::BigInt(_) => write!(f, "BigInt"), - Type::Complex(_) => write!(f, "Complex"), - Type::Int(_) => write!(f, "Int"), - Type::Double(_) => write!(f, "Double"), - Type::Qubit => write!(f, "Qubit"), - Type::Range => write!(f, "Range"), - Type::Result(_) => write!(f, "Result"), - Type::Tuple(types) => { - write!(f, "(")?; - for (i, ty) in types.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{ty}")?; - } - write!(f, ")") - } - Type::BoolArray(dim, _) => write!(f, "bool{dim}"), - Type::BigIntArray(dim, _) => write!(f, "BigInt{dim}"), - Type::IntArray(dim, _) => write!(f, "Int{dim}"), - Type::DoubleArray(dim) => write!(f, "Double{dim}"), - Type::QubitArray(dim) => write!(f, "Qubit{dim}"), - Type::ResultArray(dim, _) => write!(f, "Result{dim}"), - Type::TupleArray(dim, types) => { - write!(f, "(")?; - for (i, ty) in types.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{ty}")?; - } - write!(f, "){dim}") - } - Type::Callable(kind, num_classical, num_qubits) => { - write!(f, "Callable({kind}, {num_classical}, {num_qubits})") - } - } - } -} - -impl Display for ArrayDimensions { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - ArrayDimensions::One(..) => write!(f, "[]"), - ArrayDimensions::Two(..) => write!(f, "[][]"), - ArrayDimensions::Three(..) => write!(f, "[][][]"), - } - } -} - -/// Get the indexed type of a given type. -/// For example, if the type is `Int[2][3]`, the indexed type is `Int[2]`. -/// If the type is `Int[2]`, the indexed type is `Int`. -/// If the type is `Int`, the indexed type is `None`. -/// -/// This is useful for determining the type of an array element. -pub(crate) fn get_indexed_type( - ty: &oq3_semantics::types::Type, -) -> Option { - use oq3_semantics::types::{IsConst, Type}; - let ty = match &ty { - Type::AngleArray(dims) => match dims { - ArrayDims::D1(_) => Type::Angle(None, IsConst::False), - ArrayDims::D2(l, _) => Type::AngleArray(ArrayDims::D1(*l)), - ArrayDims::D3(l, w, _) => Type::AngleArray(ArrayDims::D2(*l, *w)), - }, - Type::BitArray(dims, is_const) => match dims { - ArrayDims::D1(_) => Type::Bit(is_const.clone()), - ArrayDims::D2(l, _) => Type::BitArray(ArrayDims::D1(*l), is_const.clone()), - ArrayDims::D3(l, w, _) => Type::BitArray(ArrayDims::D2(*l, *w), is_const.clone()), - }, - Type::BoolArray(dims) => match dims { - ArrayDims::D1(_) => Type::Bool(IsConst::False), - ArrayDims::D2(l, _) => Type::BoolArray(ArrayDims::D1(*l)), - ArrayDims::D3(l, w, _) => Type::BoolArray(ArrayDims::D2(*l, *w)), - }, - Type::ComplexArray(dims) => match dims { - ArrayDims::D1(_) => Type::Complex(None, IsConst::False), - ArrayDims::D2(l, _) => Type::ComplexArray(ArrayDims::D1(*l)), - ArrayDims::D3(l, w, _) => Type::ComplexArray(ArrayDims::D2(*l, *w)), - }, - Type::FloatArray(dims) => match dims { - ArrayDims::D1(_) => Type::Float(None, IsConst::False), - ArrayDims::D2(l, _) => Type::FloatArray(ArrayDims::D1(*l)), - ArrayDims::D3(l, w, _) => Type::FloatArray(ArrayDims::D2(*l, *w)), - }, - Type::IntArray(dims) => match dims { - ArrayDims::D1(_) => Type::Int(None, IsConst::False), - ArrayDims::D2(l, _) => Type::IntArray(ArrayDims::D1(*l)), - ArrayDims::D3(l, w, _) => Type::IntArray(ArrayDims::D2(*l, *w)), - }, - Type::QubitArray(dims) => match dims { - ArrayDims::D1(_) => Type::Qubit, - ArrayDims::D2(l, _) => Type::QubitArray(ArrayDims::D1(*l)), - ArrayDims::D3(l, w, _) => Type::QubitArray(ArrayDims::D2(*l, *w)), - }, - Type::UIntArray(dims) => match dims { - ArrayDims::D1(_) => Type::UInt(None, IsConst::False), - ArrayDims::D2(l, _) => Type::UIntArray(ArrayDims::D1(*l)), - ArrayDims::D3(l, w, _) => Type::UIntArray(ArrayDims::D2(*l, *w)), - }, - _ => return None, - }; - Some(ty) -} diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 34b9ba6609..8240cddc8a 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -26,7 +26,13 @@ do_fuzz = [ "dep:libfuzzer-sys" ] workspace = true [[bin]] -name = "compile" -path = "fuzz_targets/compile.rs" +name = "qsharp" +path = "fuzz_targets/qsharp.rs" +test = false +doc = false + +[[bin]] +name = "qasm" +path = "fuzz_targets/qasm.rs" test = false doc = false diff --git a/fuzz/fuzz_targets/qasm.rs b/fuzz/fuzz_targets/qasm.rs new file mode 100644 index 0000000000..b4c38dfbcd --- /dev/null +++ b/fuzz/fuzz_targets/qasm.rs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![no_main] + +allocator::assign_global!(); + +#[cfg(feature = "do_fuzz")] +use libfuzzer_sys::fuzz_target; + +fn compile(data: &[u8]) { + if let Ok(fuzzed_code) = std::str::from_utf8(data) { + let mut resolver = qsc::qasm::io::InMemorySourceResolver::from_iter([]); + let _ = qsc::qasm::parser::parse_source(fuzzed_code, "fuzz.qasm", &mut resolver); + } +} + +#[cfg(feature = "do_fuzz")] +fuzz_target!(|data: &[u8]| { + compile(data); +}); + +#[cfg(not(feature = "do_fuzz"))] +#[no_mangle] +pub extern "C" fn main() { + compile(&[]); +} diff --git a/fuzz/fuzz_targets/compile.rs b/fuzz/fuzz_targets/qsharp.rs similarity index 100% rename from fuzz/fuzz_targets/compile.rs rename to fuzz/fuzz_targets/qsharp.rs diff --git a/fuzz/seed_inputs/compile/list.txt b/fuzz/seed_inputs/compile/list.txt deleted file mode 100644 index 75e4c71576..0000000000 --- a/fuzz/seed_inputs/compile/list.txt +++ /dev/null @@ -1 +0,0 @@ -fuzz/seed_inputs/compile/input.qs \ No newline at end of file diff --git a/fuzz/seed_inputs/qasm/input.qasm b/fuzz/seed_inputs/qasm/input.qasm new file mode 100644 index 0000000000..cddbfe007f --- /dev/null +++ b/fuzz/seed_inputs/qasm/input.qasm @@ -0,0 +1,8 @@ +OPENQASM 3; +include "stdgates.inc"; +qubit q; +qubit[2] q2; +bit c; +bit[2] c2; +c2 = measure q2; +c = measure q; diff --git a/fuzz/seed_inputs/qasm/list.txt b/fuzz/seed_inputs/qasm/list.txt new file mode 100644 index 0000000000..a885415178 --- /dev/null +++ b/fuzz/seed_inputs/qasm/list.txt @@ -0,0 +1 @@ +fuzz/seed_inputs/qasm/input.qasm diff --git a/fuzz/seed_inputs/compile/input.qs b/fuzz/seed_inputs/qsharp/input.qs similarity index 100% rename from fuzz/seed_inputs/compile/input.qs rename to fuzz/seed_inputs/qsharp/input.qs diff --git a/fuzz/seed_inputs/qsharp/list.txt b/fuzz/seed_inputs/qsharp/list.txt new file mode 100644 index 0000000000..c6222243a3 --- /dev/null +++ b/fuzz/seed_inputs/qsharp/list.txt @@ -0,0 +1 @@ +fuzz/seed_inputs/qsharp/input.qs diff --git a/pip/qsharp/_native.pyi b/pip/qsharp/_native.pyi index d7017186de..6e64ee44ca 100644 --- a/pip/qsharp/_native.pyi +++ b/pip/qsharp/_native.pyi @@ -375,7 +375,7 @@ def physical_estimates(logical_resources: str, params: str) -> str: """ ... -def resource_estimate_qasm3( +def resource_estimate_qasm( source: str, job_params: str, read_file: Callable[[str], Tuple[str, str]], @@ -385,7 +385,7 @@ def resource_estimate_qasm3( **kwargs ) -> str: """ - Estimates the resource requirements for executing QASM3 source code. + Estimates the resource requirements for executing QASM source code. Note: This call while exported is not intended to be used directly by the user. @@ -393,7 +393,7 @@ def resource_estimate_qasm3( callbacks and other Python specific details. Args: - source (str): The QASM3 source code to estimate the resource requirements for. + source (str): The QASM source code to estimate the resource requirements for. job_params (str): The parameters for the job. read_file (Callable[[str], Tuple[str, str]]): A callable that reads a file and returns its content and path. list_directory (Callable[[str], List[Dict[str, str]]]): A callable that lists the contents of a directory. @@ -403,11 +403,11 @@ def resource_estimate_qasm3( - name (str): The name of the circuit. This is used as the entry point for the program. Defaults to 'program'. - search_path (str): The optional search path for resolving imports. Returns: - str: The estimated resource requirements for executing the QASM3 source code. + str: The estimated resource requirements for executing the QASM source code. """ ... -def run_qasm3( +def run_qasm( source: str, output_fn: Callable[[Output], None], read_file: Callable[[str], Tuple[str, str]], @@ -417,7 +417,7 @@ def run_qasm3( **kwargs ) -> Any: """ - Executes QASM3 source code using the specified target profile. + Executes QASM source code using the specified target profile. Note: This call while exported is not intended to be used directly by the user. @@ -425,7 +425,7 @@ def run_qasm3( callbacks and other Python specific details. Args: - source (str): The QASM3 source code to execute. + source (str): The QASM source code to execute. output_fn (Callable[[Output], None]): The function to handle the output of the execution. read_file (Callable[[str], Tuple[str, str]]): The function to read a file and return its contents. list_directory (Callable[[str], List[Dict[str, str]]]): The function to list the contents of a directory. @@ -443,7 +443,7 @@ def run_qasm3( """ ... -def compile_qasm3_to_qir( +def compile_qasm_to_qir( source: str, read_file: Callable[[str], Tuple[str, str]], list_directory: Callable[[str], List[Dict[str, str]]], @@ -460,7 +460,7 @@ def compile_qasm3_to_qir( callbacks and other Python specific details. Args: - source (str): The QASM3 source code to estimate the resource requirements for. + source (str): The QASM source code to estimate the resource requirements for. read_file (Callable[[str], Tuple[str, str]]): A callable that reads a file and returns its content and path. list_directory (Callable[[str], List[Dict[str, str]]]): A callable that lists the contents of a directory. resolve_path (Callable[[str, str], str]): A callable that resolves a file path given a base path and a relative path. @@ -476,7 +476,7 @@ def compile_qasm3_to_qir( """ ... -def compile_qasm3_to_qsharp( +def compile_qasm_to_qsharp( source: str, read_file: Callable[[str], Tuple[str, str]], list_directory: Callable[[str], List[Dict[str, str]]], @@ -493,7 +493,7 @@ def compile_qasm3_to_qsharp( callbacks and other Python specific details. Args: - source (str): The QASM3 source code to estimate the resource requirements for. + source (str): The QASM source code to estimate the resource requirements for. read_file (Callable[[str], Tuple[str, str]]): A callable that reads a file and returns its content and path. list_directory (Callable[[str], List[Dict[str, str]]]): A callable that lists the contents of a directory. resolve_path (Callable[[str, str], str]): A callable that resolves a file path given a base path and a relative path. diff --git a/pip/qsharp/interop/qiskit/backends/backend_base.py b/pip/qsharp/interop/qiskit/backends/backend_base.py index a6b18d8ea2..e0ed4061ab 100644 --- a/pip/qsharp/interop/qiskit/backends/backend_base.py +++ b/pip/qsharp/interop/qiskit/backends/backend_base.py @@ -181,7 +181,7 @@ def __init__( target_gates -= set(_QISKIT_STDGATES) basis_gates = list(target_gates) - # selt the default options for the exporter + # set the default options for the exporter self._qasm_export_options = { "includes": ("stdgates.inc",), "alias_classical_registers": False, @@ -294,7 +294,7 @@ def _submit_job(self, run_input: List[QuantumCircuit], **input_params) -> QsJob: pass def _compile(self, run_input: List[QuantumCircuit], **options) -> List[Compilation]: - # for each run input, convert to qasm3 + # for each run input, convert to qasm compilations = [] for circuit in run_input: args = options.copy() @@ -302,7 +302,7 @@ def _compile(self, run_input: List[QuantumCircuit], **options) -> List[Compilati circuit, QuantumCircuit ), "Input must be a QuantumCircuit." start = monotonic() - qasm = self._qasm3(circuit, **args) + qasm = self._qasm(circuit, **args) end = monotonic() time_taken = end - start @@ -407,7 +407,7 @@ def transpile(self, circuit: QuantumCircuit, **options) -> QuantumCircuit: transpiled_circuit = self._transpile(circuit, **options) return transpiled_circuit - def _qasm3(self, circuit: QuantumCircuit, **options) -> str: + def _qasm(self, circuit: QuantumCircuit, **options) -> str: """Converts a Qiskit QuantumCircuit to QASM 3 for the current backend. Args: @@ -419,17 +419,20 @@ def _qasm3(self, circuit: QuantumCircuit, **options) -> str: 'includes', 'search_path'. Returns: - str: The converted QASM3 code as a string. Any supplied includes + str: The converted QASM code as a string. Any supplied includes are emitted as include statements at the top of the program. :raises QasmError: If there is an error generating or parsing QASM. """ - + transpiled_circuit = self.transpile(circuit, **options) try: export_options = self._build_qasm_export_options(**options) exporter = Exporter(**export_options) - transpiled_circuit = self.transpile(circuit, **options) qasm3_source = exporter.dumps(transpiled_circuit) + # Qiskit QASM exporter doesn't handle experimental features correctly and always emits + # OPENQASM 3.0; even though switch case is not supported in QASM 3.0, so we bump + # the version to 3.1 for now. + qasm3_source = qasm3_source.replace("OPENQASM 3.0", "OPENQASM 3.1") return qasm3_source except Exception as ex: from .. import QasmError @@ -452,14 +455,14 @@ def _qsharp(self, circuit: QuantumCircuit, **kwargs) -> str: 'includes', 'search_path'. - output_semantics (OutputSemantics, optional): The output semantics for the compilation. Returns: - str: The converted QASM3 code as a string. Any supplied includes + str: The converted QASM code as a string. Any supplied includes are emitted as include statements at the top of the program. :raises QSharpError: If there is an error evaluating the source code. :raises QasmError: If there is an error generating, parsing, or compiling QASM. """ - qasm3_source = self._qasm3(circuit, **kwargs) + qasm_source = self._qasm(circuit, **kwargs) args = { "name": kwargs.get("name", circuit.name), @@ -473,7 +476,7 @@ def _qsharp(self, circuit: QuantumCircuit, **kwargs) -> str: ): args["output_semantics"] = output_semantics - qsharp_source = self._qasm3_to_qsharp(qasm3_source, **args) + qsharp_source = self._qasm_to_qsharp(qasm_source, **args) return qsharp_source def qir( @@ -503,7 +506,7 @@ def qir( if target_profile == TargetProfile.Unrestricted: raise ValueError(str(Errors.UNRESTRICTED_INVALID_QIR_TARGET)) - qasm3_source = self._qasm3(circuit, **kwargs) + qasm_source = self._qasm(circuit, **kwargs) args = { "name": name, @@ -521,18 +524,18 @@ def qir( ): args["output_semantics"] = output_semantics - return self._qasm3_to_qir(qasm3_source, **args) + return self._qasm_to_qir(qasm_source, **args) - def _qasm3_to_qir( + def _qasm_to_qir( self, source: str, **kwargs, ) -> str: - from ...._native import compile_qasm3_to_qir + from ...._native import compile_qasm_to_qir from ...._fs import read_file, list_directory, resolve from ...._http import fetch_github - return compile_qasm3_to_qir( + return compile_qasm_to_qir( source, read_file, list_directory, @@ -541,16 +544,16 @@ def _qasm3_to_qir( **kwargs, ) - def _qasm3_to_qsharp( + def _qasm_to_qsharp( self, source: str, **kwargs, ) -> str: - from ...._native import compile_qasm3_to_qsharp + from ...._native import compile_qasm_to_qsharp from ...._fs import read_file, list_directory, resolve from ...._http import fetch_github - return compile_qasm3_to_qsharp( + return compile_qasm_to_qsharp( source, read_file, list_directory, diff --git a/pip/qsharp/interop/qiskit/backends/errors.py b/pip/qsharp/interop/qiskit/backends/errors.py index 9fa54c9ecc..6468eddcd9 100644 --- a/pip/qsharp/interop/qiskit/backends/errors.py +++ b/pip/qsharp/interop/qiskit/backends/errors.py @@ -18,7 +18,7 @@ def __str__(self): elif self == Errors.RUN_TERMINATED_WITHOUT_OUTPUT: return "Run terminated without valid output." elif self == Errors.FAILED_TO_EXPORT_QASM: - return "Failed to export QASM3 source." + return "Failed to export QASM source." elif self == Errors.MISSING_NUMBER_OF_SHOTS: return "The number of shots must be specified." elif self == Errors.INPUT_MUST_BE_QC: diff --git a/pip/qsharp/interop/qiskit/backends/qsharp_backend.py b/pip/qsharp/interop/qiskit/backends/qsharp_backend.py index 3a47d9b1cb..298b166404 100644 --- a/pip/qsharp/interop/qiskit/backends/qsharp_backend.py +++ b/pip/qsharp/interop/qiskit/backends/qsharp_backend.py @@ -143,7 +143,7 @@ def _execute(self, programs: List[Compilation], **input_params) -> Dict[str, Any exec_results: List[Tuple[Compilation, Dict[str, Any]]] = [ ( program, - _run_qasm3(program.qasm, vars(self.options).copy(), **input_params), + _run_qasm(program.qasm, vars(self.options).copy(), **input_params), ) for program in programs ] @@ -202,7 +202,7 @@ def _submit_job( return job -def _run_qasm3( +def _run_qasm( qasm: str, default_options: Options, **options, @@ -221,7 +221,7 @@ def _run_qasm3( - target_profile (TargetProfile): The target profile to use for the compilation. - output_semantics (OutputSemantics, optional): The output semantics for the compilation. - name (str): The name of the circuit. This is used as the entry point for the program. Defaults to 'program'. - - search_path (str): The optional search path for resolving qasm3 imports. + - search_path (str): The optional search path for resolving qasm imports. - shots (int): The number of shots to run the program for. Defaults to 1. - seed (int): The seed to use for the random number generator. - output_fn (Optional[Callable[[Output], None]]): A callback function that will be called with each output. Defaults to None. @@ -232,8 +232,7 @@ def _run_qasm3( :raises QasmError: If there is an error generating, parsing, or compiling QASM. """ - from ...._native import Output - from ...._native import run_qasm3 + from ...._native import Output, run_qasm from ...._fs import read_file, list_directory, resolve from ...._http import fetch_github @@ -263,7 +262,7 @@ def value_or_default(key: str) -> Any: if seed := value_or_default("seed"): args["seed"] = seed - return run_qasm3( + return run_qasm( qasm, output_fn, read_file, diff --git a/pip/qsharp/interop/qiskit/backends/re_backend.py b/pip/qsharp/interop/qiskit/backends/re_backend.py index 54d90d6892..f88ae68d9f 100644 --- a/pip/qsharp/interop/qiskit/backends/re_backend.py +++ b/pip/qsharp/interop/qiskit/backends/re_backend.py @@ -19,7 +19,7 @@ from ..execution import DetaultExecutor from ...._fs import read_file, list_directory, resolve from ...._http import fetch_github -from ...._native import resource_estimate_qasm3 +from ...._native import resource_estimate_qasm from .... import TargetProfile from ....estimator import ( EstimatorResult, @@ -56,7 +56,7 @@ def __init__( - params (EstimatorParams): Configuration values for resource estimation. - name (str): The name of the circuit. This is used as the entry point for the program. The circuit name will be used if not specified. - - search_path (str): Path to search in for qasm3 imports. Defaults to '.'. + - search_path (str): Path to search in for qasm imports. Defaults to '.'. - executor(ThreadPoolExecutor or other Executor): The executor to be used to submit the job. Defaults to SynchronousExecutor. """ @@ -104,7 +104,7 @@ def run( **options: Additional options for the execution. - name (str): The name of the circuit. This is used as the entry point for the program. The circuit name will be used if not specified. - - search_path (str): Path to search in for qasm3 imports. Defaults to '.'. + - search_path (str): Path to search in for qasm imports. Defaults to '.'. - target_profile (TargetProfile): The target profile to use for the backend. - executor(ThreadPoolExecutor or other Executor): The executor to be used to submit the job. @@ -124,13 +124,13 @@ def run( options["params"] = params return self._run(run_input, **options) - def _estimate_qasm3( + def _estimate_qasm( self, source: str, **input_params, ) -> Dict[str, Any]: """ - Estimates the resource usage of a QASM3 source code. + Estimates the resource usage of a QASM source code. """ params = input_params.pop("params", None) if params is None: @@ -148,7 +148,7 @@ def _estimate_qasm3( "search_path": input_params.pop("search_path", "."), } kwargs.update(input_params) - res_str = resource_estimate_qasm3( + res_str = resource_estimate_qasm( source, param_str, read_file, @@ -162,7 +162,7 @@ def _estimate_qasm3( def _execute(self, programs: List[Compilation], **input_params) -> Dict: exec_results = [ - (program, self._estimate_qasm3(program.qasm, **input_params)) + (program, self._estimate_qasm(program.qasm, **input_params)) for program in programs ] success = ( diff --git a/pip/src/interop.rs b/pip/src/interop.rs index 985159d95a..368cba06e1 100644 --- a/pip/src/interop.rs +++ b/pip/src/interop.rs @@ -8,16 +8,14 @@ use std::fmt::Write; use pyo3::exceptions::PyException; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList}; +use qsc::hir::PackageId; use qsc::interpret::output::Receiver; use qsc::interpret::{into_errors, Interpreter}; -use qsc::qasm3::io::SourceResolver; -use qsc::qasm3::{ - qasm_to_program, CompilerConfig, OperationSignature, QasmCompileUnit, QubitSemantics, -}; +use qsc::qasm::io::{SourceResolver, SourceResolverContext}; +use qsc::qasm::{OperationSignature, QubitSemantics}; use qsc::target::Profile; use qsc::{ - ast::Package, error::WithSource, interpret, project::FileSystem, LanguageFeatures, - PackageStore, SourceMap, + ast::Package, error::WithSource, interpret, project::FileSystem, LanguageFeatures, SourceMap, }; use qsc::{Backend, PackageType, SparseSim}; @@ -30,13 +28,14 @@ use crate::interpreter::{ use resource_estimator as re; /// `SourceResolver` implementation that uses the provided `FileSystem` -/// to resolve qasm3 include statements. +/// to resolve qasm include statements. pub(crate) struct ImportResolver where T: FileSystem, { fs: T, path: PathBuf, + ctx: SourceResolverContext, } impl ImportResolver @@ -47,6 +46,7 @@ where Self { fs, path: PathBuf::from(path.as_ref()), + ctx: Default::default(), } } } @@ -55,12 +55,23 @@ impl SourceResolver for ImportResolver where T: FileSystem, { - fn resolve

(&self, path: P) -> miette::Result<(PathBuf, String)> + fn ctx(&mut self) -> &mut SourceResolverContext { + &mut self.ctx + } + + fn resolve

(&mut self, path: P) -> miette::Result<(PathBuf, String), qsc::qasm::io::Error> where P: AsRef, { - let path = self.path.join(path); - let (path, source) = self.fs.read_file(path.as_ref())?; + let path = self + .fs + .resolve_path(self.path.as_path(), path.as_ref()) + .map_err(|e| qsc::qasm::io::Error(qsc::qasm::io::ErrorKind::IO(e.to_string())))?; + self.ctx().check_include_errors(&path)?; + let (path, source) = self + .fs + .read_file(path.as_ref()) + .map_err(|e| qsc::qasm::io::Error(qsc::qasm::io::ErrorKind::IO(e.to_string())))?; Ok(( PathBuf::from(path.as_ref().to_owned()), source.as_ref().to_owned(), @@ -76,7 +87,7 @@ where #[pyo3( signature = (source, callback=None, read_file=None, list_directory=None, resolve_path=None, fetch_github=None, **kwargs) )] -pub fn run_qasm3( +pub fn run_qasm( py: Python, source: &str, callback: Option, @@ -97,12 +108,12 @@ pub fn run_qasm3( let search_path = get_search_path(&kwargs)?; let fs = create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github); - let resolver = ImportResolver::new(fs, PathBuf::from(search_path)); + let mut resolver = ImportResolver::new(fs, PathBuf::from(search_path)); let (package, source_map, signature) = compile_qasm_enriching_errors( source, &operation_name, - &resolver, + &mut resolver, ProgramType::File, OutputSemantics::Qiskit, false, @@ -152,7 +163,7 @@ pub(crate) fn run_ast( #[pyo3( signature = (source, job_params, read_file, list_directory, resolve_path, fetch_github, **kwargs) )] -pub(crate) fn resource_estimate_qasm3( +pub(crate) fn resource_estimate_qasm( py: Python, source: &str, job_params: &str, @@ -168,20 +179,20 @@ pub(crate) fn resource_estimate_qasm3( let search_path = get_search_path(&kwargs)?; let fs = create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github); - let resolver = ImportResolver::new(fs, PathBuf::from(search_path)); + let mut resolver = ImportResolver::new(fs, PathBuf::from(search_path)); let program_type = ProgramType::File; let output_semantics = OutputSemantics::ResourceEstimation; let (package, source_map, _) = compile_qasm_enriching_errors( source, &operation_name, - &resolver, + &mut resolver, program_type, output_semantics, false, )?; - match crate::interop::estimate_qasm3(package, source_map, job_params) { + match crate::interop::estimate_qasm(package, source_map, job_params) { Ok(estimate) => Ok(estimate), Err(errors) if matches!(errors[0], re::Error::Interpreter(_)) => { Err(QSharpError::new_err(format_errors( @@ -215,7 +226,7 @@ pub(crate) fn resource_estimate_qasm3( #[pyo3( signature = (source, read_file, list_directory, resolve_path, fetch_github, **kwargs) )] -pub(crate) fn compile_qasm3_to_qir( +pub(crate) fn compile_qasm_to_qir( py: Python, source: &str, read_file: Option, @@ -231,14 +242,14 @@ pub(crate) fn compile_qasm3_to_qir( let search_path = get_search_path(&kwargs)?; let fs = create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github); - let resolver = ImportResolver::new(fs, PathBuf::from(search_path)); + let mut resolver = ImportResolver::new(fs, PathBuf::from(search_path)); let program_ty = get_program_type(&kwargs)?; let output_semantics = get_output_semantics(&kwargs)?; let (package, source_map, signature) = compile_qasm_enriching_errors( source, &operation_name, - &resolver, + &mut resolver, program_ty, output_semantics, false, @@ -254,67 +265,28 @@ pub(crate) fn compile_qasm3_to_qir( generate_qir_from_ast(entry_expr, &mut interpreter) } -pub(crate) fn compile_qasm, R: SourceResolver>( - source: S, - operation_name: S, - resolver: &R, - program_ty: ProgramType, - output_semantics: OutputSemantics, -) -> PyResult { - let parse_result = qsc::qasm3::parse::parse_source( - source, - format!("{}.qasm", operation_name.as_ref()), - resolver, - ) - .map_err(|report| { - // this will only fail if a file cannot be read - // most likely due to a missing file or search path - QasmError::new_err(format!("{report:?}")) - })?; - - if parse_result.has_errors() { - return Err(QasmError::new_err(format_qasm_errors( - parse_result.errors(), - ))); - } - let unit = qasm_to_program( - parse_result.source, - parse_result.source_map, - CompilerConfig::new( - QubitSemantics::Qiskit, - output_semantics.into(), - program_ty.into(), - Some(operation_name.as_ref().into()), - None, - ), - ); - - if unit.has_errors() { - return Err(QasmError::new_err(format_qasm_errors(unit.errors()))); - } - Ok(unit) -} - pub(crate) fn compile_qasm_enriching_errors, R: SourceResolver>( source: S, operation_name: S, - resolver: &R, + resolver: &mut R, program_ty: ProgramType, output_semantics: OutputSemantics, allow_input_params: bool, ) -> PyResult<(Package, SourceMap, OperationSignature)> { - let unit = compile_qasm( - source, - operation_name, - resolver, - program_ty, - output_semantics, - )?; + let path = format!("{}.qasm", operation_name.as_ref()); + let config = qsc::qasm::CompilerConfig::new( + QubitSemantics::Qiskit, + output_semantics.into(), + program_ty.into(), + Some(operation_name.as_ref().into()), + None, + ); + let unit = qsc::qasm::compile_to_qsharp_ast_with_config(source, path, Some(resolver), config); - if unit.has_errors() { - return Err(QasmError::new_err(format_qasm_errors(unit.errors()))); + let (source_map, errors, package, sig) = unit.into_tuple(); + if !errors.is_empty() { + return Err(QasmError::new_err(format_qasm_errors(errors))); } - let (source_map, _, package, sig) = unit.into_tuple(); let Some(package) = package else { return Err(QasmError::new_err("package should have had value")); }; @@ -355,7 +327,7 @@ fn generate_qir_from_ast>( #[pyo3( signature = (source, read_file, list_directory, resolve_path, fetch_github, **kwargs) )] -pub(crate) fn compile_qasm3_to_qsharp( +pub(crate) fn compile_qasm_to_qsharp( py: Python, source: &str, read_file: Option, @@ -370,14 +342,14 @@ pub(crate) fn compile_qasm3_to_qsharp( let search_path = get_search_path(&kwargs)?; let fs = create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github); - let resolver = ImportResolver::new(fs, PathBuf::from(search_path)); + let mut resolver = ImportResolver::new(fs, PathBuf::from(search_path)); let program_ty = get_program_type(&kwargs)?; let output_semantics = get_output_semantics(&kwargs)?; let (package, _, _) = compile_qasm_enriching_errors( source, &operation_name, - &resolver, + &mut resolver, program_ty, output_semantics, true, @@ -467,10 +439,10 @@ fn map_qirgen_errors(errors: Vec) -> PyErr { QSharpError::new_err(message) } -/// Estimates the resources required to run a QASM3 program +/// Estimates the resources required to run a QASM program /// represented by the provided AST. The source map is used for /// error reporting during compilation or runtime. -fn estimate_qasm3( +fn estimate_qasm( ast_package: Package, source_map: SourceMap, params: &str, @@ -495,8 +467,8 @@ fn into_estimation_errors(errors: Vec) -> Vec>() } -/// Formats a list of QASM3 errors into a single string. -pub(crate) fn format_qasm_errors(errors: Vec>) -> String { +/// Formats a list of QASM errors into a single string. +pub(crate) fn format_qasm_errors(errors: Vec>) -> String { errors .into_iter() .map(|e| { @@ -537,12 +509,14 @@ fn create_interpreter_from_ast( language_features: LanguageFeatures, package_type: PackageType, ) -> Result> { - let mut store = PackageStore::new(qsc::compile::core()); - let mut dependencies = Vec::new(); - let capabilities = profile.into(); + let (stdid, qasmid, mut store) = qsc::qasm::package_store_with_qasm(capabilities); + let dependencies = vec![ + (PackageId::CORE, None), + (stdid, None), + (qasmid, Some("QasmStd".into())), + ]; - dependencies.push((store.insert(qsc::compile::std(&store, capabilities)), None)); let (mut unit, errors) = qsc::compile::compile_ast( &store, &dependencies, diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs index 38d5b6505f..225ab5cd83 100644 --- a/pip/src/interpreter.rs +++ b/pip/src/interpreter.rs @@ -5,8 +5,8 @@ use crate::{ displayable_output::{DisplayableMatrix, DisplayableOutput, DisplayableState}, fs::file_system, interop::{ - compile_qasm3_to_qir, compile_qasm3_to_qsharp, compile_qasm_enriching_errors, - map_entry_compilation_errors, resource_estimate_qasm3, run_ast, run_qasm3, ImportResolver, + compile_qasm_enriching_errors, compile_qasm_to_qir, compile_qasm_to_qsharp, + map_entry_compilation_errors, resource_estimate_qasm, run_ast, run_qasm, ImportResolver, }, noisy_simulator::register_noisy_simulator_submodule, }; @@ -79,12 +79,12 @@ fn _native<'a>(py: Python<'a>, m: &Bound<'a, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(physical_estimates, m)?)?; m.add("QSharpError", py.get_type::())?; register_noisy_simulator_submodule(py, m)?; - // QASM3 interop + // QASM interop m.add("QasmError", py.get_type::())?; - m.add_function(wrap_pyfunction!(resource_estimate_qasm3, m)?)?; - m.add_function(wrap_pyfunction!(run_qasm3, m)?)?; - m.add_function(wrap_pyfunction!(compile_qasm3_to_qir, m)?)?; - m.add_function(wrap_pyfunction!(compile_qasm3_to_qsharp, m)?)?; + m.add_function(wrap_pyfunction!(resource_estimate_qasm, m)?)?; + m.add_function(wrap_pyfunction!(run_qasm, m)?)?; + m.add_function(wrap_pyfunction!(compile_qasm_to_qir, m)?)?; + m.add_function(wrap_pyfunction!(compile_qasm_to_qsharp, m)?)?; Ok(()) } @@ -240,12 +240,12 @@ impl OutputSemantics { } } -impl From for qsc::qasm3::OutputSemantics { +impl From for qsc::qasm::OutputSemantics { fn from(output_semantics: OutputSemantics) -> Self { match output_semantics { - OutputSemantics::Qiskit => qsc::qasm3::OutputSemantics::Qiskit, - OutputSemantics::OpenQasm => qsc::qasm3::OutputSemantics::OpenQasm, - OutputSemantics::ResourceEstimation => qsc::qasm3::OutputSemantics::ResourceEstimation, + OutputSemantics::Qiskit => qsc::qasm::OutputSemantics::Qiskit, + OutputSemantics::OpenQasm => qsc::qasm::OutputSemantics::OpenQasm, + OutputSemantics::ResourceEstimation => qsc::qasm::OutputSemantics::ResourceEstimation, } } } @@ -300,12 +300,12 @@ impl ProgramType { } } -impl From for qsc::qasm3::ProgramType { +impl From for qsc::qasm::ProgramType { fn from(output_semantics: ProgramType) -> Self { match output_semantics { - ProgramType::File => qsc::qasm3::ProgramType::File, - ProgramType::Operation => qsc::qasm3::ProgramType::Operation, - ProgramType::Fragments => qsc::qasm3::ProgramType::Fragments, + ProgramType::File => qsc::qasm::ProgramType::File, + ProgramType::Operation => qsc::qasm::ProgramType::Operation, + ProgramType::Fragments => qsc::qasm::ProgramType::Fragments, } } } @@ -648,7 +648,7 @@ impl Interpreter { #[pyo3( signature = (source, callback=None, read_file=None, list_directory=None, resolve_path=None, fetch_github=None, **kwargs) )] - pub fn _run_qasm3( + pub fn _run_qasm( &mut self, py: Python, source: &str, @@ -677,12 +677,12 @@ impl Interpreter { resolve_path, fetch_github, ); - let resolver = ImportResolver::new(fs, PathBuf::from(search_path)); + let mut resolver = ImportResolver::new(fs, PathBuf::from(search_path)); let (package, _source_map, signature) = compile_qasm_enriching_errors( source, &operation_name, - &resolver, + &mut resolver, program_type, output_semantics, false, diff --git a/pip/tests-integration/interop_qiskit/test_circuits/__init__.py b/pip/tests-integration/interop_qiskit/test_circuits/__init__.py index b8acfdb654..ab3c703eb6 100644 --- a/pip/tests-integration/interop_qiskit/test_circuits/__init__.py +++ b/pip/tests-integration/interop_qiskit/test_circuits/__init__.py @@ -15,14 +15,14 @@ def generate_repro_information( message += "\n" try: - qasm3_source = backend._qasm3(circuit, **options) - message += "QASM3 source:" + qasm_source = backend._qasm(circuit, **options) + message += "QASM source:" message += "\n" - message += str(qasm3_source) + message += str(qasm_source) except Exception as ex: # if the conversion fails, print the circuit as a string - # as a fallback since we won't have the qasm3 source - message += "\nFailed converting QuantumCircuit to QASM3:\n" + # as a fallback since we won't have the qasm source + message += "\nFailed converting QuantumCircuit to QASM:\n" message += str(ex) message += "\n" message += "QuantumCircuit rendered:" @@ -32,7 +32,7 @@ def generate_repro_information( return message try: - qsharp_source = backend.qsharp(circuit, **options) + qsharp_source = backend._qsharp(circuit, **options) message += "Q# source:" message += "\n" message += str(qsharp_source) diff --git a/pip/tests-integration/interop_qiskit/test_gateset_qasm.py b/pip/tests-integration/interop_qiskit/test_gateset_qasm.py index 68245e33ce..1fbe79006a 100644 --- a/pip/tests-integration/interop_qiskit/test_gateset_qasm.py +++ b/pip/tests-integration/interop_qiskit/test_gateset_qasm.py @@ -19,7 +19,7 @@ def run_transpile_test( if "optimization_level" not in options: # Use no optimization so gate transpilation is consistent options["optimization_level"] = 0 - info = QSharpBackend()._qasm3(circuit, **options) + info = QSharpBackend()._qasm(circuit, **options) lines = info.splitlines() # remove the first four lines, which are the header # OPENQASM 3.0; diff --git a/pip/tests-integration/interop_qiskit/test_qir.py b/pip/tests-integration/interop_qiskit/test_qir.py index bc48475f83..40fbcebc3d 100644 --- a/pip/tests-integration/interop_qiskit/test_qir.py +++ b/pip/tests-integration/interop_qiskit/test_qir.py @@ -135,7 +135,7 @@ def test_generating_qir_without_registers_raises(): try: with pytest.raises(QasmError) as ex: _ = backend.qir(circuit) - assert "Qiskit circuits must have output registers." in str(ex) + assert "Qiskit circuits must have output registers" in str(ex) except AssertionError: raise except Exception as ex: diff --git a/pip/tests-integration/interop_qiskit/test_qsharp.py b/pip/tests-integration/interop_qiskit/test_qsharp.py index a92bdb0385..7f8b0a0ce8 100644 --- a/pip/tests-integration/interop_qiskit/test_qsharp.py +++ b/pip/tests-integration/interop_qiskit/test_qsharp.py @@ -26,7 +26,7 @@ def test_qsharp_smoke() -> None: backend = QSharpBackend() res = backend._qsharp(circuit) assert res is not None - assert "qasm3_import" in res + assert "qasm_import" in res assert "operation smoke() : Result[]" in res assert "Microsoft.Quantum.Arrays.Reversed" in res diff --git a/pip/tests-integration/interop_qiskit/test_re.py b/pip/tests-integration/interop_qiskit/test_re.py index d4dee46571..b7b01d716e 100644 --- a/pip/tests-integration/interop_qiskit/test_re.py +++ b/pip/tests-integration/interop_qiskit/test_re.py @@ -69,8 +69,8 @@ def test_estimate_qiskit_rgqft_multiplier() -> None: { "numQubits": 16, "tCount": 90, - "rotationCount": 1002, - "rotationDepth": 680, + "rotationCount": 972, + "rotationDepth": 666, "cczCount": 0, "ccixCount": 0, "measurementCount": 0, @@ -94,8 +94,8 @@ def test_estimate_qiskit_rgqft_multiplier_in_threadpool() -> None: { "numQubits": 16, "tCount": 90, - "rotationCount": 1002, - "rotationDepth": 680, + "rotationCount": 972, + "rotationDepth": 666, "cczCount": 0, "ccixCount": 0, "measurementCount": 0, diff --git a/pip/tests-integration/interop_qiskit/test_run_sim.py b/pip/tests-integration/interop_qiskit/test_run_sim.py index ad3c8c475c..627a7d9399 100644 --- a/pip/tests-integration/interop_qiskit/test_run_sim.py +++ b/pip/tests-integration/interop_qiskit/test_run_sim.py @@ -22,6 +22,15 @@ ) +# Convert the circuit to QASM3 and back to a backend. +# Then load the QASM3 and convert it back to a circuit. +# This is to ensure that the QASM3 conversion is semantically correct. +def round_trip_circuit(circuit, backend): + qasm = backend._qasm(circuit) + circuit = from_qasm3(qasm) + return circuit + + @pytest.mark.skipif(not QISKIT_AVAILABLE, reason=SKIP_REASON) def test_run_smoke() -> None: circuit = QuantumCircuit(2, 2) @@ -50,8 +59,7 @@ def test_get_counts_matches_qiskit_simulator(): backend = QSharpBackend(target_profile=target_profile) try: - qasm3 = backend._qasm3(circuit) - circuit = from_qasm3(qasm3) + circuit = round_trip_circuit(circuit, backend) aersim = AerSimulator() job = aersim.run(circuit, shots=5) @@ -125,8 +133,7 @@ def test_get_counts_matches_qiskit_simulator_multiple_circuits(): backend = QSharpBackend(target_profile=target_profile) try: - qasm3 = backend._qasm3(circuit) - circuit = from_qasm3(qasm3) + circuit = round_trip_circuit(circuit, backend) aersim = AerSimulator() job = aersim.run([circuit, circuit2], shots=5)