diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 7130d62d82..26938b9963 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, qasm3] + 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 e65899b1bc..1e8fde3103 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1476,11 +1476,13 @@ version = "0.0.0" dependencies = [ "bitflags 2.6.0", "difference", + "enum-iterator", "expect-test", + "indenter", "indoc", "miette", "num-bigint", - "oq3_lexer", + "num-traits", "oq3_parser", "oq3_semantics", "oq3_source_file", @@ -1489,7 +1491,9 @@ dependencies = [ "qsc_ast", "qsc_data_structures", "qsc_frontend", + "qsc_hir", "qsc_parse", + "qsc_passes", "qsc_qasm3", "rustc-hash", "thiserror", diff --git a/compiler/qsc/src/codegen.rs b/compiler/qsc/src/codegen.rs index 54ad8388c7..3b284f9e86 100644 --- a/compiler/qsc/src/codegen.rs +++ b/compiler/qsc/src/codegen.rs @@ -5,6 +5,7 @@ mod tests; pub mod qsharp { + pub use qsc_codegen::qsharp::write_item_string; pub use qsc_codegen::qsharp::write_package_string; pub use qsc_codegen::qsharp::write_stmt_string; } diff --git a/compiler/qsc/src/lib.rs b/compiler/qsc/src/lib.rs index b5d0ab39c8..00d47cfd25 100644 --- a/compiler/qsc/src/lib.rs +++ b/compiler/qsc/src/lib.rs @@ -76,7 +76,6 @@ pub mod partial_eval { } pub mod qasm3 { - pub use qsc_qasm3::io::*; pub use qsc_qasm3::parse::*; pub use qsc_qasm3::*; } diff --git a/compiler/qsc_codegen/src/qsharp.rs b/compiler/qsc_codegen/src/qsharp.rs index 9b4495df8a..9f40a9025c 100644 --- a/compiler/qsc_codegen/src/qsharp.rs +++ b/compiler/qsc_codegen/src/qsharp.rs @@ -61,6 +61,22 @@ pub fn write_package_string(package: &Package) -> String { format_str(&s) } +#[must_use] +pub fn write_item_string(item: &Item) -> String { + let mut output = Vec::new(); + let mut gen = QSharpGen::new(&mut output); + + gen.visit_item(item); + + let s = match std::str::from_utf8(&output) { + Ok(v) => v.to_owned(), + Err(e) => format!("Invalid UTF-8 sequence: {e}"), + }; + + output.clear(); + format_str(&s) +} + #[must_use] pub fn write_stmt_string(stmt: &ast::Stmt) -> String { let mut output = Vec::new(); diff --git a/compiler/qsc_qasm3/Cargo.toml b/compiler/qsc_qasm3/Cargo.toml index b7a5d9f294..8f95afc821 100644 --- a/compiler/qsc_qasm3/Cargo.toml +++ b/compiler/qsc_qasm3/Cargo.toml @@ -9,18 +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] diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm3/src/ast_builder.rs index 4269334548..e74154743a 100644 --- a/compiler/qsc_qasm3/src/ast_builder.rs +++ b/compiler/qsc_qasm3/src/ast_builder.rs @@ -6,14 +6,17 @@ 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::{ + parser::ast::{list_from_iter, List}, runtime::RuntimeFunctions, + stdlib::angle::Angle, types::{ArrayDimensions, Complex}, }; @@ -281,6 +284,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()); @@ -631,6 +683,15 @@ pub(crate) fn build_cast_call_two_params( build_global_call_with_two_params(name, fst, snd, name_span, operand_span) } +pub(crate) fn build_cast_call_by_name( + name: &str, + expr: ast::Expr, + name_span: Span, + operand_span: Span, +) -> ast::Expr { + build_global_call_with_one_param(name, expr, name_span, operand_span) +} + pub(crate) fn build_cast_call( function: RuntimeFunctions, expr: ast::Expr, @@ -677,6 +738,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, @@ -703,6 +765,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() } } @@ -740,6 +803,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() } } @@ -810,7 +874,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 { @@ -838,7 +902,38 @@ 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: Span::default(), + })))), + ..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,6 +954,14 @@ pub(crate) fn build_stmt_semi_from_expr(expr: Expr) -> Stmt { } } +pub(crate) fn build_stmt_semi_from_expr_with_span(expr: Expr, span: Span) -> Stmt { + Stmt { + id: NodeId::default(), + span, + kind: Box::new(StmtKind::Semi(Box::new(expr))), + } +} + pub(crate) fn build_wrapped_block_expr(block: Block) -> Expr { Expr { id: NodeId::default(), @@ -887,6 +990,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, @@ -998,10 +1189,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,23 +1203,36 @@ 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(), }) } +pub(crate) fn build_top_level_ns_with_item>( + whole_span: Span, + ns: S, + entry: ast::Item, +) -> TopLevelNode { + build_top_level_ns_with_items(whole_span, ns, vec![entry]) +} + pub(crate) fn build_operation_with_stmts>( name: S, input_pats: Vec, 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 +1319,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 +1356,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 +1383,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 +1396,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 +1462,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 { @@ -1279,19 +1486,6 @@ pub(crate) fn build_barrier_call(span: Span) -> Stmt { 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( name: String, cargs: Vec<(String, Ty, Pat)>, @@ -1362,7 +1556,8 @@ pub(crate) fn build_gate_decl( } } -pub(crate) fn build_gate_decl_lambda>( +#[allow(clippy::too_many_arguments, clippy::too_many_lines)] +pub(crate) fn build_lambda>( name: S, cargs: Vec<(String, Ty, Pat)>, qargs: Vec<(String, Ty, Pat)>, @@ -1370,6 +1565,8 @@ pub(crate) fn build_gate_decl_lambda>( name_span: Span, body_span: Span, gate_span: Span, + return_type: Option, + kind: CallableKind, ) -> Stmt { let args = cargs .into_iter() @@ -1404,15 +1601,15 @@ pub(crate) fn build_gate_decl_lambda>( }) .map(Box::new) .collect::>(); - let input_pat = if args.len() > 1 { + let input_pat = if args.len() == 1 { ast::Pat { - kind: Box::new(PatKind::Tuple(name_args.into_boxed_slice())), + kind: Box::new(ast::PatKind::Paren(name_args[0].clone())), span: Span { lo, hi }, ..Default::default() } } else { ast::Pat { - kind: Box::new(ast::PatKind::Paren(name_args[0].clone())), + kind: Box::new(PatKind::Tuple(name_args.into_boxed_slice())), span: Span { lo, hi }, ..Default::default() } @@ -1429,29 +1626,35 @@ pub(crate) fn build_gate_decl_lambda>( let lambda_expr = Expr { id: NodeId::default(), kind: Box::new(ExprKind::Lambda( - CallableKind::Operation, + kind, 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 { + let input_ty = if args.len() == 1 { ast::Ty { - kind: Box::new(ast::TyKind::Tuple(ty_args.into_boxed_slice())), + kind: Box::new(ast::TyKind::Paren(Box::new(ty_args[0].clone()))), ..Default::default() } } else { ast::Ty { - kind: Box::new(ast::TyKind::Paren(Box::new(ty_args[0].clone()))), + kind: Box::new(ast::TyKind::Tuple(ty_args.into_boxed_slice())), ..Default::default() } }; + let return_type = if let Some(ty) = return_type { + ty + } else { + build_path_ident_ty("Unit") + }; + let lambda_ty = ast::Ty { kind: Box::new(ast::TyKind::Arrow( - CallableKind::Operation, + kind, Box::new(input_ty), - Box::new(build_path_ident_ty("Unit")), + Box::new(return_type), None, )), ..Default::default() @@ -1477,6 +1680,104 @@ pub(crate) fn build_gate_decl_lambda>( } } +#[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)>, + body: Option, + name_span: Span, + body_span: Span, + gate_span: Span, + return_type: Ty, + kind: CallableKind, + functors: Option, + attrs: List, +) -> Stmt { + let args = cargs + .into_iter() + .chain(qargs) + .map(|(_, _, pat)| Box::new(pat)) + .collect::>(); + + let lo = args + .iter() + .min_by_key(|x| x.span.lo) + .map(|x| x.span.lo) + .unwrap_or_default(); + + let hi = args + .iter() + .max_by_key(|x| x.span.hi) + .map(|x| x.span.hi) + .unwrap_or_default(); + + let input_pat_kind = if args.len() == 1 { + PatKind::Paren(args[0].clone()) + } else { + PatKind::Tuple(args.into_boxed_slice()) + }; + + let input_pat = Pat { + kind: Box::new(input_pat_kind), + 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, + name: Box::new(Ident { + name: name.into(), + ..Default::default() + }), + generics: Box::new([]), + input: Box::new(input_pat), + 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() + }; + + Stmt { + kind: Box::new(StmtKind::Item(Box::new(item))), + span: gate_span, + ..Default::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 ctl = Box::new(FunctorExpr { + kind: Box::new(FunctorExprKind::Lit(ast::Functor::Ctl)), + id: Default::default(), + span: Default::default(), + }); + + FunctorExpr { + kind: Box::new(FunctorExprKind::BinOp(ast::SetOp::Union, adj, ctl)), + id: Default::default(), + span: Default::default(), + } +} + fn build_idents(idents: &[&str]) -> Option> { let idents = idents .iter() @@ -1491,3 +1792,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_qasm3/src/compile.rs b/compiler/qsc_qasm3/src/compile.rs index abbc10e70d..0d499060af 100644 --- a/compiler/qsc_qasm3/src/compile.rs +++ b/compiler/qsc_qasm3/src/compile.rs @@ -6,13 +6,13 @@ 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_barrier_call, build_binary_expr, build_cast_call, 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_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_indexed_assignment_statement, build_lit_bigint_expr, build_lit_bool_expr, + build_indexed_assignment_statement, build_lambda, 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, @@ -36,8 +36,8 @@ 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, + semantic::SemanticErrorKind, CompilerConfig, OperationSignature, OutputSemantics, ProgramType, + QubitSemantics, }; use ast::NodeId; @@ -68,11 +68,6 @@ pub fn qasm_to_program( 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, @@ -421,7 +416,7 @@ impl QasmCompiler { 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)) + Some(build_attr(name.to_string(), None, span)) } else { let span = span_for_syntax_node(stmt.syntax()); let kind = SemanticErrorKind::UnknownAnnotation(text.to_string(), span); @@ -2323,8 +2318,10 @@ impl QasmCompiler { }; Some(ast_builder::build_for_stmt( - &loop_var_symbol, - iterable, + &loop_var_symbol.name, + loop_var_symbol.span, + &loop_var_symbol.qsharp_ty, + iterable.expr, body, stmt_span, )) @@ -2477,7 +2474,7 @@ impl QasmCompiler { gate_span, )) } else { - Some(build_gate_decl_lambda( + Some(build_lambda( name.to_string(), cargs, qargs, @@ -2485,6 +2482,8 @@ impl QasmCompiler { name_span, body_span, gate_span, + None, + ast::CallableKind::Operation, )) } } @@ -2915,11 +2914,21 @@ impl QasmCompiler { 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::Gate(cargs, qargs) => { + if let (Ok(cargs), Ok(qargs)) = (u32::try_from(*cargs), u32::try_from(*qargs)) { + Some(crate::types::Type::Callable( + crate::types::CallableKind::Operation, + cargs, + qargs, + )) + } else { + let message = format!( + "Gate with {cargs} control and {qargs} qubits has too many arguments" + ); + self.push_unsupported_error_message(message, node); + None + } + } Type::Range => Some(crate::types::Type::Range), Type::Set => todo!("Set to Q# type"), Type::Void => Some(crate::types::Type::Tuple(vec![])), @@ -3087,7 +3096,7 @@ impl QasmCompiler { }, Some(expr) => { let span = span_for_syntax_node(expr.syntax()); - let kind = SemanticErrorKind::DesignatorMustBeIntLiteral(span); + let kind = SemanticErrorKind::DesignatorMustBePositiveIntLiteral(span); self.push_semantic_error(kind); return None; } @@ -3298,7 +3307,7 @@ impl QasmCompiler { 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); + let ty = Type::UInt(None, IsConst::True); return Some(QasmTypedExpr { ty, expr }); } } @@ -3688,7 +3697,7 @@ impl QasmCompiler { .collect::>(); ( - build_operation_with_stmts(name, input_pats, ast_ty, stmts, whole_span), + build_operation_with_stmts(name, input_pats, ast_ty, stmts, whole_span, true), signature, ) } @@ -3777,8 +3786,8 @@ impl QasmCompiler { }; let size_expr = build_lit_int_expr(size, Span::default()); - let expr = build_cast_call_two_params( - RuntimeFunctions::IntAsResultArrayBE, + let expr = build_global_call_with_two_params( + "__IntAsResultArrayBE__", rhs.expr.clone(), size_expr, name_span, @@ -3914,7 +3923,9 @@ impl QasmCompiler { node: &SyntaxNode, ) { let span = span_for_syntax_node(node); - let kind = crate::ErrorKind::Unimplemented(message.as_ref().to_string(), span); + let kind = crate::ErrorKind::Semantic(crate::semantic::Error( + SemanticErrorKind::Unimplemented(message.as_ref().to_string(), span), + )); let error = self.create_err(kind); self.errors.push(error); } @@ -3924,7 +3935,7 @@ impl QasmCompiler { 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 kind = crate::ErrorKind::Semantic(crate::semantic::Error(kind)); let error = self.create_err(kind); self.errors.push(error); } @@ -3938,7 +3949,7 @@ impl QasmCompiler { /// 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 kind = crate::ErrorKind::Semantic(crate::semantic::Error(kind)); let error = self.create_err(kind); self.errors.push(error); } @@ -3946,7 +3957,9 @@ impl QasmCompiler { /// 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 kind = crate::ErrorKind::Semantic(crate::semantic::Error( + SemanticErrorKind::NotSupported(message.as_ref().to_string(), span), + )); let error = self.create_err(kind); self.errors.push(error); } @@ -3955,7 +3968,9 @@ impl QasmCompiler { 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 kind = crate::ErrorKind::Semantic(crate::semantic::Error( + SemanticErrorKind::CalibrationsNotSupported(text, span), + )); let error = self.create_err(kind); self.errors.push(error); } diff --git a/compiler/qsc_qasm3/src/compile/tests.rs b/compiler/qsc_qasm3/src/compile/tests.rs index 33c51c242e..72b0210d14 100644 --- a/compiler/qsc_qasm3/src/compile/tests.rs +++ b/compiler/qsc_qasm3/src/compile/tests.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::tests::{parse_all, print_compilation_errors, qasm_to_program_fragments}; +use crate::tests::{compile_all_fragments, print_compilation_errors}; use miette::Report; #[test] @@ -18,9 +18,7 @@ fn programs_with_includes_with_includes_can_be_compiled() -> miette::Result<(), ("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); + let unit = compile_all_fragments("source0.qasm", all_sources)?; print_compilation_errors(&unit); assert!(!unit.has_errors()); Ok(()) @@ -41,10 +39,7 @@ fn including_stdgates_multiple_times_causes_symbol_redifintion_errors( ("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); - + let unit = compile_all_fragments("source0.qasm", all_sources)?; assert!(unit.has_errors()); for error in unit.errors() { assert!(error.to_string().contains("Redefined symbol: ")); diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs new file mode 100644 index 0000000000..0ef44dba90 --- /dev/null +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -0,0 +1,1922 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use core::f64; +use std::{path::Path, rc::Rc}; + +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_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, + }, + 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}, + SemanticErrorKind, + }, + 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_with_config(source: S, path: P, config: CompilerConfig) -> QasmCompileUnit +where + S: AsRef, + P: AsRef, +{ + let res = 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 = Span::default(); + 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 = Span::default(); + 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() + } + } + + 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 = SemanticErrorKind::NotSupported(message, input.span); + self.push_semantic_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}"); + // 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.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 = SemanticErrorKind::QiskitEntryPointMissingOutput(whole_span); + self.push_semantic_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_semantic_error(SemanticErrorKind::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( + symbol.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, + 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 QASM3 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 = symbol.span; + if expr.args.len() > 0 { + 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) + } + } else { + build_call_no_params(name, &[], 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, symbol.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.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 = SemanticErrorKind::InvalidNumberOfQubitArgs( + *num_ctrls as usize, + qubits.len(), + modifier.span, + ); + self.push_semantic_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.span, + ); + } + semast::GateModifierKind::NegCtrl(num_ctrls) => { + // remove the last n qubits from the qubit list + if qubits.len() < *num_ctrls as usize { + let kind = SemanticErrorKind::InvalidNumberOfQubitArgs( + *num_ctrls as usize, + qubits.len(), + modifier.span, + ); + self.push_semantic_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.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 the stmts list. + // TODO: This may be an issue for tooling as there isn't a way to have a forward + // declared varible in Q#. + if symbol.io_kind != IOKind::Output { + //self.push_semantic_error(SemanticErrorKind::InvalidIODeclaration(stmt.span)); + 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(); + + 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, + symbol.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_semantic_error(SemanticErrorKind::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), + 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) -> qsast::Expr { + let symbol = &self.symbols[symbol_id]; + let span = symbol.span; + 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); + qsast::Expr { + id: qsast::NodeId::default(), + span, + kind: Box::new(qsast::ExprKind::Index( + Box::new(ident), + Box::new(index[0].clone()), + )), + } + } + + 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 = SemanticErrorKind::NotSupported(message.as_ref().to_string(), 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 = 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_qasm3/src/io.rs b/compiler/qsc_qasm3/src/io.rs index 79df8d5608..bc705f037f 100644 --- a/compiler/qsc_qasm3/src/io.rs +++ b/compiler/qsc_qasm3/src/io.rs @@ -1,6 +1,10 @@ // 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, @@ -8,30 +12,27 @@ use std::{ 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)> + fn resolve

(&self, path: P) -> miette::Result<(PathBuf, String), Error> where P: AsRef, { let path = std::fs::canonicalize(path).map_err(|e| { - crate::Error(crate::ErrorKind::IO(format!( + Error(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!( + Err(_) => Err(Error(ErrorKind::NotFound(format!( "Could not resolve include file: {}", path.display() - )))) - .into_diagnostic(), + )))), } } #[cfg(not(feature = "fs"))] @@ -62,18 +63,17 @@ impl FromIterator<(Arc, Arc)> for InMemorySourceResolver { } impl SourceResolver for InMemorySourceResolver { - fn resolve

(&self, path: P) -> miette::Result<(PathBuf, String)> + fn resolve

(&self, path: P) -> miette::Result<(PathBuf, String), Error> 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!( + None => Err(Error(ErrorKind::NotFound(format!( "Could not resolve include file: {}", path.display() - )))) - .into_diagnostic(), + )))), } } } diff --git a/compiler/qsc_qasm3/src/io/error.rs b/compiler/qsc_qasm3/src/io/error.rs new file mode 100644 index 0000000000..1a3b40ecf3 --- /dev/null +++ b/compiler/qsc_qasm3/src/io/error.rs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use miette::Diagnostic; +use thiserror::Error; + +#[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), +} + +impl From for crate::Error { + fn from(val: Error) -> Self { + crate::Error(crate::ErrorKind::IO(val)) + } +} diff --git a/compiler/qsc_qasm3/src/keyword.rs b/compiler/qsc_qasm3/src/keyword.rs new file mode 100644 index 0000000000..2a2a07e8e3 --- /dev/null +++ b/compiler/qsc_qasm3/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, + Else, + End, + Extern, + False, + For, + Gate, + GPhase, + 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::Else => "else", + Keyword::End => "end", + Keyword::Extern => "extern", + Keyword::False => "false", + Keyword::For => "for", + Keyword::Gate => "gate", + Keyword::GPhase => "gphase", + 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), + "else" => Ok(Self::Else), + "end" => Ok(Self::End), + "extern" => Ok(Self::Extern), + "false" => Ok(Self::False), + "for" => Ok(Self::For), + "gate" => Ok(Self::Gate), + "gphase" => Ok(Self::GPhase), + "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_qasm3/src/lex.rs b/compiler/qsc_qasm3/src/lex.rs new file mode 100644 index 0000000000..16257a1ea0 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/lex/cooked.rs b/compiler/qsc_qasm3/src/lex/cooked.rs new file mode 100644 index 0000000000..78f1008158 --- /dev/null +++ b/compiler/qsc_qasm3/src/lex/cooked.rs @@ -0,0 +1,774 @@ +// 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("Qasm3.Lex.Incomplete"))] + Incomplete(raw::TokenKind, TokenKind, raw::TokenKind, #[label] Span), + + #[error("expected {0} to complete {1}, found EOF")] + #[diagnostic(code("Qasm3.Lex.IncompleteEof"))] + IncompleteEof(raw::TokenKind, TokenKind, #[label] Span), + + #[error("unterminated string literal")] + #[diagnostic(code("Qasm3.Lex.UnterminatedString"))] + UnterminatedString(#[label] Span), + + #[error("string literal with an invalid escape sequence")] + #[diagnostic(code("Qasm3.Lex.InvalidEscapeSequence"))] + InvalidEscapeSequence(#[label] Span), + + #[error("unrecognized character `{0}`")] + #[diagnostic(code("Qasm3.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 { + Annotation, + Pragma, + Keyword(Keyword), + Type(Type), + + // Builtin identifiers and operations + GPhase, + Inv, + Pow, + Ctrl, + NegCtrl, + Dim, + DurationOf, + Measure, + + 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::Inv => write!(f, "inv"), + TokenKind::Pow => write!(f, "pow"), + TokenKind::Ctrl => write!(f, "ctrl"), + TokenKind::NegCtrl => write!(f, "negctrl"), + TokenKind::Dim => write!(f, "dim"), + TokenKind::DurationOf => write!(f, "durationof"), + TokenKind::Measure => write!(f, "measure"), + 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 { + // A `dim` token without a `#` in front should be + // treated as an identifier and not as a keyword. + TokenKind::Dim => Ok(Some(TokenKind::Identifier)), + 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::Dim => Ok(Some(TokenKind::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, + "inv" => TokenKind::Inv, + "pow" => TokenKind::Pow, + "ctrl" => TokenKind::Ctrl, + "negctrl" => TokenKind::NegCtrl, + "dim" => TokenKind::Dim, + "durationof" => TokenKind::DurationOf, + "measure" => TokenKind::Measure, + 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_qasm3/src/lex/cooked/tests.rs b/compiler/qsc_qasm3/src/lex/cooked/tests.rs new file mode 100644 index 0000000000..c721b5e97b --- /dev/null +++ b/compiler/qsc_qasm3/src/lex/cooked/tests.rs @@ -0,0 +1,1259 @@ +// 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::Inv => Some("inv".to_string()), + TokenKind::Pow => Some("pow".to_string()), + TokenKind::Ctrl => Some("ctrl".to_string()), + TokenKind::NegCtrl => Some("negctrl".to_string()), + TokenKind::Dim => Some("dim".to_string()), + TokenKind::DurationOf => Some("durationof".to_string()), + TokenKind::Measure => Some("measure".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: Identifier, + span: Span { + lo: 0, + hi: 3, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn sharp_dim() { + check( + "#dim", + &expect![[r#" + [ + Ok( + Token { + kind: Dim, + span: Span { + lo: 0, + hi: 4, + }, + }, + ), + ] + "#]], + ); +} diff --git a/compiler/qsc_qasm3/src/lex/raw.rs b/compiler/qsc_qasm3/src/lex/raw.rs new file mode 100644 index 0000000000..4e0be3285d --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/lex/raw/tests.rs b/compiler/qsc_qasm3/src/lex/raw/tests.rs new file mode 100644 index 0000000000..aee13c0ab1 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/lib.rs b/compiler/qsc_qasm3/src/lib.rs index b1d6fd35c1..c450949440 100644 --- a/compiler/qsc_qasm3/src/lib.rs +++ b/compiler/qsc_qasm3/src/lib.rs @@ -1,15 +1,25 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -mod angle; +// while we work through the conversion, allow dead code to avoid warnings +#![allow(dead_code)] + mod ast_builder; mod compile; +mod compiler; +mod stdlib; pub use compile::qasm_to_program; +pub use compiler::compile_with_config; +pub use stdlib::package_store_with_qasm; pub mod io; +mod keyword; +mod lex; mod oqasm_helpers; mod oqasm_types; pub mod parse; +pub mod parser; mod runtime; +pub mod semantic; mod symbols; mod types; @@ -54,317 +64,38 @@ impl Error { /// - Semantic errors /// - IO errors #[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] +#[error(transparent)] 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(transparent)] + #[diagnostic(transparent)] + IO(#[from] crate::io::Error), #[error("QASM3 Parse Error: {0}")] Parse(String, #[label] Span), #[error(transparent)] #[diagnostic(transparent)] - Semantic(#[from] crate::SemanticError), + Parser(#[from] crate::parser::Error), + #[error(transparent)] + #[diagnostic(transparent)] + Semantic(#[from] crate::semantic::Error), + #[error(transparent)] + #[diagnostic(transparent)] + ConstEval(#[from] crate::semantic::ast::const_eval::ConstEvalError), #[error("QASM3 Parse Error: Not Found {0}")] NotFound(String), #[error("IO Error: {0}")] - IO(String), + OldIO(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::IO(error) => Self::IO(error), ErrorKind::Parse(error, span) => Self::Parse(error, span + offset), - ErrorKind::Semantic(error) => ErrorKind::Semantic(error.with_offset(offset)), + ErrorKind::Parser(error) => Self::Parser(error.with_offset(offset)), + ErrorKind::Semantic(error) => Self::Semantic(error.with_offset(offset)), + ErrorKind::ConstEval(error) => Self::ConstEval(error), 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) - } + ErrorKind::OldIO(error) => Self::OldIO(error), } } } diff --git a/compiler/qsc_qasm3/src/oqasm_helpers.rs b/compiler/qsc_qasm3/src/oqasm_helpers.rs index bbac93ad6e..344139d49a 100644 --- a/compiler/qsc_qasm3/src/oqasm_helpers.rs +++ b/compiler/qsc_qasm3/src/oqasm_helpers.rs @@ -50,9 +50,11 @@ pub(crate) fn safe_u128_to_f64(value: u128) -> Option { } } +/// `i64` is 64 bits wide, but `f64`'s mantissa is only 53 bits wide pub(crate) fn safe_i64_to_f64(value: i64) -> Option { - #[allow(clippy::cast_possible_truncation)] - if value <= f64::MAX as i64 { + 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 { @@ -61,9 +63,8 @@ pub(crate) fn safe_i64_to_f64(value: i64) -> Option { } 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 { + 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 { diff --git a/compiler/qsc_qasm3/src/parse.rs b/compiler/qsc_qasm3/src/parse.rs index be6c74ff1d..99c77efccc 100644 --- a/compiler/qsc_qasm3/src/parse.rs +++ b/compiler/qsc_qasm3/src/parse.rs @@ -91,9 +91,7 @@ fn create_source_map(source: &QasmSource) -> SourceMap { 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()))) + SourceMap::new(files, None) } /// Recursively collect all source files from the includes diff --git a/compiler/qsc_qasm3/src/parser.rs b/compiler/qsc_qasm3/src/parser.rs new file mode 100644 index 0000000000..69b9e9204b --- /dev/null +++ b/compiler/qsc_qasm3/src/parser.rs @@ -0,0 +1,312 @@ +// 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; + +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: &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: &R) -> QasmSource +where + P: AsRef, + R: SourceResolver, +{ + match resolver.resolve(&path) { + Ok((path, source)) => parse_qasm_source(source, path, resolver), + 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: &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: &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: &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_qasm3/src/parser/ast.rs b/compiler/qsc_qasm3/src/parser/ast.rs new file mode 100644 index 0000000000..6f974e5180 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/ast.rs @@ -0,0 +1,1807 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +pub(crate) mod display_utils; + +use 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, +}; + +// TODO: Profile this with iai-callgrind in a large OpenQASM3 +// sample to verify that is actually faster than using Vec. +// Even though Box uses less stack space, it reduces cache +// locality, because now you need to be jumping around in +// memory to read contiguous elements of a list. +/// 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 kind: GateModifierKind, +} + +impl Display for QuantumGateModifier { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "QuantumGateModifier {}: {}", self.span, 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_qasm3/src/parser/ast/display_utils.rs b/compiler/qsc_qasm3/src/parser/ast/display_utils.rs new file mode 100644 index 0000000000..4ffbb29042 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/ast/display_utils.rs @@ -0,0 +1,206 @@ +// 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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_qasm3/src/parser/completion.rs b/compiler/qsc_qasm3/src/parser/completion.rs new file mode 100644 index 0000000000..ae8f8e57e9 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/completion.rs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +pub(crate) mod collector; +#[cfg(test)] +mod tests; +pub(crate) mod word_kinds; + +pub(crate) use collector::ValidWordCollector; +pub(crate) 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() +} + +/// Returns the words that would be valid syntax at a particular offset +/// in the given notebook cell (using the fragments parser). +/// +/// This is useful for providing completions in an editor. +#[must_use] +pub fn possible_words_at_offset_in_fragments(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_qasm3/src/parser/completion/collector.rs b/compiler/qsc_qasm3/src/parser/completion/collector.rs new file mode 100644 index 0000000000..5339058d2f --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/completion/collector.rs @@ -0,0 +1,186 @@ +// 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::{ClosedBinOp, 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::ClosedBinOp(ClosedBinOp::AmpAmp | ClosedBinOp::BarBar) + | 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_qasm3/src/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs new file mode 100644 index 0000000000..13d3ebe40b --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -0,0 +1,55 @@ +// 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); +} + +fn check_valid_words_no_source_name(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 | Def | DefCal | DefCalGrammar | Delay | End | Extern | False | For | Gate | If | Include | Input | Let | OpenQASM | Output | 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 | Def | DefCal | DefCalGrammar | Delay | End | Extern | False | For | Gate | If | Include | Input | Let | Output | Pragma | QReg | Qubit | Reset | True | Return | Switch | While, + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm3/src/parser/completion/word_kinds.rs b/compiler/qsc_qasm3/src/parser/completion/word_kinds.rs new file mode 100644 index 0000000000..8ad18f770d --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/completion/word_kinds.rs @@ -0,0 +1,175 @@ +// 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. Callables, UDT constructors, local variables. + 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; + /// A primitive class. + const PrimitiveClass = 1 << 2; + + + // + // End names. + // + + // + // Begin hardcoded identifiers. + // + + /// An annotation, without the leading `@`. + const Annotation = 1 << 3; + + + // + // 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 GPhase = keyword_bit(Keyword::GPhase); + 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 = 4; +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_qasm3/src/parser/error.rs b/compiler/qsc_qasm3/src/parser/error.rs new file mode 100644 index 0000000000..99042af191 --- /dev/null +++ b/compiler/qsc_qasm3/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("Qasm3.Parse.Literal"))] + Lit(&'static str, #[label] Span), + #[error("unknown escape sequence: `{0}`")] + #[diagnostic(code("Qasm3.Parse.Escape"))] + Escape(char, #[label] Span), + #[error("expected {0}, found {1}")] + #[diagnostic(code("Qasm3.Parse.Token"))] + Token(TokenKind, TokenKind, #[label] Span), + #[error("Empty statements are not supported")] + #[diagnostic(code("Qasm3.Parse.EmptyStatement"))] + EmptyStatement(#[label] Span), + #[error("Annotation missing target statement.")] + #[diagnostic(code("Qasm3.Parse.FloatingAnnotation"))] + FloatingAnnotation(#[label] Span), + #[error("expected {0}, found {1}")] + #[diagnostic(code("Qasm3.Parse.Rule"))] + Rule(&'static str, TokenKind, #[label] Span), + #[error("expected {0}, found {1}")] + #[diagnostic(code("Qasm3.Parse.Convert"))] + Convert(&'static str, &'static str, #[label] Span), + #[error("expected statement to end with a semicolon")] + #[diagnostic(code("Qasm3.Parse.MissingSemi"))] + MissingSemi(#[label] Span), + #[error("expected inputs to be parenthesized")] + #[diagnostic(code("Qasm3.Parse.MissingParens"))] + MissingParens(#[label] Span), + #[error("missing entry in sequence")] + #[diagnostic(code("Qasm3.Parse.MissingSeqEntry"))] + MissingSeqEntry(#[label] Span), + #[error("missing switch statement cases")] + #[diagnostic(code("Qasm3.Parse.MissingSwitchCases"))] + MissingSwitchCases(#[label] Span), + #[error("missing switch statement case labels")] + #[diagnostic(code("Qasm3.Parse.MissingSwitchCaseLabels"))] + MissingSwitchCaseLabels(#[label] Span), + #[error("missing switch statement case labels")] + #[diagnostic(code("Qasm3.Parse.MissingGateCallOperands"))] + MissingGateCallOperands(#[label] Span), + #[error("expected an item or closing brace, found {0}")] + #[diagnostic(code("Qasm3.Parse.ExpectedItem"))] + ExpectedItem(TokenKind, #[label] Span), + #[error("gphase gate requires exactly one angle")] + #[diagnostic(code("Qasm3.Parse.GPhaseInvalidArguments"))] + GPhaseInvalidArguments(#[label] Span), + #[error("invalid gate call designator")] + #[diagnostic(code("Qasm3.Parse.InvalidGateCallDesignator"))] + InvalidGateCallDesignator(#[label] Span), + #[error("multiple index operators are only allowed in assignments")] + #[diagnostic(code("Qasm3.Parse.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_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs new file mode 100644 index 0000000000..ae69817719 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -0,0 +1,838 @@ +// 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::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, +} + +// TODO: This seems to be an unnecessary wrapper. +// OpName::Keyword is never used. +// Consider removing. +#[derive(Clone, Copy)] +enum OpName { + Token(TokenKind), + Keyword(Keyword), +} + +// TODO: This seems to be an unnecessary wrapper. +// We ended up removing the OpContext::Stmt variant. +// Consider removing. +#[derive(Clone, Copy)] +enum OpContext { + Precedence(u8), +} + +#[derive(Clone, Copy)] +enum Assoc { + Left, + Right, +} + +const RANGE_PRECEDENCE: u8 = 1; + +pub(super) fn expr(s: &mut ParserContext) -> Result { + expr_op(s, OpContext::Precedence(0)) +} + +pub(super) fn expr_with_lhs(s: &mut ParserContext, lhs: Expr) -> Result { + expr_op_with_lhs(s, OpContext::Precedence(0), lhs) +} + +fn expr_op(s: &mut ParserContext, context: OpContext) -> 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, OpContext::Precedence(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, context, lhs) +} + +fn expr_op_with_lhs(s: &mut ParserContext, context: OpContext, mut lhs: Expr) -> Result { + let lo = lhs.span.lo; + + let OpContext::Precedence(min_precedence) = context; + + 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, OpContext::Precedence(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. + match BigInt::from_str_radix(lexeme, radix) { + Ok(value) => Some(value), + Err(_) => None, + } +} + +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(keyword) => OpName::Keyword(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::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_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs new file mode 100644 index 0000000000..29e92bb9cd --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/mut_visit.rs b/compiler/qsc_qasm3/src/parser/mut_visit.rs new file mode 100644 index 0000000000..e848076e1a --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/mut_visit.rs @@ -0,0 +1,840 @@ +// 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| vis.visit_ident(p)); + stmt.qubits.iter_mut().for_each(|p| vis.visit_ident(p)); + 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); + 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_qasm3/src/parser/prgm.rs b/compiler/qsc_qasm3/src/parser/prgm.rs new file mode 100644 index 0000000000..b9ca8586cd --- /dev/null +++ b/compiler/qsc_qasm3/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::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_qasm3/src/parser/prim.rs b/compiler/qsc_qasm3/src/parser/prim.rs new file mode 100644 index 0000000000..7f8d189a10 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/prim.rs @@ -0,0 +1,321 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#[cfg(test)] +pub(crate) mod tests; + +use super::{ + error::{Error, ErrorKind}, + scan::ParserContext, + Parser, Result, +}; +use crate::{lex::TokenKind, parser::completion::WordKinds}; + +use super::ast::{Ident, IncompletePath, Path, PathKind}; + +use qsc_data_structures::span::{Span, WithSpan}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(super) enum FinalSep { + Present, + Missing, +} + +impl FinalSep { + pub(super) fn reify( + self, + mut xs: Vec, + mut as_paren: impl FnMut(T) -> U, + mut as_seq: impl FnMut(Box<[T]>) -> U, + ) -> U { + if self == Self::Missing && xs.len() == 1 { + as_paren(xs.pop().expect("vector should have exactly one item")) + } else { + as_seq(xs.into_boxed_slice()) + } + } +} + +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, + ))) + } +} + +/// 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, + kind: WordKinds, +) -> std::result::Result, (Error, Option>)> { + s.expect(kind); + + 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. +pub(super) fn recovering_path(s: &mut ParserContext, kind: WordKinds) -> Result { + match path(s, kind) { + 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), + } +} + +/// 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)) +} + +/// Try to parse with the given parser. +/// +/// If the parser fails on the first token, returns the default value. +/// +/// If the parser fails after consuming some tokens, propagates the error. +pub(super) fn parse_or_else( + s: &mut ParserContext, + default: impl FnOnce(Span) -> T, + mut p: impl Parser, +) -> Result { + let lo = s.peek().span.lo; + match p(s) { + Ok(value) => Ok(value), + Err(error) if advanced(s, lo) => Err(error), + Err(error) => { + s.push_error(error); + // The whitespace will become part of the error span + s.skip_trivia(); + Ok(default(s.span(lo))) + } + } +} + +/// 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), + } +} + +/// Try to parse with the given parser. +/// +/// If the parser fails on the first token, returns the default value. +/// +/// 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. +/// +/// This behavior is a combination of [`recovering`] and [`parse_or_else`], +/// and provides the most aggressive error recovery. +pub(super) fn recovering_parse_or_else( + s: &mut ParserContext, + default: impl FnOnce(Span) -> T, + tokens: &[TokenKind], + mut p: impl Parser, +) -> T { + let lo = s.peek().span.lo; + match p(s) { + Ok(value) => value, + Err(error) => { + s.push_error(error); + + if advanced(s, lo) { + s.recover(tokens); + } else { + // The whitespace will become part of the error node span + s.skip_trivia(); + } + default(s.span(lo)) + } + } +} + +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 +} + +fn map_rule_name(name: &'static str, error: Error) -> Error { + Error::new(match error.0 { + ErrorKind::Rule(_, found, span) => ErrorKind::Rule(name, found, span), + ErrorKind::Convert(_, found, span) => ErrorKind::Convert(name, found, span), + kind => kind, + }) +} diff --git a/compiler/qsc_qasm3/src/parser/prim/tests.rs b/compiler/qsc_qasm3/src/parser/prim/tests.rs new file mode 100644 index 0000000000..7b2814b717 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/prim/tests.rs @@ -0,0 +1,275 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use super::{ident, opt, seq}; +use crate::{ + keyword::Keyword, + lex::TokenKind, + parser::ast::PathKind, + parser::{ + completion::WordKinds, + error::{Error, ErrorKind}, + scan::ParserContext, + tests::{check, check_opt, check_seq}, + }, +}; +use expect_test::expect; + +use qsc_data_structures::span::Span; + +fn path(s: &mut ParserContext) -> Result { + super::recovering_path(s, WordKinds::empty()) +} + +#[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( + path, + "Foo", + &expect![[r#" + Path [0-3]: + name: Ident [0-3] "Foo" + segments: "#]], + ); +} + +#[test] +fn path_double() { + check( + path, + "Foo.Bar", + &expect![[r#" + Path [0-7]: + name: Ident [4-7] "Bar" + segments: + Ident [0-3] "Foo""#]], + ); +} + +#[test] +fn path_triple() { + check( + 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( + 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( + 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, 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, path), "123", &expect!["None"]); +} + +#[test] +fn opt_fail_consume() { + check_opt( + |s| opt(s, 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_qasm3/src/parser/scan.rs b/compiler/qsc_qasm3/src/parser/scan.rs new file mode 100644 index 0000000000..50fbd3b45f --- /dev/null +++ b/compiler/qsc_qasm3/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, 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, + } + } + + // while we work through the conversion, allow dead code to avoid warnings + #[allow(dead_code)] + 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. + 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. + 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_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs new file mode 100644 index 0000000000..17eeac9c9c --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -0,0 +1,1793 @@ +// 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::WordKinds, + error::{Error, ErrorKind}, + expr::{self, designator, gate_operand, indexed_identifier}, + prim::{self, barrier, many, opt, recovering, recovering_semi, recovering_token, seq, shorten}, + 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::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(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(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 kind = if opt(s, |s| token(s, TokenKind::Inv))?.is_some() { + GateModifierKind::Inv + } else if opt(s, |s| token(s, TokenKind::Pow))?.is_some() { + 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::Ctrl))?.is_some() { + 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::NegCtrl)?; + 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), + 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_qasm3/src/parser/stmt/tests.rs b/compiler/qsc_qasm3/src/parser/stmt/tests.rs new file mode 100644 index 0000000000..1db174f83a --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/alias.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs new file mode 100644 index 0000000000..3c6d47ec8a --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/annotation.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/annotation.rs new file mode 100644 index 0000000000..a37d4e11ff --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/barrier.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs new file mode 100644 index 0000000000..2a37910565 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/block.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/block.rs new file mode 100644 index 0000000000..06773eacd5 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/box_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs new file mode 100644 index 0000000000..5695656f8b --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/cal.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/cal.rs new file mode 100644 index 0000000000..13633b5175 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/cal_grammar.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/cal_grammar.rs new file mode 100644 index 0000000000..da777fc9fa --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/classical_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs new file mode 100644 index 0000000000..0b7f4ddb01 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs @@ -0,0 +1,1127 @@ +// 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, + ), + Measure, + Span { + lo: 16, + hi: 23, + }, + ), + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs new file mode 100644 index 0000000000..d257821433 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/defcal.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/defcal.rs new file mode 100644 index 0000000000..b086ea2e68 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/delay.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs new file mode 100644 index 0000000000..35bf3bb7d1 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/expr_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs new file mode 100644 index 0000000000..bfad136fb6 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/extern_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/extern_decl.rs new file mode 100644 index 0000000000..6022cf83f1 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/for_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs new file mode 100644 index 0000000000..a0debdda1c --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/gate_call.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs new file mode 100644 index 0000000000..103b3f066f --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs @@ -0,0 +1,339 @@ +// 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]: 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]: 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]: Ctrl Some(Expr { span: Span { lo: 5, hi: 6 }, kind: Lit(Lit { span: Span { lo: 5, hi: 6 }, kind: Int(2) }) }) + QuantumGateModifier [10-15]: 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_qasm3/src/parser/stmt/tests/gate_def.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_def.rs new file mode 100644 index 0000000000..d54e31d86a --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/gphase.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs new file mode 100644 index 0000000000..ca70bdef4f --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs @@ -0,0 +1,203 @@ +// 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]: 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]: Ctrl None + QuantumGateModifier [7-12]: 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_qasm3/src/parser/stmt/tests/if_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs new file mode 100644 index 0000000000..b2d1bd177b --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/include.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/include.rs new file mode 100644 index 0000000000..6acdd8c0b7 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/invalid_stmts.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts.rs new file mode 100644 index 0000000000..deff9b0e90 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/invalid_stmts/branch.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/branch.rs new file mode 100644 index 0000000000..59d12cb6dc --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/invalid_stmts/cal.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/cal.rs new file mode 100644 index 0000000000..1d50f9d05d --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/invalid_stmts/constant.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/constant.rs new file mode 100644 index 0000000000..571e8341ba --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/invalid_stmts/decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/decl.rs new file mode 100644 index 0000000000..a653c561ec --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/decl.rs @@ -0,0 +1,994 @@ +// 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", + 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_qasm3/src/parser/stmt/tests/invalid_stmts/gate_calls.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/gate_calls.rs new file mode 100644 index 0000000000..0fdeb38c57 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/gate_calls.rs @@ -0,0 +1,220 @@ +// 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]: 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]: 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]: 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]: Inv + QuantumGateModifier [9-15]: 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_qasm3/src/parser/stmt/tests/invalid_stmts/headers.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/headers.rs new file mode 100644 index 0000000000..8fa9c878e0 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/invalid_stmts/io.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/io.rs new file mode 100644 index 0000000000..a6ed43817a --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/invalid_stmts/loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/loops.rs new file mode 100644 index 0000000000..5e775bc19f --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/invalid_stmts/measure.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/measure.rs new file mode 100644 index 0000000000..b23fb4a5b3 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/measure.rs @@ -0,0 +1,211 @@ +// 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", + Measure, + Span { + lo: 8, + hi: 15, + }, + ), + ) + "#]], + ); + check( + parse, + "a = (measure $0) + (measure $1);", + &expect![[r#" + Error( + Token( + Close( + Paren, + ), + Measure, + Span { + lo: 5, + hi: 12, + }, + ), + ) + "#]], + ); +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/switch.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/switch.rs new file mode 100644 index 0000000000..17a0a7e9d9 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/invalid_stmts/tokens.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/tokens.rs new file mode 100644 index 0000000000..3be5bbc5ba --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/io_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/io_decl.rs new file mode 100644 index 0000000000..aa9c48b864 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/measure.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs new file mode 100644 index 0000000000..fd2518ff97 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/old_style_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/old_style_decl.rs new file mode 100644 index 0000000000..5b9071def6 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/pragma.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/pragma.rs new file mode 100644 index 0000000000..42abef06cb --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/quantum_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/quantum_decl.rs new file mode 100644 index 0000000000..67031b9bd2 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/reset.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs new file mode 100644 index 0000000000..dc8670b04a --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/switch_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs new file mode 100644 index 0000000000..2ecdea6fd4 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/stmt/tests/while_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs new file mode 100644 index 0000000000..0e5493d1f5 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/parser/tests.rs b/compiler/qsc_qasm3/src/parser/tests.rs new file mode 100644 index 0000000000..30bd4e45d7 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/tests.rs @@ -0,0 +1,173 @@ +// 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 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, &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 resolver = InMemorySourceResolver::from_iter([("test".into(), source.as_ref().into())]); + let res = parse_source(source, "test", &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(), + }); +} + +#[allow(dead_code)] +pub(super) fn check_vec(parser: impl Parser>, input: &str, expect: &Expect) { + check_map(parser, input, expect, |values| { + values + .iter() + .map(ToString::to_string) + .collect::>() + .join(",\n") + }); +} + +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_qasm3/src/runtime.rs b/compiler/qsc_qasm3/src/runtime.rs index 2b0080d5fd..d1bf332285 100644 --- a/compiler/qsc_qasm3/src/runtime.rs +++ b/compiler/qsc_qasm3/src/runtime.rs @@ -1,16 +1,22 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +//! Q# doesn't support gphase and U gates which are used in QASM3. +//! We provide the implementation of these gates here so that QASM3 +//! users can still define custom gates in terms of these operations. +//! +//! We also provide 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. +//! +//! Finally, we provide QASM3 runtime functions mapped to their Q# counterparts. + 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 { @@ -132,18 +138,19 @@ 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; + const Pow = 0b1; + const Barrier = 0b10; + const BoolAsResult = 0b100; + const BoolAsInt = 0b1000; + const BoolAsBigInt = 0b1_0000; + const BoolAsDouble = 0b10_0000; + const ResultAsBool = 0b100_0000; + const ResultAsInt = 0b1000_0000; + const ResultAsBigInt = 0b1_0000_0000; /// IntAsResultArray requires BoolAsResult to be included. - const IntAsResultArrayBE = 0b1_000_000_000 | 0b100; - const ResultArrayAsIntBE = 0b10_000_000_000; + const IntAsResultArrayBE = 0b10_0000_0000 | 0b100; + const ResultArrayAsIntBE = 0b100_0000_0000; + const GATES = 0b1000_0000_0000; } } @@ -197,9 +204,12 @@ pub(crate) fn get_result_array_as_int_be_decl() -> Stmt { parse_stmt(RESULT_ARRAY_AS_INT_BE) } +/// As we are trying to add statements to the AST, we parse the Q# implementations +/// of the runtime functions and return the AST nodes. This saves us a lot of time +/// in writing the AST nodes manually. 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!(errors.is_empty(), "Failed to parse: {errors:?}"); assert!( nodes.len() == 1, "Expected one top-level node, found {:?}", @@ -207,12 +217,29 @@ fn parse_stmt(name: &str) -> Stmt { ); match nodes.into_iter().next().expect("no top-level nodes found") { TopLevelNode::Namespace(..) => { - panic!("Expected operation, got Namespace") + panic!("Expected stmt, got Namespace") } TopLevelNode::Stmt(stmt) => *stmt, } } +fn parse_stmts(name: &str) -> Vec { + let (nodes, errors) = qsc_parse::top_level_nodes(name, LanguageFeatures::default()); + assert!(errors.is_empty(), "Failed to parse: {errors:?}"); + let mut stmts = vec![]; + for stmt in nodes { + match stmt { + TopLevelNode::Namespace(..) => { + panic!("Expected stmt, got Namespace") + } + TopLevelNode::Stmt(stmt) => stmts.push(*stmt), + } + } + + stmts +} + +/// Get the runtime function declarations for the given runtime functions. pub(crate) fn get_runtime_function_decls(runtime: RuntimeFunctions) -> Vec { let mut stmts = vec![]; if runtime.contains(RuntimeFunctions::Pow) { diff --git a/compiler/qsc_qasm3/src/semantic.rs b/compiler/qsc_qasm3/src/semantic.rs new file mode 100644 index 0000000000..e99dac9906 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic.rs @@ -0,0 +1,127 @@ +// 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 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 resolver = InMemorySourceResolver::from_iter([( + path.as_ref().display().to_string().into(), + source.as_ref().into(), + )]); + parse_source(source, path, &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: &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_qasm3/src/semantic/ast.rs b/compiler/qsc_qasm3/src/semantic/ast.rs new file mode 100644 index 0000000000..e044269351 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/ast.rs @@ -0,0 +1,1779 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +pub mod const_eval; + +use num_bigint::BigInt; +use qsc_data_structures::span::{Span, WithSpan}; +use std::{ + fmt::{self, Display, Formatter}, + hash::Hash, + rc::Rc, +}; + +use crate::{ + parser::ast::{ + 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, + }, + 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 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)?; + 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) + } +} + +impl WithSpan for HardwareQubit { + fn with_span(self, span: Span) -> Self { + Self { span, ..self } + } +} + +#[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)] +pub enum Identifier { + Ident(Box), + IndexedIdent(Box), +} + +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 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) + } +} + +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, + pub ty: super::types::Type, +} + +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 { + 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 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 kind: GateModifierKind, +} + +impl Display for QuantumGateModifier { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "QuantumGateModifier {}: {}", self.span, 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 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 { + Scalar(ScalarType, Span), + Quantum(Option, Span), + ArrayReference(ArrayReferenceType, Span), +} + +impl Display for ExternParameter { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + ExternParameter::Scalar(ty, span) => { + write!(f, "{span}: {ty}") + } + ExternParameter::Quantum(expr, span) => { + write!(f, "{span}: {expr:?}") + } + 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::Quantum(expr, _) => ExternParameter::Quantum(expr, 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 { + 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 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 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, "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 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_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 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, "ty", &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, "ty", &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 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 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_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 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, "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, +} + +/// 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, Debug)] +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"), + } + } +} + +#[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_qasm3/src/semantic/ast/const_eval.rs b/compiler/qsc_qasm3/src/semantic/ast/const_eval.rs new file mode 100644 index 0000000000..cc7fc68f39 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/ast/const_eval.rs @@ -0,0 +1,780 @@ +// 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::{ + BinOp, BinaryOpExpr, Cast, Expr, ExprKind, FunctionCall, IndexExpr, IndexedIdent, LiteralKind, + SymbolId, UnaryOp, UnaryOpExpr, +}; +use crate::semantic::Lowerer; +use crate::stdlib::angle; +use crate::{ + oqasm_helpers::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("Qsc.Qasm3.Compile.ExprMustBeConst"))] + ExprMustBeConst(#[label] Span), + #[error("uint expression must evaluate to a non-negative value, but it evaluated to {0}")] + #[diagnostic(code("Qsc.Qasm3.Compile.NegativeUIntValue"))] + NegativeUIntValue(i64, #[label] Span), + #[error("{0} doesn't fit in {1}")] + #[diagnostic(code("Qsc.Qasm3.Compile.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}; + + assert_binary_op_ty_invariant(self.op, &self.lhs.ty, &self.rhs.ty); + 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; + + 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)), { + // TODO: we need to issue the same lint in Q#. + #[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)), { + // TODO: we need to issue the same lint in Q#. + #[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()?)) + } + // TODO: UInt Overflowing behavior. + // This is tricky because the inner repersentation + // already is a i64. Therefore, there is nothing to do? + Type::Int(..) | Type::UInt(..) => Some(lit), + Type::Float(..) => rewrap_lit!(lit, Float(val), { + // TODO: we need to issue the same lint in Q#. + #[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()?)) + } + // TODO: Int Overflowing behavior. + // This is tricky because the inner representation + // is a i64. Therefore, even we might end with the + // same result anyways. Need to think through this. + Type::Int(..) | Type::UInt(..) => Some(lit), + Type::Float(..) => rewrap_lit!(lit, Float(val), { + // TODO: we need to issue the same lint in Q#. + #[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), { + // TODO: we need to issue the same lint in Q#. + #[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_qasm3/src/semantic/error.rs b/compiler/qsc_qasm3/src/semantic/error.rs new file mode 100644 index 0000000000..ccbe28a2a6 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -0,0 +1,426 @@ +// 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); + +impl Error { + #[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)] +pub enum SemanticErrorKind { + #[error("Array literals are only allowed in classical declarations.")] + #[diagnostic(code("Qsc.Qasm3.Compile.ArrayLiteralInNonClassicalDecl"))] + ArrayLiteralInNonClassicalDecl(#[label] Span), + #[error("{0} must fit in a u32")] + #[diagnostic(code("Qsc.Qasm3.Compile.ExprMustFitInU32"))] + ExprMustFitInU32(String, #[label] Span), + #[error("{0} must be a const expression")] + #[diagnostic(code("Qsc.Qasm3.Compile.ExprMustBeConst"))] + ExprMustBeConst(String, #[label] Span), + #[error("Annotation missing target statement.")] + #[diagnostic(code("Qsc.Qasm3.Compile.AnnotationWithoutStatement"))] + AnnotationWithoutStatement(#[label] Span), + #[error("calibration statements are not supported: {0}")] + #[diagnostic(code("Qsc.Qasm3.Compile.CalibrationsNotSupported"))] + CalibrationsNotSupported(String, #[label] Span), + #[error("Cannot alias type {0}. Only qubit and qubit[] can be aliased.")] + #[diagnostic(code("Qsc.Qasm3.Compile.CannotAliasType"))] + CannotAliasType(String, #[label] 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 an expression that is not a function.")] + #[diagnostic(code("Qsc.Qasm3.Compile.CannotCallNonFunction"))] + CannotCallNonFunction(#[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 cast literal expression of type {0} to type {1}")] + #[diagnostic(code("Qsc.Qasm3.Compile.CannotCastLiteral"))] + CannotCastLiteral(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("invalid classical statement in box")] + #[diagnostic(code("Qsc.Qasm3.Compile.ClassicalStmtInBox"))] + ClassicalStmtInBox(#[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 positive literal integer.")] + #[diagnostic(code("Qsc.Qasm3.Compile.DesignatorMustBePositiveIntLiteral"))] + DesignatorMustBePositiveIntLiteral(#[label] Span), + #[error("Type width must be a positive integer const expression.")] + #[diagnostic(code("Qsc.Qasm3.Compile.TypeWidthMustBePositiveIntConstExpr"))] + TypeWidthMustBePositiveIntConstExpr(#[label] Span), + #[error("Array size must be a non-negative integer const expression.")] + #[diagnostic(code("Qsc.Qasm3.Compile.ArraySizeMustBeNonNegativeConstExpr"))] + ArraySizeMustBeNonNegativeConstExpr(#[label] Span), + #[error("Def declarations must be done in global scope.")] + #[diagnostic(code("Qsc.Qasm3.Compile.DefDeclarationInNonGlobalScope"))] + DefDeclarationInNonGlobalScope(#[label] Span), + #[error("Designator is too large.")] + #[diagnostic(code("Qsc.Qasm3.Compile.DesignatorTooLarge"))] + DesignatorTooLarge(#[label] Span), + #[error("Extern declarations must be done in global scope.")] + #[diagnostic(code("Qsc.Qasm3.Compile.DefDeclarationInNonGlobalScope"))] + ExternDeclarationInNonGlobalScope(#[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("Inconsisten types in alias expression: {0}.")] + #[diagnostic(code("Qsc.Qasm3.Compile.InconsistentTypesInAlias"))] + InconsistentTypesInAlias(String, #[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 def and gate statements.")] + #[diagnostic(code("Qsc.Qasm3.Compile.InvalidAnnotationTarget"))] + InvalidAnnotationTarget(#[label] Span), + #[error("Assigning {0} values to {1} must be in a range that be converted to {1}.")] + #[diagnostic(code("Qsc.Qasm3.Compile.InvalidCastValueRange"))] + 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("{0} can only appear in {1} scopes.")] + #[diagnostic(code("Qsc.Qasm3.Compile.InvalidScope"))] + InvalidScope(String, String, #[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("Return statements on a non-void subroutine should have a target expression.")] + #[diagnostic(code("Qsc.Qasm3.Compile.MissingTargetExpressionInReturnStmt"))] + MissingTargetExpressionInReturnStmt(#[label] Span), + #[error("Control counts must be postitive integers.")] + #[diagnostic(code("Qsc.Qasm3.Compile.NegativeControlCount"))] + NegativeControlCount(#[label] Span), + #[error("{0} are not supported.")] + #[diagnostic(code("Qsc.Qasm3.Compile.NotSupported"))] + NotSupported(String, #[label] Span), + #[error("{0} were introduced in version {1}")] + #[diagnostic(code("Qsc.Qasm3.Compile.NotSupportedInThisVersion"))] + NotSupportedInThisVersion(String, String, #[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("Cannot return an expression from a void subroutine.")] + #[diagnostic(code("Qsc.Qasm3.Compile.ReturningExpressionFromVoidSubroutine"))] + ReturningExpressionFromVoidSubroutine(#[label] Span), + #[error("Return statements are only allowed within subroutines.")] + #[diagnostic(code("Qsc.Qasm3.Compile.ReturnNotInSubroutine"))] + ReturnNotInSubroutine(#[label] Span), + #[error("Switch statement must have at least one non-default case.")] + #[diagnostic(code("Qsc.Qasm3.Compile.SwitchStatementMustHaveAtLeastOneCase"))] + SwitchStatementMustHaveAtLeastOneCase(#[label] Span), + #[error("Too many controls specified.")] + #[diagnostic(code("Qsc.Qasm3.Compile.TooManyControls"))] + TooManyControls(#[label] Span), + #[error("Too many indicies specified.")] + #[diagnostic(code("Qsc.Qasm3.Compile.TooManyIndices"))] + TooManyIndices(#[label] Span), + #[error("Bitwise not `~` is not allowed for instances of {0}.")] + #[diagnostic(code("Qsc.Qasm3.Compile.TypeDoesNotSupportBitwiseNot"))] + TypeDoesNotSupportBitwiseNot(String, #[label] Span), + #[error("Unary negation is not allowed for instances of {0}.")] + #[diagnostic(code("Qsc.Qasm3.Compile.TypeDoesNotSupportedUnaryNegation"))] + TypeDoesNotSupportedUnaryNegation(String, #[label] Span), + #[error("{0} max width is {1} but {2} was provided.")] + #[diagnostic(code("Qsc.Qasm3.Compile.TypeMaxWidthExceeded"))] + TypeMaxWidthExceeded(String, usize, usize, #[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("this statement is not yet handled during OpenQASM 3 import: {0}")] + #[diagnostic(code("Qsc.Qasm3.Compile.Unimplemented"))] + Unimplemented(String, #[label] Span), + #[error("Unexpected annotation: {0}.")] + #[diagnostic(code("Qsc.Qasm3.Compile.UnknownAnnotation"))] + UnknownAnnotation(String, #[label] Span), + #[error("Unknown index operation kind.")] + #[diagnostic(code("Qsc.Qasm3.Compile.UnknownIndexedOperatorKind"))] + UnknownIndexedOperatorKind(#[label] Span), + #[error("Unsupported version: '{0}'.")] + #[diagnostic(code("Qsc.Qasm3.Compile.UnsupportedVersion"))] + UnsupportedVersion(String, #[label] Span), + #[error("While statement missing {0} expression.")] + #[diagnostic(code("Qsc.Qasm3.Compile.WhileStmtMissingExpression"))] + WhileStmtMissingExpression(String, #[label] 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::ArrayLiteralInNonClassicalDecl(span) => { + Self::ArrayLiteralInNonClassicalDecl(span + offset) + } + Self::ExprMustBeConst(name, span) => Self::ExprMustBeConst(name, span + offset), + Self::ExprMustFitInU32(name, span) => Self::ExprMustFitInU32(name, span + offset), + Self::AnnotationWithoutStatement(span) => { + Self::AnnotationWithoutStatement(span + offset) + } + Self::CannotCast(lhs, rhs, span) => Self::CannotCast(lhs, rhs, span + offset), + Self::CannotCastLiteral(lhs, rhs, span) => { + Self::CannotCastLiteral(lhs, rhs, span + offset) + } + Self::CastWouldCauseTruncation(lhs, rhs, span) => { + Self::CastWouldCauseTruncation(lhs, rhs, span + offset) + } + Self::ClassicalStmtInBox(span) => Self::ClassicalStmtInBox(span + offset), + Self::CalibrationsNotSupported(name, span) => { + Self::CalibrationsNotSupported(name, 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::CannotCallNonFunction(span) => Self::CannotCallNonFunction(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::DesignatorMustBePositiveIntLiteral(span) => { + Self::DesignatorMustBePositiveIntLiteral(span + offset) + } + Self::TypeWidthMustBePositiveIntConstExpr(span) => { + Self::TypeWidthMustBePositiveIntConstExpr(span + offset) + } + Self::ArraySizeMustBeNonNegativeConstExpr(span) => { + Self::ArraySizeMustBeNonNegativeConstExpr(span + offset) + } + Self::DefDeclarationInNonGlobalScope(span) => { + Self::DefDeclarationInNonGlobalScope(span + offset) + } + Self::DesignatorTooLarge(span) => Self::DesignatorTooLarge(span + offset), + Self::ExternDeclarationInNonGlobalScope(span) => { + Self::ExternDeclarationInNonGlobalScope(span + offset) + } + Self::FailedToCompileExpressionList(span) => { + Self::FailedToCompileExpressionList(span + offset) + } + Self::ForIterableInvalidExpression(span) => { + Self::ForIterableInvalidExpression(span + offset) + } + Self::ForStatementsMustHaveABodyOrStatement(span) => { + Self::ForStatementsMustHaveABodyOrStatement(span + offset) + } + Self::InconsistentTypesInAlias(name, span) => { + Self::InconsistentTypesInAlias(name, span + offset) + } + Self::InvalidScope(name, scope, span) => Self::InvalidScope(name, scope, 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::MissingTargetExpressionInReturnStmt(span) => { + Self::MissingTargetExpressionInReturnStmt(span + offset) + } + Self::NegativeControlCount(span) => Self::NegativeControlCount(span + offset), + Self::NotSupported(name, span) => Self::NotSupported(name, span + offset), + Self::NotSupportedInThisVersion(name, version, span) => { + Self::NotSupportedInThisVersion(name, version, 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::ReturningExpressionFromVoidSubroutine(span) => { + Self::ReturningExpressionFromVoidSubroutine(span + offset) + } + Self::ReturnNotInSubroutine(span) => Self::ReturnNotInSubroutine(span + offset), + Self::SwitchStatementMustHaveAtLeastOneCase(span) => { + Self::SwitchStatementMustHaveAtLeastOneCase(span + offset) + } + Self::TooManyControls(span) => Self::TooManyControls(span + offset), + Self::TooManyIndices(span) => Self::TooManyIndices(span + offset), + Self::TypeDoesNotSupportBitwiseNot(name, span) => { + Self::TypeDoesNotSupportBitwiseNot(name, span + offset) + } + Self::TypeDoesNotSupportedUnaryNegation(name, span) => { + Self::TypeDoesNotSupportedUnaryNegation(name, span + offset) + } + Self::TypeMaxWidthExceeded(name, max_width, provided_width, span) => { + Self::TypeMaxWidthExceeded(name, max_width, provided_width, 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::Unimplemented(name, span) => Self::Unimplemented(name, span + offset), + Self::UnknownAnnotation(name, span) => Self::UnknownAnnotation(name, span + offset), + Self::UnknownIndexedOperatorKind(span) => { + Self::UnknownIndexedOperatorKind(span + offset) + } + Self::UnsupportedVersion(version, span) => { + Self::UnsupportedVersion(version, span + offset) + } + Self::WhileStmtMissingExpression(name, span) => { + Self::WhileStmtMissingExpression(name, span + offset) + } + } + } +} + +impl From for crate::Error { + fn from(val: Error) -> Self { + crate::Error(crate::ErrorKind::Semantic(val)) + } +} diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs new file mode 100644 index 0000000000..a9eb613d21 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -0,0 +1,3302 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::ops::ShlAssign; +use std::rc::Rc; + +use super::ast::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 super::symbols::{IOKind, Symbol, SymbolTable}; + +use crate::oqasm_helpers::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); + 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, include: &syntax::IncludeStmt) { + 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("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("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(), include.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); + return semantic::StmtKind::Err; + } + + 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, + 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.len() == 0 { + &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 = "HardwareQubit 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.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, + 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((qsharp_name, implicit_modifier)) = + try_get_qsharp_name_and_implicit_modifiers(&name, stmt.name.span) + { + // Override the gate name if Q# name is another. + name = qsharp_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); + } + + // 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, + 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 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, + 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.len() as u32; + #[allow(clippy::cast_possible_truncation)] + let quantum_arity = stmt.qubits.len() 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); + + let params = stmt + .params + .iter() + .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() + .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, stmt.ident.span, ty, qsharp_ty, IOKind::Default); + self.try_insert_or_get_existing_symbol_id(&arg.name, symbol) + }) + .collect(); + + let body = semantic::Block { + span: stmt.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, + 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::CannotAssignToType( + 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 = "HardwareQubit default values"; + self.push_unsupported_error_message(message, span); + None + } + Type::QubitArray(_) => { + let message = "QubitArray 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( + "Integer".to_string(), + "Double".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 IntNumber"); + } + } + (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( + "BigInt".to_string(), + "Int".to_string(), + 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 IntNumber 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 + ) + } + + // TODO: which these are parsed as different types, they are effectively the same + 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, + 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_qasm3/src/semantic/symbols.rs b/compiler/qsc_qasm3/src/semantic/symbols.rs new file mode 100644 index 0000000000..0fbb4081e3 --- /dev/null +++ b/compiler/qsc_qasm3/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::parser::ast::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_qasm3/src/semantic/tests.rs b/compiler/qsc_qasm3/src/semantic/tests.rs new file mode 100644 index 0000000000..d2eb3f29a0 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests.rs @@ -0,0 +1,357 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +pub mod assignment; +pub mod decls; + +pub mod expression; +pub mod statements; + +use std::path::Path; +use std::sync::Arc; + +use crate::io::InMemorySourceResolver; +use crate::io::SourceResolver; + +use super::parse_source; + +use super::QasmSemanticParseResult; + +use expect_test::expect; +use miette::Report; + +use expect_test::Expect; + +pub(crate) fn parse_all

( + path: P, + sources: impl IntoIterator, Arc)>, +) -> miette::Result> +where + P: AsRef, +{ + let resolver = InMemorySourceResolver::from_iter(sources); + let (path, source) = resolver + .resolve(path.as_ref()) + .map_err(|e| vec![Report::new(e)])?; + let res = parse_source(source, path, &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 resolver = InMemorySourceResolver::from_iter([("test".into(), source.as_ref().into())]); + let res = parse_source(source, "test", &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(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]; + value.push_str(&format!("[{}] {symbol}", decl.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]; + value.push_str(&format!("[{symbol_id}] {symbol}")); + 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 resolver = InMemorySourceResolver::from_iter([("test".into(), input.as_ref().into())]); + let res = parse_source(input, "test", &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 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, &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: 35 + 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: 35 + ty_span: [211-215] + init_expr: Expr [220-226]: + ty: Bool(false) + kind: BinaryOpExpr: + op: AndL + lhs: Expr [220-221]: + ty: Err + kind: SymbolId(36) + rhs: Expr [225-226]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [225-226]: + ty: Bit(false) + kind: SymbolId(35) + Stmt [140-154]: + annotations: + kind: ClassicalDeclarationStmt [140-154]: + symbol_id: 37 + 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: 38 + 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(37) + 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: 40 + ty_span: [74-77] + init_expr: Expr [82-83]: + ty: Err + kind: SymbolId(39) + + [Qsc.Qasm3.Compile.UndefinedSymbol + + x Undefined symbol: v. + ,-[source2.qasm:2:14] + 1 | bit l = 1; + 2 | bool l = v && l; // undefined y, redefine l + : ^ + `---- + , Qsc.Qasm3.Compile.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 + : ^ + `---- + , Qsc.Qasm3.Compile.RedefinedSymbol + + x Redefined symbol: l. + ,-[source2.qasm:2:10] + 1 | bit l = 1; + 2 | bool l = v && l; // undefined y, redefine l + : ^ + `---- + , Qsc.Qasm3.Compile.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 + : ^ + `---- + , Qsc.Qasm3.Compile.UndefinedSymbol + + x Undefined symbol: r. + ,-[source0.qasm:4:13] + 3 | include "source1.qasm"; + 4 | bit c = r; // undefined symbol r + : ^ + 5 | + `---- + , Qsc.Qasm3.Compile.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_qasm3/src/semantic/tests/assignment.rs b/compiler/qsc_qasm3/src/semantic/tests/assignment.rs new file mode 100644 index 0000000000..bdf65084d2 --- /dev/null +++ b/compiler/qsc_qasm3/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_indicies_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_qasm3/src/semantic/tests/decls.rs b/compiler/qsc_qasm3/src/semantic/tests/decls.rs new file mode 100644 index 0000000000..1240c0c4d8 --- /dev/null +++ b/compiler/qsc_qasm3/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#" + + + [Qsc.Qasm3.Compile.NotSupported + + x Duration type values are not supported. + ,-[test:2:9] + 1 | + 2 | duration i; + : ^^^^^^^^ + 3 | stretch n; + `---- + , Qsc.Qasm3.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 + + [Qsc.Qasm3.Compile.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 + + [Qsc.Qasm3.Compile.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; + : ^^^^ + `---- + , Qsc.Qasm3.Compile.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_qasm3/src/semantic/tests/decls/angle.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs new file mode 100644 index 0000000000..e64a0ff65a --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs @@ -0,0 +1,479 @@ +// 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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.TypeWidthMustBePositiveIntConstExpr + + x Type width must be a positive integer const expression. + ,-[test:1:7] + 1 | angle[0] x = 42.1; + : ^ + `---- + , Qsc.Qasm3.Compile.CannotAssignToType + + x Cannot assign a value of Float(None, true) type to a classical variable of + | Err type. + ,-[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) + + [Qsc.Qasm3.Compile.TypeMaxWidthExceeded + + x angle max width is 64 but 65 was provided. + ,-[test:1:7] + 1 | const angle[65] x = 42.1; + : ^^^^^^^^^ + `---- + , Qsc.Qasm3.Compile.CannotAssignToType + + x Cannot assign a value of Float(None, true) type to a classical variable of + | Err type. + ,-[test:1:1] + 1 | const angle[65] x = 42.1; + : ^^^^^^^^^^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/bit.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/bit.rs new file mode 100644 index 0000000000..6a12f8b39e --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/decls/bool.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/bool.rs new file mode 100644 index 0000000000..f7792018c6 --- /dev/null +++ b/compiler/qsc_qasm3/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 + + [Qsc.Qasm3.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_qasm3/src/semantic/tests/decls/complex.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs new file mode 100644 index 0000000000..6cf6676fdb --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/decls/creg.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/creg.rs new file mode 100644 index 0000000000..7ac120ca04 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/decls/duration.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/duration.rs new file mode 100644 index 0000000000..56d173ae52 --- /dev/null +++ b/compiler/qsc_qasm3/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) + + [Qsc.Qasm3.Compile.NotSupported + + x Duration type values are not supported. + ,-[test:1:1] + 1 | duration a; + : ^^^^^^^^ + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/extern_decl.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/extern_decl.rs new file mode 100644 index 0000000000..ddf10d85b8 --- /dev/null +++ b/compiler/qsc_qasm3/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: () + + [Qsc.Qasm3.Compile.DefDeclarationInNonGlobalScope + + x Extern declarations must be done in global scope. + ,-[test:1:3] + 1 | { extern f(); } + : ^^^^^^^^^^^ + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs new file mode 100644 index 0000000000..5c372ba5b6 --- /dev/null +++ b/compiler/qsc_qasm3/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) + + [Qsc.Qasm3.Compile.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; + : ^^^^^^^^^^^^^^^^ + `---- + , Qsc.Qasm3.Compile.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_qasm3/src/semantic/tests/decls/int.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs new file mode 100644 index 0000000000..6beda22ff3 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/decls/qreg.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/qreg.rs new file mode 100644 index 0000000000..349b3bc768 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/decls/stretch.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/stretch.rs new file mode 100644 index 0000000000..56f4f0dd61 --- /dev/null +++ b/compiler/qsc_qasm3/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 + + [Qsc.Qasm3.Compile.NotSupported + + x Stretch type values are not supported. + ,-[test:1:1] + 1 | stretch a; + : ^^^^^^^ + `---- + , Qsc.Qasm3.Compile.NotSupported + + x Stretch default values are not supported. + ,-[test:1:1] + 1 | stretch a; + : ^^^^^^^^^^ + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs new file mode 100644 index 0000000000..29eaf3c1ae --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/expression.rs b/compiler/qsc_qasm3/src/semantic/tests/expression.rs new file mode 100644 index 0000000000..8a2f3511ad --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/expression/binary.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary.rs new file mode 100644 index 0000000000..4e43914b2c --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/expression/binary/arithmetic_conversions.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/arithmetic_conversions.rs new file mode 100644 index 0000000000..847c4abe36 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/expression/binary/comparison.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison.rs new file mode 100644 index 0000000000..2ec6e5c4e8 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/expression/binary/comparison/bit_to_bit.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison/bit_to_bit.rs new file mode 100644 index 0000000000..435eec3e15 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/expression/binary/comparison/bool_to_bool.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison/bool_to_bool.rs new file mode 100644 index 0000000000..d1e2ee4edb --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/expression/binary/comparison/float_to_float.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison/float_to_float.rs new file mode 100644 index 0000000000..9439a6c6d2 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/expression/binary/comparison/int_to_int.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison/int_to_int.rs new file mode 100644 index 0000000000..a3ff2d14e1 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/expression/binary/comparison/uint_to_uint.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison/uint_to_uint.rs new file mode 100644 index 0000000000..37847ee8e2 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/expression/binary/complex.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs new file mode 100644 index 0000000000..d070cbb067 --- /dev/null +++ b/compiler/qsc_qasm3/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) + "#]], + ); +} + +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 + InputDeclaration [41-64]: + symbol_id: 9 + ClassicalDeclarationStmt [73-93]: + symbol_id: 10 + ty_span: [73-80] + init_expr: Expr [85-92]: + ty: Complex(None, false) + kind: Paren Expr [86-91]: + ty: Complex(None, false) + kind: BinaryOpExpr: + op: Add + lhs: Expr [86-87]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [90-91]: + ty: Complex(None, false) + kind: SymbolId(9) + "#]], + ); +} + +#[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_qasm3/src/semantic/tests/expression/binary/ident.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/ident.rs new file mode 100644 index 0000000000..a88ba0f04f --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/expression/implicit_cast_from_angle.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_angle.rs new file mode 100644 index 0000000000..3936536762 --- /dev/null +++ b/compiler/qsc_qasm3/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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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_qasm3/src/semantic/tests/expression/implicit_cast_from_bit.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_bit.rs new file mode 100644 index 0000000000..b0f0e96648 --- /dev/null +++ b/compiler/qsc_qasm3/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) + + [Qsc.Qasm3.Compile.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_qasm3/src/semantic/tests/expression/implicit_cast_from_bitarray.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_bitarray.rs new file mode 100644 index 0000000000..d48f090faa --- /dev/null +++ b/compiler/qsc_qasm3/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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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_qasm3/src/semantic/tests/expression/implicit_cast_from_bool.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_bool.rs new file mode 100644 index 0000000000..5f444fb295 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/expression/implicit_cast_from_float.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_float.rs new file mode 100644 index 0000000000..bce8631b3f --- /dev/null +++ b/compiler/qsc_qasm3/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) + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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_qasm3/src/semantic/tests/expression/implicit_cast_from_int.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_int.rs new file mode 100644 index 0000000000..b1e756ab95 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/statements.rs b/compiler/qsc_qasm3/src/semantic/tests/statements.rs new file mode 100644 index 0000000000..c31fcb3141 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/tests/statements/box_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/box_stmt.rs new file mode 100644 index 0000000000..5089ce953f --- /dev/null +++ b/compiler/qsc_qasm3/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 + + [Qsc.Qasm3.Compile.ClassicalStmtInBox + + x invalid classical statement in box + ,-[test:2:9] + 1 | box { + 2 | 2 + 4; + : ^^^^^^ + 3 | } + `---- + , Qsc.Qasm3.Compile.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 + + [Qsc.Qasm3.Compile.NotSupported + + x Box with duration are not supported. + ,-[test:1:6] + 1 | box [4us] { } + : ^^^ + `---- + , Qsc.Qasm3.Compile.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_qasm3/src/semantic/tests/statements/break_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/break_stmt.rs new file mode 100644 index 0000000000..5e3c9fcc3b --- /dev/null +++ b/compiler/qsc_qasm3/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 + + [Qsc.Qasm3.Compile.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 [36-54]: + Stmt [46-52]: + annotations: + kind: Err + + [Qsc.Qasm3.Compile.DefDeclarationInNonGlobalScope + + x Def declarations must be done in global scope. + ,-[test:3:13] + 2 | while (true) { + 3 | def f() { break; } + : ^^^^^^^^^^^^^^^^^^ + 4 | } + `---- + , Qsc.Qasm3.Compile.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_qasm3/src/semantic/tests/statements/continue_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/continue_stmt.rs new file mode 100644 index 0000000000..7a73aaae27 --- /dev/null +++ b/compiler/qsc_qasm3/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 + + [Qsc.Qasm3.Compile.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 [36-57]: + Stmt [46-55]: + annotations: + kind: Err + + [Qsc.Qasm3.Compile.DefDeclarationInNonGlobalScope + + x Def declarations must be done in global scope. + ,-[test:3:13] + 2 | while (true) { + 3 | def f() { continue; } + : ^^^^^^^^^^^^^^^^^^^^^ + 4 | } + `---- + , Qsc.Qasm3.Compile.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_qasm3/src/semantic/tests/statements/for_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/for_stmt.rs new file mode 100644 index 0000000000..147c328b78 --- /dev/null +++ b/compiler/qsc_qasm3/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) + + [Qsc.Qasm3.Compile.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_qasm3/src/semantic/tests/statements/if_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/if_stmt.rs new file mode 100644 index 0000000000..850228461a --- /dev/null +++ b/compiler/qsc_qasm3/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: + + [Qsc.Qasm3.Compile.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) + + [Qsc.Qasm3.Compile.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_qasm3/src/semantic/tests/statements/switch_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/switch_stmt.rs new file mode 100644 index 0000000000..fd72b20b3b --- /dev/null +++ b/compiler/qsc_qasm3/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: + + [Qsc.Qasm3.Compile.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_qasm3/src/semantic/tests/statements/while_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs new file mode 100644 index 0000000000..bf8d92833f --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/semantic/types.rs b/compiler/qsc_qasm3/src/semantic/types.rs new file mode 100644 index 0000000000..7373338207 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/stdlib.rs b/compiler/qsc_qasm3/src/stdlib.rs new file mode 100644 index 0000000000..ff466a50e6 --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/stdlib/QasmStd/qsharp.json b/compiler/qsc_qasm3/src/stdlib/QasmStd/qsharp.json new file mode 100644 index 0000000000..a29a2e746f --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/stdlib/QasmStd/src/QasmStd/Angle.qs b/compiler/qsc_qasm3/src/stdlib/QasmStd/src/QasmStd/Angle.qs new file mode 100644 index 0000000000..d3926410ce --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/src/stdlib/QasmStd/src/QasmStd/Convert.qs b/compiler/qsc_qasm3/src/stdlib/QasmStd/src/QasmStd/Convert.qs new file mode 100644 index 0000000000..eec43ab655 --- /dev/null +++ b/compiler/qsc_qasm3/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 QASM3 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 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. +@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. +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. +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. + +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. + +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. +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. +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. +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. +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. +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_qasm3/src/stdlib/QasmStd/src/QasmStd/Intrinsic.qs b/compiler/qsc_qasm3/src/stdlib/QasmStd/src/QasmStd/Intrinsic.qs new file mode 100644 index 0000000000..8c99eb8374 --- /dev/null +++ b/compiler/qsc_qasm3/src/stdlib/QasmStd/src/QasmStd/Intrinsic.qs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +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; + +import Angle.*; + +import Std.Intrinsic.*; + + +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); +} + +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(__DoubleAsAngle__(0., 1), __DoubleAsAngle__(0., 1), lambda, qubit); +} + +operation id(qubit : Qubit) : Unit is Adj + Ctl { + I(qubit) +} + +operation u1(lambda : __Angle__, qubit : Qubit) : Unit is Adj + Ctl { + U(__DoubleAsAngle__(0., 1), __DoubleAsAngle__(0., 1), lambda, qubit); +} + +operation u2(phi : __Angle__, lambda : __Angle__, qubit : Qubit) : Unit is Adj + Ctl { + let half_pi = __DivideAngleByInt__(__DoubleAsAngle__(Std.Math.PI(), 53), 2); + + gphase(__NegAngle__(__DivideAngleByInt__(__AddAngles__( + phi, + __AddAngles__( + lambda, + half_pi + ) + ), 2))); + + U(half_pi, 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); +} + diff --git a/compiler/qsc_qasm3/src/angle.rs b/compiler/qsc_qasm3/src/stdlib/angle.rs similarity index 57% rename from compiler/qsc_qasm3/src/angle.rs rename to compiler/qsc_qasm3/src/stdlib/angle.rs index 44de3cf37a..63bd90463b 100644 --- a/compiler/qsc_qasm3/src/angle.rs +++ b/compiler/qsc_qasm3/src/stdlib/angle.rs @@ -7,37 +7,75 @@ pub(crate) mod tests; use num_bigint::BigInt; use crate::oqasm_helpers::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) } 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_qasm3/src/stdlib/angle/tests.rs similarity index 59% rename from compiler/qsc_qasm3/src/angle/tests.rs rename to compiler/qsc_qasm3/src/stdlib/angle/tests.rs index 783d8dc3e2..9c1142b8e0 100644 --- a/compiler/qsc_qasm3/src/angle/tests.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/stdlib/compile.rs b/compiler/qsc_qasm3/src/stdlib/compile.rs new file mode 100644 index 0000000000..af801d567a --- /dev/null +++ b/compiler/qsc_qasm3/src/stdlib/compile.rs @@ -0,0 +1,149 @@ +// 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 OPENQASM_LIBRARY_URI_SCHEME: &str = "openqasm-library-source"; + +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_qasm3/src/tests.rs b/compiler/qsc_qasm3/src/tests.rs index 66c8c67e60..e47f0d4a30 100644 --- a/compiler/qsc_qasm3/src/tests.rs +++ b/compiler/qsc_qasm3/src/tests.rs @@ -1,23 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::{ - parse::{QasmParseResult, QasmSource}, - qasm_to_program, CompilerConfig, OutputSemantics, ProgramType, QasmCompileUnit, QubitSemantics, -}; +use crate::stdlib::compile::package_store_with_qasm; +use crate::{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, + SourceMap, Span, }; +use qsc_hir::hir::PackageId; use std::{path::Path, sync::Arc}; -use crate::{ - io::{InMemorySourceResolver, SourceResolver}, - parse::parse_source, -}; +use crate::io::{InMemorySourceResolver, SourceResolver}; +use crate::semantic::{parse_source, QasmSemanticParseResult}; pub(crate) mod assignment; pub(crate) mod declaration; @@ -57,11 +54,13 @@ pub(crate) fn generate_qir_from_ast( 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)); - + 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, @@ -71,21 +70,122 @@ pub(crate) fn generate_qir_from_ast( ) } -fn compile_qasm_to_qir(source: &str, profile: Profile) -> Result> { +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)?; - 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, - ), + 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) +} + +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) +} + +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| { @@ -109,12 +209,13 @@ pub(crate) fn compare_compilation_to_qsharp(unit: &QasmCompileUnit, expected: &s difference::assert_diff!(&qsharp, expected, "\n", 0); } -pub(crate) fn parse(source: S) -> miette::Result> +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])?; + let resolver = + InMemorySourceResolver::from_iter([("Test.qasm".into(), source.as_ref().into())]); + let res = parse_source(source, "Test.qasm", &resolver); if res.source.has_errors() { let errors = res .errors() @@ -129,13 +230,16 @@ where pub(crate) fn parse_all

( path: P, sources: impl IntoIterator, Arc)>, -) -> miette::Result> +) -> 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])?; + let source = resolver + .resolve(path.as_ref()) + .map_err(|e| vec![Report::new(e)])? + .1; + let res = parse_source(source, path, &resolver); if res.source.has_errors() { let errors = res .errors() @@ -148,34 +252,15 @@ where } } -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, - ), + 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); @@ -188,19 +273,14 @@ pub fn compile_qasm_to_qsharp_file(source: &str) -> miette::Result 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, - ), + 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); @@ -220,19 +300,14 @@ 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, - ), + let config = CompilerConfig::new( + qubit_semantics, + OutputSemantics::Qiskit, + ProgramType::Fragments, + None, + None, ); + let unit = compile_with_config(source, config)?; qsharp_from_qasm_compilation(unit) } @@ -256,19 +331,14 @@ 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, - ), + 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); @@ -276,12 +346,23 @@ pub fn compile_qasm_stmt_to_qsharp_with_semantics( let Some(package) = unit.package else { panic!("Expected package, got None"); }; - let qsharp = get_first_statement_as_qsharp(&package); + 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 +} + fn get_first_statement_as_qsharp(package: &Package) -> String { - let qsharp = match package.nodes.first() { + let qsharp = match package.nodes.get(1) { Some(i) => match i { TopLevelNode::Namespace(_) => panic!("Expected Stmt, got Namespace"), TopLevelNode::Stmt(stmt) => gen_qsharp_stmt(stmt.as_ref()), diff --git a/compiler/qsc_qasm3/src/tests/assignment.rs b/compiler/qsc_qasm3/src/tests/assignment.rs index cf454274f6..0b5a3cce26 100644 --- a/compiler/qsc_qasm3/src/tests/assignment.rs +++ b/compiler/qsc_qasm3/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,9 +43,7 @@ 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(()) } @@ -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_qasm3/src/tests/assignment/alias.rs index 830646fc2b..eca2f60e2c 100644 --- a/compiler/qsc_qasm3/src/tests/assignment/alias.rs +++ b/compiler/qsc_qasm3/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,9 +32,7 @@ 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(()) } @@ -50,9 +46,7 @@ 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(()) } @@ -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_qasm3/src/tests/declaration.rs b/compiler/qsc_qasm3/src/tests/declaration.rs index bd4136c229..debae1f218 100644 --- a/compiler/qsc_qasm3/src/tests/declaration.rs +++ b/compiler/qsc_qasm3/src/tests/declaration.rs @@ -5,6 +5,7 @@ mod array; mod bit; mod bool; mod complex; +mod def; mod float; mod gate; mod integer; @@ -13,14 +14,12 @@ mod qubit; mod unsigned_integer; use crate::{ - tests::{fail_on_compilation_errors, parse, qasm_to_program_fragments}, + tests::{compile_fragments, compile_with_config, fail_on_compilation_errors}, 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; @@ -35,7 +34,6 @@ fn classical() -> miette::Result<(), Vec> { 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; @@ -43,9 +41,7 @@ fn classical() -> miette::Result<(), Vec> { float[32] m = .1e+3; "#; - 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(()) } @@ -60,30 +56,24 @@ fn duration_literal() -> miette::Result<(), Vec> { 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, - ), + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::Fragments, + None, + None, ); - println!("{:?}", unit.errors); - assert!(unit.errors.len() == 5); + 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!( - error - .to_string() - .contains("Duration type values are not supported.") - || error - .to_string() - .contains("Timing literal expressions are not supported.") - ); + assert!([ + "Duration type values are not supported.", + "Timing literals are not supported.", + ] + .contains(&error.to_string().as_str())); } Ok(()) @@ -95,23 +85,23 @@ fn stretch() { 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, - ), + 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()); - println!("{:?}", unit.errors); - assert!(unit.errors.len() == 1); + 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."),); } diff --git a/compiler/qsc_qasm3/src/tests/declaration/array.rs b/compiler/qsc_qasm3/src/tests/declaration/array.rs index a6e2a874e5..344d89eb6b 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/array.rs +++ b/compiler/qsc_qasm3/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/bool.rs b/compiler/qsc_qasm3/src/tests/declaration/bool.rs index a3bca38d69..18f7c24ce5 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/bool.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/tests/declaration/complex.rs index 88567b1478..039b17c667 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/complex.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/tests/declaration/def.rs b/compiler/qsc_qasm3/src/tests/declaration/def.rs new file mode 100644 index 0000000000..3e1f200e5c --- /dev/null +++ b/compiler/qsc_qasm3/src/tests/declaration/def.rs @@ -0,0 +1,259 @@ +// 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#" + [Qsc.Qasm3.Compile.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#" + [Qsc.Qasm3.Compile.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#" + [Qsc.Qasm3.Compile.UndefinedSymbol + + x Undefined symbol: a. + ,-[Test.qasm:4:20] + 3 | def f() -> int { + 4 | return a; + : ^ + 5 | } + `---- + , Qsc.Qasm3.Compile.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#" + [Qsc.Qasm3.Compile.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 { + `---- + , Qsc.Qasm3.Compile.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_qasm3/src/tests/declaration/float.rs index 5b4eced981..2f7ca8a970 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/float.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/tests/declaration/gate.rs b/compiler/qsc_qasm3/src/tests/declaration/gate.rs index de4614d2d8..879b0cb354 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/gate.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/gate.rs @@ -15,13 +15,11 @@ fn single_qubit() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let my_h : (Qubit) => Unit = (q) => { - H(q); - }; - "# - ] + expect![[r#" + operation my_h(q : Qubit) : Unit is Adj + Ctl { + h(q); + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -37,14 +35,12 @@ fn two_qubits() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let my_h : (Qubit, Qubit) => Unit = (q, q2) => { - H(q2); - H(q); - }; - "# - ] + expect![[r#" + operation my_h(q : Qubit, q2 : Qubit) : Unit is Adj + Ctl { + h(q2); + h(q); + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -59,13 +55,11 @@ fn single_angle_single_qubit() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let my_h : (Double, Qubit) => Unit = (θ, q) => { - Rx(θ, q); - }; - "# - ] + expect![[r#" + operation my_h(θ : __Angle__, q : Qubit) : Unit is Adj + Ctl { + rx(θ, q); + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -81,14 +75,106 @@ fn two_angles_two_qubits() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let my_h : (Double, Double, Qubit, Qubit) => Unit = (θ, φ, q, q2) => { - Rx(θ, q2); - Ry(φ, q); - }; - "# - ] + 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#" + [Qsc.Qasm3.Compile.UndefinedSymbol + + x Undefined symbol: a. + ,-[Test.qasm:4:21] + 3 | gate my_gate q { + 4 | int x = a; + : ^ + 5 | } + `---- + , Qsc.Qasm3.Compile.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#" + [Qsc.Qasm3.Compile.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 { + `---- + , Qsc.Qasm3.Compile.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_qasm3/src/tests/declaration/integer.rs index e4d9e1f9a0..09ba4498b6 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/integer.rs +++ b/compiler/qsc_qasm3/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/explicit_input.rs b/compiler/qsc_qasm3/src/tests/declaration/io/explicit_input.rs index f479416272..7224dc346f 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/io/explicit_input.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/tests/declaration/io/explicit_output.rs b/compiler/qsc_qasm3/src/tests/declaration/io/explicit_output.rs index 92e186465e..ea8ac75890 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/io/explicit_output.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/io/explicit_output.rs @@ -12,15 +12,15 @@ output bit[2] c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Result[] { - mutable c = [Zero, Zero]; - c -} -"# - ] + expect![[r#" + operation Test() : Result[] { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = [Zero, Zero]; + c + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -32,15 +32,15 @@ output bit c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Result { - mutable c = Zero; - c -} -"# - ] + expect![[r#" + operation Test() : Result { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = Zero; + c + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -52,15 +52,15 @@ output bool c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Bool { - mutable c = false; - c -} -"# - ] + expect![[r#" + operation Test() : Bool { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = false; + c + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -72,15 +72,15 @@ 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 -} -"# - ] + 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(()) } @@ -92,15 +92,15 @@ output float f; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Double { - mutable f = 0.; - f -} -"# - ] + expect![[r#" + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable f = 0.; + f + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -112,15 +112,15 @@ output float[42] f; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Double { - mutable f = 0.; - f -} -"# - ] + expect![[r#" + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable f = 0.; + f + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -132,15 +132,15 @@ output int[42] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -152,15 +152,15 @@ output int i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -172,15 +172,15 @@ output uint i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -192,15 +192,15 @@ output uint[42] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -212,15 +212,15 @@ output int[65] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : BigInt { - mutable i = 0L; - i -} -"# - ] + expect![[r#" + operation Test() : BigInt { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -237,7 +237,7 @@ output 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] @@ -255,23 +255,46 @@ 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) + 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_qasm3/src/tests/declaration/io/implicit_output.rs b/compiler/qsc_qasm3/src/tests/declaration/io/implicit_output.rs index 2a927e45e4..79b7291ec8 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/io/implicit_output.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/io/implicit_output.rs @@ -12,15 +12,15 @@ bit[2] c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Result[] { - mutable c = [Zero, Zero]; - c -} -"# - ] + expect![[r#" + operation Test() : Result[] { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = [Zero, Zero]; + c + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -32,15 +32,15 @@ bit c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Result { - mutable c = Zero; - c -} -"# - ] + expect![[r#" + operation Test() : Result { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = Zero; + c + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -52,15 +52,15 @@ bool c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Bool { - mutable c = false; - c -} -"# - ] + expect![[r#" + operation Test() : Bool { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = false; + c + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -72,15 +72,15 @@ 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 -} -"# - ] + 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(()) } @@ -92,15 +92,15 @@ float f; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Double { - mutable f = 0.; - f -} -"# - ] + expect![[r#" + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable f = 0.; + f + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -112,15 +112,15 @@ float[42] f; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Double { - mutable f = 0.; - f -} -"# - ] + expect![[r#" + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable f = 0.; + f + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -132,15 +132,15 @@ int[42] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -152,15 +152,15 @@ int i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -172,15 +172,15 @@ uint i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -192,15 +192,15 @@ uint[42] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -212,15 +212,15 @@ int[65] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : BigInt { - mutable i = 0L; - i -} -"# - ] + expect![[r#" + operation Test() : BigInt { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -240,23 +240,46 @@ 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) + 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_qasm3/src/tests/declaration/qubit.rs index de647e60c7..15ec73d002 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/qubit.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/tests/declaration/unsigned_integer.rs index f5e1695c35..e35fe8a539 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/tests/expression.rs index 8ee361df0c..b8803d23e9 100644 --- a/compiler/qsc_qasm3/src/tests/expression.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/tests/expression/binary.rs index f15be6380e..dc676059b5 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/tests/expression/binary/angle.rs b/compiler/qsc_qasm3/src/tests/expression/binary/angle.rs new file mode 100644 index 0000000000..42a38fd02f --- /dev/null +++ b/compiler/qsc_qasm3/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_qasm3/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_qasm3/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_qasm3/src/tests/expression/binary/comparison.rs index 5bbb6ba08a..a14625c864 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary/comparison.rs +++ b/compiler/qsc_qasm3/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#" + expect![[r#" namespace qasm3_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#" + expect![[r#" namespace qasm3_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#" + expect![[r#" namespace qasm3_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#" + expect![[r#" namespace qasm3_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#" + expect![[r#" namespace qasm3_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#" + expect![[r#" namespace qasm3_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_qasm3/src/tests/expression/binary/complex.rs b/compiler/qsc_qasm3/src/tests/expression/binary/complex.rs index 869e58d936..00366c153a 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary/complex.rs +++ b/compiler/qsc_qasm3/src/tests/expression/binary/complex.rs @@ -2,42 +2,69 @@ // 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> { +fn addition() -> miette::Result<(), Vec> { let source = " input complex[float] a; input complex[float] b; - complex x = (a - b); + complex x = a + b; "; let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - mutable x = (Microsoft.Quantum.Math.MinusC(a, b)); - "# - ] + expect![[r#" + mutable x = Microsoft.Quantum.Math.PlusC(a, b); + "#]] .assert_eq(&qsharp); Ok(()) } #[test] -fn addition() -> miette::Result<(), Vec> { +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); + 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#" - mutable x = (Microsoft.Quantum.Math.PlusC(a, b)); - "# - ] + expect![[r#" + set x = Microsoft.Quantum.Math.MinusC(x, a); + "#]] .assert_eq(&qsharp); Ok(()) } @@ -47,15 +74,29 @@ fn multiplication() -> miette::Result<(), Vec> { let source = " input complex[float] a; input complex[float] b; - complex x = (a * b); + complex x = a * b; "; let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - mutable x = (Microsoft.Quantum.Math.TimesC(a, b)); - "# - ] + 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(()) } @@ -65,34 +106,61 @@ fn division() -> miette::Result<(), Vec> { let source = " input complex[float] a; input complex[float] b; - complex x = (a / b); + complex x = a / b; "; let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - mutable x = (Microsoft.Quantum.Math.DividedByC(a, b)); - "# - ] + 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] -#[ignore = "QASM3 parser bug"] fn power() -> miette::Result<(), Vec> { let source = " input complex[float] a; input complex[float] b; - complex x = (a ** 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#" - mutable x = (Microsoft.Quantum.Math.PowC(a, b)); - "# - ] + 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_qasm3/src/tests/expression/binary/ident.rs index 5e6dee5f9c..a6224743b5 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary/ident.rs +++ b/compiler/qsc_qasm3/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/multiplication.rs b/compiler/qsc_qasm3/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_qasm3/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_qasm3/src/tests/expression/bits.rs index c3c9cddf7a..6751290904 100644 --- a/compiler/qsc_qasm3/src/tests/expression/bits.rs +++ b/compiler/qsc_qasm3/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#" + expect![[r#" namespace qasm3_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_qasm3/src/tests/expression/function_call.rs b/compiler/qsc_qasm3/src/tests/expression/function_call.rs new file mode 100644 index 0000000000..1599bf4d13 --- /dev/null +++ b/compiler/qsc_qasm3/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#" + [Qsc.Qasm3.Compile.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#" + [Qsc.Qasm3.Compile.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#" + [Qsc.Qasm3.Compile.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#" + [Qsc.Qasm3.Compile.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_qasm3/src/tests/expression/ident.rs index 86d689015b..b7a5b1ce6b 100644 --- a/compiler/qsc_qasm3/src/tests/expression/ident.rs +++ b/compiler/qsc_qasm3/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#" + Qsc.Qasm3.Compile.UndefinedSymbol + + x Undefined symbol: t. + ,-[Test.qasm:2:19] + 1 | + 2 | float x = t; + : ^ + 3 | + `---- + + Qsc.Qasm3.Compile.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 @@ -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_qasm3/src/tests/expression/implicit_cast_from_bit.rs index 10dd05ef3a..9ff05cade3 100644 --- a/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_bit.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/tests/expression/implicit_cast_from_bitarray.rs index 73489272a2..c76bffefd4 100644 --- a/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_bitarray.rs +++ b/compiler/qsc_qasm3/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_float.rs b/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_float.rs index 99d28f98dc..a1de1bd0e0 100644 --- a/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_float.rs +++ b/compiler/qsc_qasm3/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![[r#"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_qasm3/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_qasm3/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_qasm3/src/tests/expression/indexed.rs index 6ca7873af5..ab308947d3 100644 --- a/compiler/qsc_qasm3/src/tests/expression/indexed.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/tests/expression/unary.rs b/compiler/qsc_qasm3/src/tests/expression/unary.rs index f4accf3eaf..fd6b709470 100644 --- a/compiler/qsc_qasm3/src/tests/expression/unary.rs +++ b/compiler/qsc_qasm3/src/tests/expression/unary.rs @@ -7,22 +7,28 @@ use miette::Report; use crate::tests::compile_qasm_to_qsharp; #[test] -#[ignore = "OPENQASM 3.0 parser bug"] -fn bitwise_not_int() -> miette::Result<(), Vec> { +fn bitwise_not_int_fails() { 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(()) + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected error"); + }; + + expect![[r#" + [Qsc.Qasm3.Compile.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] @@ -33,12 +39,13 @@ fn not_bool() -> 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 = true; mutable y = not x; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -51,18 +58,13 @@ fn not_result() -> 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 x = One; mutable y = __BoolAsResult__(not __ResultAsBool__(x)); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -75,22 +77,23 @@ fn logical_not_int() -> 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 = 159; mutable y = not if x == 0 { false } else { true }; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } #[test] -#[ignore = "OPENQASM 3.0 parser bug"] +#[ignore = "negating a Result type is an invalid Q# operation"] fn bitwise_not_result() -> miette::Result<(), Vec> { let source = " bit[1] x; @@ -98,10 +101,8 @@ fn bitwise_not_result() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - "# - ] + expect![[r#" + "#]] .assert_eq(&qsharp); Ok(()) } @@ -116,17 +117,59 @@ fn logical_not_indexed_bit_array_in_if_cond() -> 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 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_qasm3/src/tests/output.rs b/compiler/qsc_qasm3/src/tests/output.rs index 711b64e9ad..f3cd433dbc 100644 --- a/compiler/qsc_qasm3/src/tests/output.rs +++ b/compiler/qsc_qasm3/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 { + 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 { + 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 { + 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 { + 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/bell_pair.rs b/compiler/qsc_qasm3/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_qasm3/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_qasm3/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_qasm3/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_qasm3/src/tests/scopes.rs b/compiler/qsc_qasm3/src/tests/scopes.rs index 5480fca9f1..c200ad0ffd 100644 --- a/compiler/qsc_qasm3/src/tests/scopes.rs +++ b/compiler/qsc_qasm3/src/tests/scopes.rs @@ -20,18 +20,19 @@ fn can_access_const_decls_from_global_scope() -> miette::Result<(), Vec> "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let i = 7; - let my_h : (Qubit) => Unit = (q) => { - if i == 0 { - H(q); + 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(()) } @@ -53,3 +54,173 @@ fn cannot_access_mutable_decls_from_global_scope() { }; expect![r#"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_qasm3/src/tests/statement.rs index 9938cb4160..495d7df324 100644 --- a/compiler/qsc_qasm3/src/tests/statement.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/tests/statement/annotation.rs b/compiler/qsc_qasm3/src/tests/statement/annotation.rs index 64155978ba..672058209d 100644 --- a/compiler/qsc_qasm3/src/tests/statement/annotation.rs +++ b/compiler/qsc_qasm3/src/tests/statement/annotation.rs @@ -16,14 +16,87 @@ fn simulatable_intrinsic_can_be_applied_to_gate() -> miette::Result<(), Vec 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(()) } diff --git a/compiler/qsc_qasm3/src/tests/statement/const_eval.rs b/compiler/qsc_qasm3/src/tests/statement/const_eval.rs new file mode 100644 index 0000000000..e4060157e8 --- /dev/null +++ b/compiler/qsc_qasm3/src/tests/statement/const_eval.rs @@ -0,0 +1,1984 @@ +// 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#" + Qsc.Qasm3.Compile.ExprMustBeConst + + x expression must be const + ,-[Test.qasm:5:13] + 4 | int c = a + 3; + 5 | bit[b] r1; + : ^ + 6 | bit[c] r2; + `---- + + Qsc.Qasm3.Compile.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; + `---- + + Qsc.Qasm3.Compile.ExprMustBeConst + + x expression must be const + ,-[Test.qasm:6:13] + 5 | bit[b] r1; + 6 | bit[c] r2; + : ^ + 7 | + `---- + + Qsc.Qasm3.Compile.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#" + Qasm3.Parse.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; + `---- + + Qasm3.Parse.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; + `---- + + Qsc.Qasm3.Compile.UndefinedSymbol + + x Undefined symbol: b. + ,-[Test.qasm:4:13] + 3 | const creg b[3] = a << 2; + 4 | bit[b] r; + : ^ + 5 | + `---- + + Qsc.Qasm3.Compile.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 | + `---- + + Qsc.Qasm3.Compile.ExprMustBeConst + + x expression must be const + ,-[Test.qasm:4:13] + 3 | const creg b[3] = a << 2; + 4 | bit[b] r; + : ^ + 5 | + `---- + + Qsc.Qasm3.Compile.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#" + Qasm3.Parse.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; + `---- + + Qasm3.Parse.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; + `---- + + Qsc.Qasm3.Compile.UndefinedSymbol + + x Undefined symbol: b. + ,-[Test.qasm:4:13] + 3 | const creg b[4] = a >> 2; + 4 | bit[b] r; + : ^ + 5 | + `---- + + Qsc.Qasm3.Compile.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 | + `---- + + Qsc.Qasm3.Compile.ExprMustBeConst + + x expression must be const + ,-[Test.qasm:4:13] + 3 | const creg b[4] = a >> 2; + 4 | bit[b] r; + : ^ + 5 | + `---- + + Qsc.Qasm3.Compile.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(()) +} diff --git a/compiler/qsc_qasm3/src/tests/statement/end.rs b/compiler/qsc_qasm3/src/tests/statement/end.rs index 411f9fe7ea..61a8a58703 100644 --- a/compiler/qsc_qasm3/src/tests/statement/end.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/tests/statement/for_loop.rs index 4c476bdf96..ea708fccef 100644 --- a/compiler/qsc_qasm3/src/tests/statement/for_loop.rs +++ b/compiler/qsc_qasm3/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![[r#"Undefined symbol: i."#]].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 index 3165ccd847..d1f7ec175d 100644 --- a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs +++ b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs @@ -6,6 +6,42 @@ 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 x_gate_can_be_called() -> miette::Result<(), Vec> { let source = r#" @@ -15,12 +51,13 @@ fn 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(); - X(q); - "# - ] + x(q); + "#]] .assert_eq(&qsharp); Ok(()) } @@ -34,14 +71,13 @@ fn barrier_can_be_called_on_single_qubit() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - @SimulatableIntrinsic() - operation __quantum__qis__barrier__body() : Unit {} + 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(()) } @@ -55,14 +91,13 @@ fn barrier_can_be_called_without_qubits() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - @SimulatableIntrinsic() - operation __quantum__qis__barrier__body() : Unit {} + 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(()) } @@ -81,7 +116,7 @@ fn barrier_generates_qir() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qir(source, Profile::AdaptiveRI)?; - expect![ + expect![[ r#" %Result = type opaque %Qubit = type opaque @@ -119,7 +154,7 @@ fn barrier_generates_qir() -> miette::Result<(), Vec> { !3 = !{i32 1, !"dynamic_result_management", i1 false} !4 = !{i32 1, !"int_computations", !"i64"} "# - ] + ]] .assert_eq(&qsharp); Ok(()) } @@ -133,14 +168,358 @@ fn barrier_can_be_called_on_two_qubit() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - @SimulatableIntrinsic() - operation __quantum__qis__barrier__body() : Unit {} + 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#" + [Qsc.Qasm3.Compile.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#" + [Qsc.Qasm3.Compile.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#" + [Qsc.Qasm3.Compile.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#" + [Qsc.Qasm3.Compile.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(()) +} diff --git a/compiler/qsc_qasm3/src/tests/statement/if_stmt.rs b/compiler/qsc_qasm3/src/tests/statement/if_stmt.rs index 6dbd2129f0..a5714850fd 100644 --- a/compiler/qsc_qasm3/src/tests/statement/if_stmt.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/tests/statement/implicit_modified_gate_call.rs b/compiler/qsc_qasm3/src/tests/statement/implicit_modified_gate_call.rs index 0163ac470e..c75fca08fb 100644 --- a/compiler/qsc_qasm3/src/tests/statement/implicit_modified_gate_call.rs +++ b/compiler/qsc_qasm3/src/tests/statement/implicit_modified_gate_call.rs @@ -5,6 +5,72 @@ 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#" @@ -14,12 +80,13 @@ fn sdg_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 S(q); - "# - ] + Adjoint s(q); + "#]] .assert_eq(&qsharp); Ok(()) } @@ -33,12 +100,13 @@ fn tdg_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 T(q); - "# - ] + Adjoint t(q); + "#]] .assert_eq(&qsharp); Ok(()) } @@ -47,17 +115,20 @@ fn tdg_gate_can_be_called() -> miette::Result<(), Vec> { 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]; + qubit ctl; + qubit target; + crx(0.5) ctl, target; "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - let q = QIR.Runtime.AllocateQubitArray(2); - Controlled Rx([q[1]], (0.5, q[0])); - "# - ] + 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(()) } @@ -66,17 +137,20 @@ fn crx_gate_can_be_called() -> miette::Result<(), Vec> { 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]; + qubit ctl; + qubit target; + cry(0.5) ctl, target; "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - let q = QIR.Runtime.AllocateQubitArray(2); - Controlled Ry([q[1]], (0.5, q[0])); - "# - ] + 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(()) } @@ -85,36 +159,86 @@ fn cry_gate_can_be_called() -> miette::Result<(), Vec> { 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]; + qubit ctl; + qubit target; + crz(0.5) ctl, target; "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - let q = QIR.Runtime.AllocateQubitArray(2); - Controlled Rz([q[1]], (0.5, q[0])); - "# - ] + 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 ch_gate_can_be_called() -> miette::Result<(), Vec> { +fn cswap_gate_can_be_called() -> miette::Result<(), Vec> { let source = r#" include "stdgates.inc"; + qubit ctl; qubit[2] q; - ch q[1], q[0]; + cswap ctl, q[0], q[1]; "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + 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 H([q[1]], q[0]); - "# - ] + 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_qasm3/src/tests/statement/include.rs b/compiler/qsc_qasm3/src/tests/statement/include.rs index 7d096298a4..19e4f96472 100644 --- a/compiler/qsc_qasm3/src/tests/statement/include.rs +++ b/compiler/qsc_qasm3/src/tests/statement/include.rs @@ -2,8 +2,7 @@ // Licensed under the MIT License. use crate::{ - qasm_to_program, - tests::{parse_all, qsharp_from_qasm_compilation}, + tests::{compile_all_with_config, qsharp_from_qasm_compilation}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; use expect_test::expect; @@ -30,27 +29,25 @@ fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { ("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 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 qasm3_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; @EntryPoint() operation Test() : Result[] { @SimulatableIntrinsic() operation my_gate(q : Qubit) : Unit { - X(q); + x(q); } mutable c = [Zero]; let q = QIR.Runtime.AllocateQubitArray(1); diff --git a/compiler/qsc_qasm3/src/tests/statement/measure.rs b/compiler/qsc_qasm3/src/tests/statement/measure.rs index c21b8e6a87..f3695d51e6 100644 --- a/compiler/qsc_qasm3/src/tests/statement/measure.rs +++ b/compiler/qsc_qasm3/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#" + Qsc.Qasm3.Compile.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_qasm3/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_qasm3/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_qasm3/src/tests/statement/reset.rs index c6d9a7b86a..d0ef520368 100644 --- a/compiler/qsc_qasm3/src/tests/statement/reset.rs +++ b/compiler/qsc_qasm3/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#" + expect![[r#" namespace qasm3_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_qasm3/src/tests/statement/switch.rs index 17aa7257bd..cafd8d2627 100644 --- a/compiler/qsc_qasm3/src/tests/statement/switch.rs +++ b/compiler/qsc_qasm3/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#" + expect![[r#" namespace qasm3_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_qasm3/src/tests/statement/while_loop.rs index 2f92febde3..e2910a871b 100644 --- a/compiler/qsc_qasm3/src/tests/statement/while_loop.rs +++ b/compiler/qsc_qasm3/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_qasm3/src/types.rs b/compiler/qsc_qasm3/src/types.rs index 6b24bfd83a..d8f607001b 100644 --- a/compiler/qsc_qasm3/src/types.rs +++ b/compiler/qsc_qasm3/src/types.rs @@ -97,9 +97,9 @@ impl Complex { } } -#[allow(dead_code)] -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) enum Type { +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub enum Type { + Angle(bool), Bool(bool), BigInt(bool), Complex(bool), @@ -117,7 +117,9 @@ pub(crate) enum Type { ResultArray(ArrayDimensions, bool), TupleArray(ArrayDimensions, Vec), /// Function or operation, with the number of classical parameters and qubits. - Callable(CallableKind, usize, usize), + Callable(CallableKind, u32, u32), + #[default] + Err, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -156,9 +158,27 @@ impl From<&ArrayDims> for ArrayDimensions { } } +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"), @@ -196,6 +216,7 @@ impl Display for Type { Type::Callable(kind, num_classical, num_qubits) => { write!(f, "Callable({kind}, {num_classical}, {num_qubits})") } + Type::Err => write!(f, "Err"), } } } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 34b9ba6609..552c9e4d18 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 = "qasm3" +path = "fuzz_targets/qasm3.rs" test = false doc = false diff --git a/fuzz/fuzz_targets/qasm3.rs b/fuzz/fuzz_targets/qasm3.rs new file mode 100644 index 0000000000..ce7b6f2ee1 --- /dev/null +++ b/fuzz/fuzz_targets/qasm3.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 resolver = qsc::qasm3::io::InMemorySourceResolver::from_iter([]); + let _ = qsc::qasm3::parser::parse_source(fuzzed_code, "fuzz.qasm", &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/qasm3/input.qasm b/fuzz/seed_inputs/qasm3/input.qasm new file mode 100644 index 0000000000..cddbfe007f --- /dev/null +++ b/fuzz/seed_inputs/qasm3/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/qasm3/list.txt b/fuzz/seed_inputs/qasm3/list.txt new file mode 100644 index 0000000000..08acb8e3f9 --- /dev/null +++ b/fuzz/seed_inputs/qasm3/list.txt @@ -0,0 +1 @@ +fuzz/seed_inputs/qasm3/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/src/interop.rs b/pip/src/interop.rs index b109061dd8..4a6cd01f62 100644 --- a/pip/src/interop.rs +++ b/pip/src/interop.rs @@ -8,16 +8,17 @@ 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::io::{Error, ErrorKind}; use qsc::qasm3::{ qasm_to_program, CompilerConfig, OperationSignature, QasmCompileUnit, 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}; @@ -55,12 +56,15 @@ impl SourceResolver for ImportResolver where T: FileSystem, { - fn resolve

(&self, path: P) -> miette::Result<(PathBuf, String)> + fn resolve

(&self, path: P) -> miette::Result<(PathBuf, String), Error> where P: AsRef, { let path = self.path.join(path); - let (path, source) = self.fs.read_file(path.as_ref())?; + let (path, source) = self + .fs + .read_file(path.as_ref()) + .map_err(|e| Error(ErrorKind::IO(format!("{e}"))))?; Ok(( PathBuf::from(path.as_ref().to_owned()), source.as_ref().to_owned(), @@ -535,12 +539,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::qasm3::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,