From b0da809df69f4a5a9493f4b5d51b446deff5aa72 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Sun, 2 Feb 2025 15:07:06 -0800 Subject: [PATCH 01/98] Implement OpenQASM 3 lexer (#2129) This PR adds our own implementation of an OpenQASM3 parser to the `compiler/qsc_qasm3` directory. --- Cargo.lock | 2 +- compiler/qsc_qasm3/Cargo.toml | 2 +- compiler/qsc_qasm3/src/keyword.rs | 104 ++ compiler/qsc_qasm3/src/lex.rs | 51 + compiler/qsc_qasm3/src/lex/cooked.rs | 696 ++++++++++++ compiler/qsc_qasm3/src/lex/cooked/tests.rs | 941 +++++++++++++++++ compiler/qsc_qasm3/src/lex/raw.rs | 601 +++++++++++ compiler/qsc_qasm3/src/lex/raw/tests.rs | 1112 ++++++++++++++++++++ compiler/qsc_qasm3/src/lib.rs | 2 + 9 files changed, 3509 insertions(+), 2 deletions(-) create mode 100644 compiler/qsc_qasm3/src/keyword.rs create mode 100644 compiler/qsc_qasm3/src/lex.rs create mode 100644 compiler/qsc_qasm3/src/lex/cooked.rs create mode 100644 compiler/qsc_qasm3/src/lex/cooked/tests.rs create mode 100644 compiler/qsc_qasm3/src/lex/raw.rs create mode 100644 compiler/qsc_qasm3/src/lex/raw/tests.rs diff --git a/Cargo.lock b/Cargo.lock index e65899b1bc..0206fd7648 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1476,11 +1476,11 @@ version = "0.0.0" dependencies = [ "bitflags 2.6.0", "difference", + "enum-iterator", "expect-test", "indoc", "miette", "num-bigint", - "oq3_lexer", "oq3_parser", "oq3_semantics", "oq3_source_file", diff --git a/compiler/qsc_qasm3/Cargo.toml b/compiler/qsc_qasm3/Cargo.toml index b7a5d9f294..4be5b3092d 100644 --- a/compiler/qsc_qasm3/Cargo.toml +++ b/compiler/qsc_qasm3/Cargo.toml @@ -9,6 +9,7 @@ version.workspace = true [dependencies] bitflags = { workspace = true } +enum-iterator = { workspace = true } num-bigint = { workspace = true } miette = { workspace = true } qsc_ast = { path = "../qsc_ast" } @@ -20,7 +21,6 @@ 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/keyword.rs b/compiler/qsc_qasm3/src/keyword.rs new file mode 100644 index 0000000000..dd30c8bd02 --- /dev/null +++ b/compiler/qsc_qasm3/src/keyword.rs @@ -0,0 +1,104 @@ +// 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 { + Box, + Break, + Cal, + Case, + Continue, + Def, + Default, + Defcalgrammar, + Else, + End, + Extern, + For, + Gate, + If, + In, + Include, + Let, + OpenQASM, + Pragma, + Return, + Switch, + While, +} + +impl Keyword { + pub(super) fn as_str(self) -> &'static str { + match self { + Keyword::Box => "box", + Keyword::Break => "break", + Keyword::Cal => "cal", + Keyword::Case => "case", + Keyword::Continue => "continue", + Keyword::Def => "def", + Keyword::Default => "default", + Keyword::Defcalgrammar => "defcalgrammar", + Keyword::Else => "else", + Keyword::End => "end", + Keyword::Extern => "extern", + Keyword::For => "for", + Keyword::Gate => "gate", + Keyword::If => "if", + Keyword::In => "in", + Keyword::Include => "include", + Keyword::Let => "let", + Keyword::OpenQASM => "openqasm", + Keyword::Pragma => "pragma", + Keyword::Return => "return", + Keyword::Switch => "switch", + 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 { + "box" => Ok(Self::Box), + "break" => Ok(Self::Break), + "cal" => Ok(Self::Cal), + "case" => Ok(Self::Case), + "continue" => Ok(Self::Continue), + "def" => Ok(Self::Def), + "default" => Ok(Self::Default), + "defcalgrammar" => Ok(Self::Defcalgrammar), + "else" => Ok(Self::Else), + "end" => Ok(Self::End), + "extern" => Ok(Self::Extern), + "for" => Ok(Self::For), + "gate" => Ok(Self::Gate), + "if" => Ok(Self::If), + "in" => Ok(Self::In), + "include" => Ok(Self::Include), + "let" => Ok(Self::Let), + "openqasm" => Ok(Self::OpenQASM), + "pragma" => Ok(Self::Pragma), + "return" => Ok(Self::Return), + "switch" => Ok(Self::Switch), + "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..34683c5259 --- /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::{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..faebc1bb28 --- /dev/null +++ b/compiler/qsc_qasm3/src/lex/cooked.rs @@ -0,0 +1,696 @@ +// 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("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::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::Unknown(_, s) => s, + } + } +} + +/// A token kind. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum TokenKind { + Annotation, + Keyword(Keyword), + Type(Type), + + // Builtin identifiers and operations + GPhase, + Inv, + Pow, + Ctrl, + NegCtrl, + Dim, + DurationOf, + Delay, + Reset, + Measure, + Barrier, + + Literal(Literal), + + // Symbols + /// `{[(` + Open(Delim), + /// `}])` + Close(Delim), + + // Punctuation + /// `:` + Colon, + /// `;` + Semicolon, + /// `.` + Dot, + /// `,` + Comma, + /// `++` + PlusPlus, + /// `->` + Arrow, + + // Operators, + ClosedBinOp(ClosedBinOp), + BinOpEq(ClosedBinOp), + ComparisonOp(ComparisonOp), + /// `=` + Eq, + /// `!` + Bang, + /// `~` + Tilde, + + Identifier, + HardwareQubit, +} + +impl Display for TokenKind { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + TokenKind::Annotation => write!(f, "annotation"), + 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::Delay => write!(f, "delay"), + TokenKind::Reset => write!(f, "reset"), + TokenKind::Measure => write!(f, "measure"), + TokenKind::Barrier => write!(f, "barrier"), + 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::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"), + } + } +} + +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, + Boolean, + 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::Boolean => "boolean", + 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>, +} + +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(), + } + } + + 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) + } + + /// Returns the second token ahead of the cursor without consuming it. This is slower + /// than [`first`] and should be avoided when possible. + fn second(&self) -> Option { + let mut tokens = self.tokens.clone(); + tokens.next(); + tokens.next().map(|i| i.kind) + } + + /// 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(()) + } + + 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::Newline | raw::TokenKind::Whitespace => { + Ok(None) + } + raw::TokenKind::Ident => { + let ident = &self.input[(token.offset as usize)..(self.offset() as usize)]; + Ok(Some(Self::ident(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. + match (self.first(), self.second()) { + (Some(raw::TokenKind::LiteralFragment(fragment)), _) + | ( + Some(raw::TokenKind::Whitespace), + Some(raw::TokenKind::LiteralFragment(fragment)), + ) => { + use self::Literal::{Imaginary, Timing}; + use TokenKind::Literal; + + // if first() was a whitespace, we need to consume an extra token + if self.first() == Some(raw::TokenKind::Whitespace) { + self.next(); + } + 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)), + })) + } + _ => Ok(Some(number.into())), + } + } + 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 => { + let complete = TokenKind::Annotation; + self.expect(raw::TokenKind::Ident, complete)?; + self.kleen_star( + &[raw::TokenKind::Single(Single::Dot), raw::TokenKind::Ident], + complete, + )?; + Ok(complete) + } + 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::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, + "delay" => TokenKind::Delay, + "reset" => TokenKind::Reset, + "measure" => TokenKind::Measure, + "barrier" => TokenKind::Barrier, + "false" | "true" => TokenKind::Literal(Literal::Boolean), + 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) => {} + Ok(Some(token)) => return Some(Ok(token)), + Err(err) => 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..7002c957b7 --- /dev/null +++ b/compiler/qsc_qasm3/src/lex/cooked/tests.rs @@ -0,0 +1,941 @@ +// 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::Delay => Some("delay".to_string()), + TokenKind::Reset => Some("reset".to_string()), + TokenKind::Measure => Some("measure".to_string()), + TokenKind::Barrier => Some("barrier".to_string()), + TokenKind::Semicolon => Some(";".to_string()), + TokenKind::Arrow => Some("->".to_string()), + TokenKind::ClosedBinOp(op) => Some(op.to_string()), + TokenKind::BinOpEq(super::ClosedBinOp::AmpAmp | super::ClosedBinOp::BarBar) + | TokenKind::Literal(_) + | TokenKind::Annotation => 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()), + } +} + +#[test] +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 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 hardware_qubit() { + check( + r"$12", + &expect![[r#" + [ + Ok( + Token { + kind: HardwareQubit, + span: Span { + lo: 0, + hi: 3, + }, + }, + ), + ] + "#]], + ); +} + +#[test] +fn unknown() { + check( + "##", + &expect![[r#" + [ + Err( + Unknown( + '#', + Span { + lo: 0, + hi: 1, + }, + ), + ), + Err( + Unknown( + '#', + Span { + lo: 1, + 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, + }, + }, + ), + ] + "#]], + ); +} diff --git a/compiler/qsc_qasm3/src/lex/raw.rs b/compiler/qsc_qasm3/src/lex/raw.rs new file mode 100644 index 0000000000..65bf3d7fea --- /dev/null +++ b/compiler/qsc_qasm3/src/lex/raw.rs @@ -0,0 +1,601 @@ +// 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 LexError { + /// An incomplete token was parsed, e.g., a string missing + /// the closing quote or a number ending in an underscore. + Incomplete(T), + /// 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, + /// `/` + 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::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> { + self.leading_zero(c) + .or_else(|_| self.leading_dot(c)) + .or_else(|_| 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> { + let first = self.first(); + if c == '.' && first.is_some_and(|c| c == '_' || c.is_ascii_digit()) { + self.chars.next(); + let c1 = first.expect("first.is_some_and() succeeded"); + self.decimal(c1)?; + match self.exp() { + Ok(()) | Err(LexError::None) => Ok(Number::Float), + Err(_) => Err(LexError::Incomplete(Number::Float)), + } + } else { + Err(LexError::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 == '_' || c1.is_ascii_digit() => { + self.chars.next(); + match self.decimal(c1) { + Err(LexError::Incomplete(_)) => Err(LexError::Incomplete(Number::Float)), + Ok(_) | Err(LexError::None) => match self.exp() { + Ok(()) | Err(LexError::None) => Ok(Number::Float), + Err(_) => Err(LexError::Incomplete(Number::Float)), + }, + } + } + None | Some(_) => Ok(Number::Float), + } + } else { + Err(LexError::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(LexError::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 | Some('_') => Err(LexError::Incomplete(Number::Int(radix))), + _ => Ok(Number::Int(radix)), + }, + Radix::Decimal => match self.first() { + Some(c1 @ '.') => { + self.chars.next(); + self.mid_dot(c1) + } + 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(LexError::None); + } + + let last_eaten = self.eat_while(|c| c == '_' || c.is_ascii_digit()); + + match last_eaten { + None if c == '_' => Err(LexError::None), + Some('_') => Err(LexError::Incomplete(Number::Int(Radix::Decimal))), + _ => 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(LexError::None) => Ok(Number::Int(Radix::Decimal)), + Err(_) => Err(LexError::Incomplete(Number::Float)), + }, + } + } + + /// Parses an exponent. Errors if the exponent was missing or incomplete. + /// 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<(), LexError> { + 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 the next character isn't a digit or an + // underscore we issue an error without consuming it. + let first = self.first().ok_or(LexError::Incomplete(Number::Float))?; + if first != '_' && !first.is_ascii_digit() { + Err(LexError::Incomplete(Number::Float)) + } else { + self.chars.next(); + match self.decimal(first) { + Ok(_) => Ok(()), + Err(_) => Err(LexError::Incomplete(Number::Float)), + } + } + } else { + Err(LexError::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); + } + + 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(LexError::Incomplete(_)) => TokenKind::Unknown, + Err(LexError::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), + _ => 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..96ff8383ec --- /dev/null +++ b/compiler/qsc_qasm3/src/lex/raw/tests.rs @@ -0,0 +1,1112 @@ +// 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_escape_quote() { + check( + r#""str\"ing""#, + &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 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 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 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: Unknown, + offset: 0, + }, + Token { + kind: Unknown, + 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, + }, + ] + "#]], + ); +} diff --git a/compiler/qsc_qasm3/src/lib.rs b/compiler/qsc_qasm3/src/lib.rs index b1d6fd35c1..9c1f806371 100644 --- a/compiler/qsc_qasm3/src/lib.rs +++ b/compiler/qsc_qasm3/src/lib.rs @@ -6,6 +6,8 @@ mod ast_builder; mod compile; pub use compile::qasm_to_program; pub mod io; +mod keyword; +mod lex; mod oqasm_helpers; mod oqasm_types; pub mod parse; From fd616c468e334133135323acf5454acb06121b86 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 3 Feb 2025 18:02:15 -0800 Subject: [PATCH 02/98] Add initial AST and parser infrastructure (#2148) --- Cargo.lock | 2 + compiler/qsc_qasm3/Cargo.toml | 2 + compiler/qsc_qasm3/src/ast.rs | 2018 +++++++++++++++++ compiler/qsc_qasm3/src/keyword.rs | 76 +- compiler/qsc_qasm3/src/lex.rs | 2 +- compiler/qsc_qasm3/src/lex/cooked.rs | 18 +- compiler/qsc_qasm3/src/lex/cooked/tests.rs | 4 +- compiler/qsc_qasm3/src/lib.rs | 2 + compiler/qsc_qasm3/src/parser.rs | 265 +++ compiler/qsc_qasm3/src/parser/completion.rs | 39 + .../src/parser/completion/collector.rs | 186 ++ .../qsc_qasm3/src/parser/completion/tests.rs | 55 + .../src/parser/completion/word_kinds.rs | 179 ++ compiler/qsc_qasm3/src/parser/error.rs | 137 ++ compiler/qsc_qasm3/src/parser/expr.rs | 292 +++ compiler/qsc_qasm3/src/parser/prgm.rs | 76 + compiler/qsc_qasm3/src/parser/prim.rs | 326 +++ compiler/qsc_qasm3/src/parser/prim/tests.rs | 265 +++ compiler/qsc_qasm3/src/parser/scan.rs | 248 ++ compiler/qsc_qasm3/src/parser/stmt.rs | 132 ++ compiler/qsc_qasm3/src/parser/tests.rs | 172 ++ 21 files changed, 4477 insertions(+), 19 deletions(-) create mode 100644 compiler/qsc_qasm3/src/ast.rs create mode 100644 compiler/qsc_qasm3/src/parser.rs create mode 100644 compiler/qsc_qasm3/src/parser/completion.rs create mode 100644 compiler/qsc_qasm3/src/parser/completion/collector.rs create mode 100644 compiler/qsc_qasm3/src/parser/completion/tests.rs create mode 100644 compiler/qsc_qasm3/src/parser/completion/word_kinds.rs create mode 100644 compiler/qsc_qasm3/src/parser/error.rs create mode 100644 compiler/qsc_qasm3/src/parser/expr.rs create mode 100644 compiler/qsc_qasm3/src/parser/prgm.rs create mode 100644 compiler/qsc_qasm3/src/parser/prim.rs create mode 100644 compiler/qsc_qasm3/src/parser/prim/tests.rs create mode 100644 compiler/qsc_qasm3/src/parser/scan.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt.rs create mode 100644 compiler/qsc_qasm3/src/parser/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 0206fd7648..c202b2f70b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1478,9 +1478,11 @@ dependencies = [ "difference", "enum-iterator", "expect-test", + "indenter", "indoc", "miette", "num-bigint", + "num-traits", "oq3_parser", "oq3_semantics", "oq3_source_file", diff --git a/compiler/qsc_qasm3/Cargo.toml b/compiler/qsc_qasm3/Cargo.toml index 4be5b3092d..0884792b61 100644 --- a/compiler/qsc_qasm3/Cargo.toml +++ b/compiler/qsc_qasm3/Cargo.toml @@ -10,7 +10,9 @@ 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" } diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs new file mode 100644 index 0000000000..b63abb55d3 --- /dev/null +++ b/compiler/qsc_qasm3/src/ast.rs @@ -0,0 +1,2018 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// while we work through the conversion, allow dead code to avoid warnings +#![allow(dead_code)] + +use indenter::{indented, Indented}; +use qsc_data_structures::span::{Span, WithSpan}; +use std::{ + fmt::{self, Display, Formatter, Write}, + hash::Hash, + rc::Rc, +}; + +fn set_indentation<'a, 'b>( + indent: Indented<'a, Formatter<'b>>, + level: usize, +) -> Indented<'a, Formatter<'b>> { + match level { + 0 => indent.with_str(""), + 1 => indent.with_str(" "), + 2 => indent.with_str(" "), + _ => unimplemented!("indentation level not supported"), + } +} + +// TODO: profile this with iai-callgrind in a large OpenQASM3 +// sample to verify that is actually faster than using Vec. +/// An alternative to `Vec` that uses less stack space. +type List = Box<[Box]>; + +#[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 { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "Program {}:", self.span)?; + indent = set_indentation(indent, 1); + if let Some(version) = &self.version { + write!(indent, "\nVersion {version}")?; + } + for stmt in &self.statements { + write!(indent, "\n{stmt}")?; + } + Ok(()) + } +} + +#[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 { + let mut indent = set_indentation(indented(f), 0); + for annotation in &self.annotations { + write!(indent, "\n{annotation}")?; + } + write!(indent, "Stmt {}", self.span)?; + write!(indent, "\n{}", self.kind)?; + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct Annotation { + pub span: Span, + pub name: Box, + pub value: Option>, +} +impl Display for Annotation { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(value) = &self.value { + write!(f, "Annotation {}: {}, {}", self.span, self.name, value) + } else { + write!(f, "Annotation {}: {}", self.span, self.name) + } + } +} + +/// 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)) => { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "Err IncompletePath {}:", incomplete_path.span)?; + indent = set_indentation(indent, 1); + for part in &incomplete_path.segments { + write!(indent, "\n{part}")?; + } + } + PathKind::Err(None) => write!(f, "Err",)?, + } + Ok(()) + } +} + +/// 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 { + if self.segments.is_none() { + write!(f, "Path {} ({})", self.span, self.name)?; + } else { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "Path {}:", self.span)?; + indent = set_indentation(indent, 1); + if let Some(parts) = &self.segments { + for part in parts { + write!(indent, "\n{part}")?; + } + } + write!(indent, "\n{}", self.name)?; + } + Ok(()) + } +} + +impl WithSpan for Path { + fn with_span(self, span: Span) -> Self { + Self { span, ..self } + } +} + +#[derive(Clone, Debug)] +pub enum AssignmentExpr { + Expr(Expr), + Measurement(MeasureExpr), +} + +impl Display for AssignmentExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + AssignmentExpr::Expr(expr) => write!(f, "AssignmentExpr {expr}"), + AssignmentExpr::Measurement(measure) => write!(f, "AssignmentExpr {measure}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct MeasureExpr { + pub span: Span, + pub operand: GateOperand, +} + +impl Display for MeasureExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "MeasureExpr {}: {}", self.span, self.operand) + } +} +/// A binary operator. +#[derive(Clone, 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, Debug)] +pub enum UnOp { + /// Negation: `-`. + Neg, + /// Bitwise NOT: `~`. + NotB, + /// Logical NOT: `!`. + NotL, +} + +impl Display for UnOp { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + UnOp::Neg => write!(f, "Neg"), + UnOp::NotB => write!(f, "NotB"), + UnOp::NotL => write!(f, "NotL"), + } + } +} + +#[derive(Clone, Debug)] +pub enum GateOperand { + Ident(Box), + HardwareQubit(Box), +} + +impl Display for GateOperand { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + GateOperand::Ident(ident) => write!(f, "GateOperand {ident}"), + GateOperand::HardwareQubit(qubit) => write!(f, "GateOperand {qubit}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct HardwareQubit { + pub span: Span, + pub name: Rc, +} + +impl Display for HardwareQubit { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "HardwareQubit {}: {}", self.span, self.name) + } +} + +#[derive(Clone, Debug)] +pub struct Alias { + pub ident: Box, + pub expr: Box>, + pub span: Span, +} + +impl Display for Alias { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "Alias {}: {}", self.span, self.ident)?; + indent = set_indentation(indent, 1); + for expr in &*self.expr { + write!(indent, "\n{expr}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct Assign { + pub ident: Box, + pub expr: Box, + pub span: Span, +} + +impl Display for Assign { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Assign {}: {}, {}", self.span, self.ident, self.expr) + } +} + +#[derive(Clone, Debug)] +pub struct AssignOp { + pub op: BinOp, + pub ident: Box, + pub expr: Box, + pub span: Span, +} + +impl Display for AssignOp { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "AssignOp {}: {}, {}, {}", + self.span, self.op, self.ident, self.expr + ) + } +} + +/// A statement kind. +#[derive(Clone, Debug, Default)] +pub enum StmtKind { + Alias(Alias), + Assign(Assign), + AssignOp(AssignOp), + Barrier(BarrierStmt), + Box(BoxStmt), + Break(BreakStmt), + Block(Box), + Cal(CalibrationStmt), + CalibrationGrammar(CalibrationGrammarStmt), + ClassicalDecl(ClassicalDeclarationStmt), + ConstDecl(ConstantDeclaration), + Continue(ContinueStmt), + Def(DefStmt), + DefCal(DefCalStmt), + DelayStmt(DelayStmt), + /// An empty statement. + Empty, + ExprStmt(ExprStmt), + ExternDecl(ExternDecl), + For(ForStmt), + If(IfStmt), + Include(IncludeStmt), + IODeclaration(IODeclaration), + Measure(MeasureStmt), + Pragma(Pragma), + QuantumGateDefinition(QuantumGateDefinition), + QuantumDecl(QubitDeclaration), + Quantum(QuantumStmt), + 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 { + write!(f, "StmtKind: ")?; + match self { + StmtKind::Alias(alias) => write!(f, "{alias}"), + StmtKind::Assign(assign) => write!(f, "{assign}"), + StmtKind::AssignOp(assign_op) => write!(f, "{assign_op}"), + 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::DelayStmt(delay) => write!(f, "{delay}"), + StmtKind::Empty => write!(f, "Empty"), + StmtKind::ExprStmt(expr) => write!(f, "{expr}"), + StmtKind::ExternDecl(decl) => write!(f, "{decl}"), + StmtKind::For(for_stmt) => write!(f, "{for_stmt}"), + 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::Quantum(quantum_stmt) => write!(f, "{quantum_stmt}"), + 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 { + write!(f, "CalibrationGrammarStmt {}: {}", self.span, self.name) + } +} + +#[derive(Clone, Debug)] +pub struct DefCalStmt {} + +impl Display for DefCalStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "DefCalStmt") + } +} + +#[derive(Clone, Debug)] +pub struct IfStmt { + pub span: Span, + pub condition: ExprStmt, + pub if_block: List, + pub else_block: Option>, +} + +impl Display for IfStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "IfStmt {}: {}", self.span, self.condition)?; + for stmt in &self.if_block { + write!(indent, "\n{stmt}")?; + } + if let Some(else_block) = &self.else_block { + write!(indent, "\nElse:")?; + for stmt in else_block { + write!(indent, "\n{stmt}")?; + } + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct DelayStmt { + pub span: Span, + pub duration: ExprStmt, +} + +impl Display for DelayStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "DelayStmt {}: {}", self.span, self.duration) + } +} + +#[derive(Clone, Debug)] +pub struct BarrierStmt { + pub span: Span, + pub qubits: List, +} + +impl Display for BarrierStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "Barrier {}: [", self.span)?; + for qubit in &self.qubits { + write!(indent, "\n{qubit}")?; + } + write!(indent, "]") + } +} + +#[derive(Clone, Debug)] +pub struct ResetStmt { + pub span: Span, + pub operand: Box, +} + +impl Display for ResetStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "ResetStmt {}: {}", self.span, 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 { + if self.stmts.is_empty() { + write!(f, "Block {}: ", self.span)?; + } else { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "Block {}:", self.span)?; + indent = set_indentation(indent, 1); + for s in &self.stmts { + write!(indent, "\n{s}")?; + } + } + Ok(()) + } +} + +#[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: Ident, + pub indices: List, +} + +impl Display for IndexedIdent { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "IndexedIdent {}: {}[", self.span, self.name)?; + + for index in &self.indices { + write!(f, "\n{index}")?; + } + write!(f, "]") + } +} + +#[derive(Clone, Debug)] +pub struct AliasStmt { + pub span: Span, + pub kind: Box, +} + +impl Display for AliasStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "AliasStmt {}: {}", self.span, self.kind) + } +} + +#[derive(Clone, Debug)] +pub struct ExprStmt { + pub span: Span, + pub expr: Box, +} + +impl Display for ExprStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "ExprStmt {}: {}", self.span, 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 { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "DiscreteSet {}:", self.span)?; + indent = set_indentation(indent, 1); + for value in &self.values { + write!(indent, "\n{value}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct RangeDefinition { + pub span: Span, + pub start: Option, + pub end: Option, + pub step: Option, +} + +#[derive(Clone, Debug)] +pub struct QuantumGateModifier { + pub span: Span, + pub qubit: Box, +} + +impl Display for QuantumGateModifier { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "QuantumGateModifier {}: {}", self.span, self.qubit) + } +} + +#[derive(Clone, Debug)] +pub struct QuantumMeasurement { + pub span: Span, + pub qubit: Box, +} + +impl Display for QuantumMeasurement { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "QuantumMeasurement {}: {}", self.span, self.qubit) + } +} + +#[derive(Clone, Debug)] +pub struct ClassicalArgument { + pub span: Span, + pub r#type: ClassicalType, + 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.r#type, self.name, access + ) + } else { + write!( + f, + "ClassicalArgument {}: {}, {}", + self.span, self.r#type, self.name + ) + } + } +} + +#[derive(Clone, Debug)] +pub struct ExternArgument { + pub span: Span, + pub r#type: ClassicalType, + pub access: Option, +} + +impl Display for ExternArgument { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(access) = &self.access { + write!( + f, + "ExternArgument {}: {}, {}", + self.span, self.r#type, access + ) + } else { + write!(f, "ExternArgument {}: {}", self.span, self.r#type) + } + } +} + +#[derive(Clone, Debug)] +pub struct ClassicalType { + pub span: Span, + pub kind: ClassicalTypeKind, +} + +impl Display for ClassicalType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "ClassicalType {}: {}", self.span, self.kind) + } +} + +#[derive(Clone, Debug)] +pub enum ClassicalTypeKind { + Int(IntType), + UInt(UIntType), + Float(FloatType), + Complex(ComplexType), + Angle(AngleType), + Bit(BitType), + BoolType, + Array(ArrayType), + ArrayReference(ArrayReferenceType), + Duration, + Stretch, +} + +impl Display for ClassicalTypeKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + ClassicalTypeKind::Int(int) => write!(f, "ClassicalTypeKind {int}"), + ClassicalTypeKind::UInt(uint) => write!(f, "ClassicalTypeKind {uint}"), + ClassicalTypeKind::Float(float) => write!(f, "ClassicalTypeKind {float}"), + ClassicalTypeKind::Complex(complex) => write!(f, "ClassicalTypeKind {complex}"), + ClassicalTypeKind::Angle(angle) => write!(f, "ClassicalTypeKind {angle}"), + ClassicalTypeKind::Bit(bit) => write!(f, "ClassicalTypeKind {bit}"), + ClassicalTypeKind::BoolType => write!(f, "ClassicalTypeKind BoolType"), + ClassicalTypeKind::Array(array) => write!(f, "ClassicalTypeKind {array}"), + ClassicalTypeKind::ArrayReference(array) => write!(f, "ClassicalTypeKind {array}"), + ClassicalTypeKind::Duration => write!(f, "ClassicalTypeKind Duration"), + ClassicalTypeKind::Stretch => write!(f, "ClassicalTypeKind Stretch"), + } + } +} + +#[derive(Clone, Debug)] +pub enum ArrayBaseTypeKind { + Int(IntType), + UInt(UIntType), + Float(FloatType), + Complex(ComplexType), + Angle(AngleType), + Bit(BitType), + BoolType, +} + +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::Bit(bit) => write!(f, "ArrayBaseTypeKind {bit}"), + ArrayBaseTypeKind::BoolType => write!(f, "ArrayBaseTypeKind BoolType"), + } + } +} + +#[derive(Clone, Debug)] +pub struct IntType { + pub size: Option, + pub span: Span, +} + +impl Display for IntType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(size) = &self.size { + write!(f, "IntType {}: {}", self.span, size) + } else { + write!(f, "IntType") + } + } +} + +#[derive(Clone, Debug)] +pub struct UIntType { + pub size: Option, + pub span: Span, +} + +impl Display for UIntType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(size) = &self.size { + write!(f, "UIntType {}: {}", self.span, size) + } else { + write!(f, "UIntType") + } + } +} + +#[derive(Clone, Debug)] +pub struct FloatType { + pub size: Option, + pub span: Span, +} + +impl Display for FloatType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(size) = &self.size { + write!(f, "FloatType {}: {}", self.span, size) + } else { + write!(f, "FloatType") + } + } +} + +#[derive(Clone, Debug)] +pub struct ComplexType { + pub base_size: Option, + pub span: Span, +} + +impl Display for ComplexType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(size) = &self.base_size { + write!(f, "ComplexType {}: {}", self.span, size) + } else { + write!(f, "ComplexType") + } + } +} + +#[derive(Clone, Debug)] +pub struct AngleType { + pub size: Option, + pub span: Span, +} + +impl Display for AngleType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(size) = &self.size { + write!(f, "AngleType {}: {}", self.span, size) + } else { + write!(f, "AngleType") + } + } +} + +#[derive(Clone, Debug)] +pub struct BitType { + pub size: Option, + pub span: Span, +} + +impl Display for BitType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(size) = &self.size { + write!(f, "BitType {}: {}", self.span, size) + } else { + write!(f, "BitType") + } + } +} + +#[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 { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "ArrayType {}: {}", self.span, self.base_type)?; + for dimension in &self.dimensions { + write!(indent, "\n{dimension}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct ArrayReferenceType { + pub span: Span, + pub base_type: ArrayBaseTypeKind, + pub dimensions: List, +} + +impl Display for ArrayReferenceType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!( + indent, + "ArrayReferenceType {}: {}", + self.span, self.base_type + )?; + for dimension in &self.dimensions { + write!(indent, "\n{dimension}")?; + } + Ok(()) + } +} + +#[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 { + write!(f, "QuantumArgument {}: {:?}", self.span, self.expr) + } +} + +#[derive(Clone, Debug)] +pub struct Pragma { + pub span: Span, + pub name: Box, + pub value: Option>, +} + +impl Display for Pragma { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(value) = &self.value { + write!(f, "Pragma {}: {}, {}", self.span, self.name, value) + } else { + write!(f, "Pragma {}: {}", self.span, self.name) + } + } +} + +#[derive(Clone, Debug)] +pub struct CompoundStmt { + pub span: Span, + pub statements: List, +} + +impl Display for CompoundStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "CompoundStmt {}:", self.span)?; + for stmt in &self.statements { + write!(indent, "\n{stmt}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct IncludeStmt { + pub span: Span, + pub filename: String, +} + +impl Display for IncludeStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "IncludeStmt {}: {}", self.span, self.filename) + } +} + +#[derive(Clone, Debug)] +pub struct QubitDeclaration { + pub span: Span, + pub qubit: Identifier, + pub size: Option, +} + +impl Display for QubitDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(size) = &self.size { + write!( + f, + "QubitDeclaration {}: {}, {}", + self.span, self.qubit, size + ) + } else { + write!(f, "QubitDeclaration {}: {}", self.span, self.qubit) + } + } +} + +#[derive(Clone, Debug)] +pub struct QuantumGateDefinition { + pub span: Span, + pub name: Identifier, + pub arguments: Vec, + pub qubits: Vec, + pub body: Vec, +} + +impl Display for QuantumGateDefinition { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "QuantumGateDefinition {}: {}", self.span, self.name)?; + for arg in &self.arguments { + write!(indent, "\n{arg}")?; + } + for qubit in &self.qubits { + write!(indent, "\n{qubit}")?; + } + for stmt in &self.body { + write!(indent, "\n{stmt}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct ExternDecl { + pub span: Span, + pub name: Identifier, + pub arguments: List, + pub return_type: Option, +} + +impl Display for ExternDecl { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "ExternDecl {}: {}", self.span, self.name)?; + for arg in &self.arguments { + write!(indent, "\n{arg}")?; + } + if let Some(return_type) = &self.return_type { + write!(indent, "\n{return_type}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct QuantumStmt { + pub span: Span, + pub kind: QuantumStmtKind, +} + +impl Display for QuantumStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "QuantumStmt {}: {}", self.span, self.kind) + } +} + +#[derive(Clone, Debug)] +pub enum QuantumStmtKind { + Gate(QuantumGate), + Phase(QuantumPhase), + Barrier(List), + Reset(List>), + DelayInstruction(DelayInstruction), + Box(BoxStmt), +} + +impl Display for QuantumStmtKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + QuantumStmtKind::Gate(gate) => write!(f, "QuantumStmtKind {gate}"), + QuantumStmtKind::Phase(phase) => write!(f, "QuantumStmtKind {phase}"), + QuantumStmtKind::Barrier(barrier) => write!(f, "QuantumStmtKind {barrier:?}"), + QuantumStmtKind::Reset(reset) => write!(f, "QuantumStmtKind {reset:?}"), + QuantumStmtKind::DelayInstruction(delay) => write!(f, "QuantumStmtKind {delay:?}"), + QuantumStmtKind::Box(box_stmt) => write!(f, "QuantumStmtKind {box_stmt:?}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct QuantumGate { + pub span: Span, + pub modifiers: List, + pub name: Identifier, + pub args: List, + pub qubits: List>, + pub duration: Option, +} + +impl Display for QuantumGate { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "QuantumGate {}: {}", self.span, self.name)?; + for arg in &self.args { + write!(indent, "\n{arg}")?; + } + for qubit in &self.qubits { + write!(indent, "\n{qubit}")?; + } + if let Some(duration) = &self.duration { + write!(indent, "\n{duration}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct QuantumPhase { + pub span: Span, + pub modifiers: List, + pub arg: ExprStmt, + pub qubits: List>, +} + +impl Display for QuantumPhase { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "QuantumPhase {}: {}", self.span, self.arg)?; + for qubit in &self.qubits { + write!(indent, "\n{qubit}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct DelayInstruction { + span: Span, + duration: ExprStmt, + qubits: List>, +} + +impl Display for DelayInstruction { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "DelayInstruction {}: {}", self.span, self.duration)?; + for qubit in &self.qubits { + write!(indent, "\n{qubit}")?; + } + Ok(()) + } +} + +#[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 { + let mut indent = set_indentation(indented(f), 0); + if let Some(duration) = &self.duration { + write!(indent, "BoxStmt {}: {}", self.span, duration)?; + } else { + write!(indent, "BoxStmt {}: ", self.span)?; + } + for stmt in &self.body { + write!(indent, "\n{stmt}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct MeasureStmt { + pub span: Span, + pub measure: QuantumMeasurement, + pub target: Option>, +} + +impl Display for MeasureStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(target) = &self.target { + write!(f, "MeasureStmt {}: {}, {}", self.span, self.measure, target) + } else { + write!(f, "MeasureStmt {}: {}", self.span, self.measure) + } + } +} + +#[derive(Clone, Debug)] +pub struct ClassicalDeclarationStmt { + pub span: Span, + pub r#type: ClassicalType, + pub identifier: Identifier, + pub init_expr: Option>, +} + +impl Display for ClassicalDeclarationStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(init_expr) = &self.init_expr { + write!( + f, + "ClassicalDeclarationStmt {}: {}, {}, {}", + self.span, self.r#type, self.identifier, init_expr + ) + } else { + write!( + f, + "ClassicalDeclarationStmt {}: {}, {}", + self.span, self.r#type, self.identifier + ) + } + } +} + +#[derive(Clone, Debug)] +pub enum ValueExpression { + Expr(ExprStmt), + Measurement(QuantumMeasurement), +} + +impl Display for ValueExpression { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + ValueExpression::Expr(expr) => write!(f, "ValueExpression {expr}"), + ValueExpression::Measurement(measure) => write!(f, "ValueExpression {measure}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct IODeclaration { + pub span: Span, + pub io_identifier: IOKeyword, + pub r#type: ClassicalType, + pub identifier: Identifier, +} + +impl Display for IODeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "IODeclaration {}: {}, {}, {}", + self.span, self.io_identifier, self.r#type, self.identifier + ) + } +} + +#[derive(Clone, Debug)] +pub struct ConstantDeclaration { + span: Span, + r#type: ClassicalType, + identifier: Identifier, + init_expr: ExprStmt, +} + +impl Display for ConstantDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "ConstantDeclaration {}: {}, {}, {}", + self.span, self.r#type, self.identifier, 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 { + write!( + f, + "CalibrationGrammarDeclaration {}: {}", + self.span, self.name + ) + } +} + +#[derive(Clone, Debug)] +pub struct CalibrationStmt { + span: Span, + body: String, +} + +impl Display for CalibrationStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "CalibrationStmt {}: {}", self.span, self.body) + } +} + +#[derive(Clone, Debug)] +pub struct CalibrationDefinition { + span: Span, + name: Identifier, + args: List, + qubits: List, + return_type: Option, + body: String, +} + +impl Display for CalibrationDefinition { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "CalibrationDefinition {}: {}", self.span, self.name)?; + for arg in &self.args { + write!(indent, "\n{arg}")?; + } + for qubit in &self.qubits { + write!(indent, "\n{qubit}")?; + } + if let Some(return_type) = &self.return_type { + write!(indent, "\n{return_type}")?; + } + write!(indent, "\n{}", self.body) + } +} + +#[derive(Clone, Debug)] +pub enum CalibrationArgument { + Classical(ClassicalArgument), + Expr(ExprStmt), +} + +impl Display for CalibrationArgument { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + CalibrationArgument::Classical(arg) => write!(f, "CalibrationArgument {arg}"), + CalibrationArgument::Expr(expr) => write!(f, "CalibrationArgument {expr}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct DefStmt { + span: Span, + name: Identifier, + args: List>, + body: List>, + return_type: Option, +} + +impl Display for DefStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "DefStmt {}: {}", self.span, self.name)?; + for arg in &self.args { + write!(indent, "\n{arg}")?; + } + for stmt in &self.body { + write!(indent, "\n{stmt}")?; + } + if let Some(return_type) = &self.return_type { + write!(indent, "\n{return_type}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub enum Operand { + Classical(ClassicalArgument), + Quantum(QuantumArgument), +} + +impl Display for Operand { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Operand::Classical(arg) => write!(f, "Operand {arg}"), + Operand::Quantum(arg) => write!(f, "Operand {arg}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct ReturnStmt { + span: Span, + expr: Option>, +} + +impl Display for ReturnStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(expr) = &self.expr { + write!(f, "ReturnStmt {}: {}", self.span, expr) + } else { + write!(f, "ReturnStmt {}: ", self.span) + } + } +} + +#[derive(Clone, Debug)] +pub struct BranchingStmt { + span: Span, + condition: ExprStmt, + if_block: List, + else_block: List, +} + +impl Display for BranchingStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "BranchingStmt {}: {}", self.span, self.condition)?; + for stmt in &self.if_block { + write!(indent, "\n{stmt}")?; + } + for stmt in &self.else_block { + write!(indent, "\n{stmt}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct WhileLoop { + span: Span, + while_condition: ExprStmt, + block: List, +} + +impl Display for WhileLoop { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "WhileLoop {}: {}", self.span, self.while_condition)?; + for stmt in &self.block { + write!(indent, "\n{stmt}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct ForStmt { + span: Span, + r#type: ClassicalType, + identifier: Identifier, + set_declaration: Box, + block: List, +} + +impl Display for ForStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!( + indent, + "ForStmt {}: {}, {}, {}", + self.span, self.r#type, self.identifier, self.set_declaration + )?; + for stmt in &self.block { + write!(indent, "\n{stmt}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub enum EnumerableSet { + DiscreteSet(DiscreteSet), + RangeDefinition(RangeDefinition), + Expr(ExprStmt), +} + +impl Display for EnumerableSet { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + EnumerableSet::DiscreteSet(set) => write!(f, "{set}"), + EnumerableSet::RangeDefinition(range) => { + let indent = set_indentation(indented(f), 0); + display_range(indent, range) + } + EnumerableSet::Expr(expr) => write!(f, "{expr}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct SwitchStmt { + pub span: Span, + pub target: ExprStmt, + pub cases: List<(List, CompoundStmt)>, + /// 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 { + todo!("SwitchStmt display"); + } +} + +#[derive(Clone, Debug)] +pub struct ClassicalAssignment { + pub span: Span, + pub lvalue: Identifier, + pub op: AssignmentOp, +} + +impl Display for ClassicalAssignment { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "ClassicalAssignment {}: {}, {}", + self.span, self.lvalue, self.op + ) + } +} + +#[derive(Clone, Debug, Default)] +pub enum ExprKind { + /// An expression with invalid syntax that can't be parsed. + #[default] + Err, + Ident(Ident), + UnaryExpr(UnaryExpr), + BinaryExpr(BinaryExpr), + Literal(Lit), + FunctionCall(FunctionCall), + Cast(Cast), + Concatenation(Concatenation), + IndexExpr(IndexExpr), +} + +impl Display for ExprKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let indent = set_indentation(indented(f), 0); + match self { + ExprKind::Err => write!(f, "Err"), + ExprKind::Ident(id) => write!(f, "Ident {id}"), + ExprKind::UnaryExpr(expr) => write!(f, "UnaryExpr {expr}"), + ExprKind::BinaryExpr(expr) => display_bin_op(indent, expr), + ExprKind::Literal(lit) => write!(f, "Literal {lit}"), + ExprKind::FunctionCall(call) => write!(f, "FunctionCall {call}"), + ExprKind::Cast(cast) => write!(f, "Cast {cast}"), + ExprKind::Concatenation(concat) => write!(f, "Concatenation {concat}"), + ExprKind::IndexExpr(index) => write!(f, "IndexExpr {index}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct UnaryExpr { + pub span: Span, + pub op: UnaryOp, + pub expr: Box, +} + +impl Display for UnaryExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let indent = set_indentation(indented(f), 0); + display_un_op(indent, self.op, &self.expr) + } +} + +#[derive(Clone, Debug)] +pub struct BinaryExpr { + pub span: Span, + pub op: BinaryOp, + pub lhs: ExprStmt, + pub rhs: ExprStmt, +} + +impl Display for BinaryExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let indent = set_indentation(indented(f), 0); + display_bin_op(indent, self) + } +} + +#[derive(Clone, Debug)] +pub struct FunctionCall { + pub span: Span, + pub name: Identifier, + pub args: List, +} + +impl Display for FunctionCall { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "FunctionCall {}: {}", self.span, self.name)?; + indent = set_indentation(indent, 1); + for arg in &self.args { + write!(indent, "\n{arg}")?; + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct Cast { + pub span: Span, + pub r#type: ClassicalType, + pub arg: ExprStmt, +} + +impl Display for Cast { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Cast {}: {}, {}", self.span, self.r#type, self.arg) + } +} + +#[derive(Clone, Debug)] +pub struct IndexExpr { + pub span: Span, + pub collection: ExprStmt, + pub index: IndexElement, +} + +impl Display for IndexExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "IndexExpr {}: {}, {}", + self.span, self.collection, self.index + ) + } +} + +#[derive(Clone, Copy, Debug)] +pub enum UnaryOp { + NegB, + NegL, + NegN, +} + +impl Display for UnaryOp { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + UnaryOp::NegB => write!(f, "NegB"), + UnaryOp::NegL => write!(f, "NegL"), + UnaryOp::NegN => write!(f, "NegN"), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum BinaryOp { + /// `>` + Gt, + /// `<` + Lt, + /// `>=` + Gte, + /// `<=` + Lte, + /// `==` + Eq, + /// `!=` + Neq, + /// `&&` + AndL, + /// `||` + OrL, + /// `|` + OrB, + /// `^` + XorB, + /// `&` + AndB, + /// `<<` + ShL, + /// `>>` + ShR, + /// `+` + Add, + /// `-` + Sub, + /// `*` + Mul, + /// `/` + Div, + /// `%` + Mod, + /// `**` + Exp, +} + +#[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.span, self.kind) + } +} + +#[derive(Clone, Debug)] +pub enum LiteralKind { + Array(List), + Bitstring { value: usize, width: u32 }, + Boolean(bool), + Duration { value: f64, unit: TimeUnit }, + Float(f64), + Imaginary(f64), + Integer(i64), + String(Rc), +} + +impl Display for LiteralKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + LiteralKind::Array(exprs) => { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "Array:")?; + indent = set_indentation(indent, 1); + for expr in exprs { + write!(indent, "\n{expr}")?; + } + Ok(()) + } + LiteralKind::Bitstring { value, width } => { + write!(f, "Bitstring: {value} (width {width})") + } + LiteralKind::Boolean(b) => write!(f, "Boolean: {b}"), + LiteralKind::Duration { value, unit } => { + write!(f, "Duration: {value} {unit}") + } + LiteralKind::Float(value) => write!(f, "Float: {value}"), + LiteralKind::Imaginary(value) => write!(f, "Imaginary: {value} im"), + LiteralKind::Integer(i) => write!(f, "Integer: {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 struct Concatenation { + lhs: ExprStmt, + rhs: ExprStmt, +} + +impl Display for Concatenation { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "Concatenation:")?; + indent = set_indentation(indent, 1); + write!(indent, "\n{}", self.lhs)?; + write!(indent, "\n{}", self.rhs)?; + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub enum IndexElement { + DiscreteSet(DiscreteSet), + IndexSet(List), +} + +impl Display for IndexElement { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + IndexElement::DiscreteSet(set) => write!(f, "IndexElement {set}"), + IndexElement::IndexSet(items) => { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "IndexElement:")?; + indent = set_indentation(indent, 1); + for item in items { + write!(indent, "\n{item}")?; + } + Ok(()) + } + } + } +} + +#[derive(Clone, Debug)] +pub enum IndexSetItem { + RangeDefinition(RangeDefinition), + Expr(ExprStmt), +} + +impl Display for IndexSetItem { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let indent = set_indentation(indented(f), 0); + match self { + IndexSetItem::RangeDefinition(range) => display_range(indent, range), + IndexSetItem::Expr(expr) => write!(f, "IndexSetItem {expr}"), + } + } +} + +#[derive(Clone, Debug)] +pub enum AssignmentOp { + BinaryOp(BinaryOp), + /// `OpenQASM3` has the `~=` assignment operator. + /// This enum variant is meant to capture that. + UnaryOp(UnaryOp), + Assign, +} + +impl Display for AssignmentOp { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + AssignmentOp::BinaryOp(op) => write!(f, "AssignmentOp ({op:?})"), + AssignmentOp::UnaryOp(op) => write!(f, "AssignmentOp ({op:?})"), + AssignmentOp::Assign => write!(f, "AssignmentOp (Assign)"), + } + } +} + +#[derive(Clone, Debug)] +pub enum GateModifierName { + Inv, + Pow, + Ctrl, + NegCtrl, +} + +impl Display for GateModifierName { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + GateModifierName::Inv => write!(f, "inv"), + GateModifierName::Pow => write!(f, "pow"), + GateModifierName::Ctrl => write!(f, "ctrl"), + GateModifierName::NegCtrl => write!(f, "negctrl"), + } + } +} + +#[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, 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"), + } + } +} + +#[derive(Clone, Debug)] +pub struct BreakStmt { + pub span: Span, +} + +impl Display for BreakStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Break {}", 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, "Continue {}", self.span) + } +} + +fn display_assign(mut indent: Indented, lhs: &Expr, rhs: &Expr) -> fmt::Result { + write!(indent, "Assign:")?; + indent = set_indentation(indent, 1); + write!(indent, "\n{lhs}")?; + write!(indent, "\n{rhs}")?; + Ok(()) +} + +fn display_assign_op( + mut indent: Indented, + op: BinaryOp, + lhs: &Expr, + rhs: &Expr, +) -> fmt::Result { + write!(indent, "AssignOp ({op:?}):")?; + indent = set_indentation(indent, 1); + write!(indent, "\n{lhs}")?; + write!(indent, "\n{rhs}")?; + Ok(()) +} + +fn display_bin_op(mut indent: Indented, expr: &BinaryExpr) -> fmt::Result { + write!(indent, "BinOp ({:?}):", expr.op)?; + indent = set_indentation(indent, 1); + write!(indent, "\n{}", expr.lhs)?; + write!(indent, "\n{}", expr.rhs)?; + Ok(()) +} + +fn display_un_op(mut indent: Indented, op: UnaryOp, expr: &Expr) -> fmt::Result { + write!(indent, "UnOp ({op}):")?; + indent = set_indentation(indent, 1); + write!(indent, "\n{expr}")?; + Ok(()) +} + +fn display_while(mut indent: Indented, cond: &Expr, block: &Block) -> fmt::Result { + write!(indent, "While:")?; + indent = set_indentation(indent, 1); + write!(indent, "\n{cond}")?; + write!(indent, "\n{block}")?; + Ok(()) +} + +fn display_range(mut indent: Indented, range: &RangeDefinition) -> fmt::Result { + write!(indent, "Range: {}", range.span)?; + indent = set_indentation(indent, 1); + match &range.start { + Some(e) => write!(indent, "\n{e}")?, + None => write!(indent, "\n")?, + } + match &range.step { + Some(e) => write!(indent, "\n{e}")?, + None => write!(indent, "\n")?, + } + match &range.end { + Some(e) => write!(indent, "\n{e}")?, + None => write!(indent, "\n")?, + } + Ok(()) +} diff --git a/compiler/qsc_qasm3/src/keyword.rs b/compiler/qsc_qasm3/src/keyword.rs index dd30c8bd02..86f520b5c3 100644 --- a/compiler/qsc_qasm3/src/keyword.rs +++ b/compiler/qsc_qasm3/src/keyword.rs @@ -9,54 +9,98 @@ use std::{ #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Sequence)] pub enum Keyword { + Array, + Barrier, Box, Break, Cal, Case, + Const, Continue, + CReg, + Ctrl, Def, + DefCal, + DefCalGrammar, Default, - Defcalgrammar, + 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::Array => "array", + 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::Defcalgrammar => "defcalgrammar", + 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::OpenQASM => "openqasm", + 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", } } @@ -76,27 +120,49 @@ impl FromStr for Keyword { // frequency in Q# so that fewer comparisons are needed on average. fn from_str(s: &str) -> Result { match s { + "array" => Ok(Self::Array), + "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), - "defcalgrammar" => Ok(Self::Defcalgrammar), + "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), - "openqasm" => Ok(Self::OpenQASM), + "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 index 34683c5259..16257a1ea0 100644 --- a/compiler/qsc_qasm3/src/lex.rs +++ b/compiler/qsc_qasm3/src/lex.rs @@ -6,7 +6,7 @@ pub mod cooked; pub mod raw; use enum_iterator::Sequence; -pub(super) use cooked::{Error, Lexer, Token, TokenKind}; +pub(super) use cooked::{ClosedBinOp, Error, Lexer, Token, TokenKind}; /// A delimiter token. #[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] diff --git a/compiler/qsc_qasm3/src/lex/cooked.rs b/compiler/qsc_qasm3/src/lex/cooked.rs index faebc1bb28..afc1bbb62d 100644 --- a/compiler/qsc_qasm3/src/lex/cooked.rs +++ b/compiler/qsc_qasm3/src/lex/cooked.rs @@ -96,7 +96,6 @@ pub enum TokenKind { Delay, Reset, Measure, - Barrier, Literal(Literal), @@ -119,6 +118,7 @@ pub enum TokenKind { PlusPlus, /// `->` Arrow, + At, // Operators, ClosedBinOp(ClosedBinOp), @@ -133,6 +133,8 @@ pub enum TokenKind { Identifier, HardwareQubit, + /// End of file. + Eof, } impl Display for TokenKind { @@ -151,7 +153,6 @@ impl Display for TokenKind { TokenKind::Delay => write!(f, "delay"), TokenKind::Reset => write!(f, "reset"), TokenKind::Measure => write!(f, "measure"), - TokenKind::Barrier => write!(f, "barrier"), TokenKind::Literal(literal) => write!(f, "literal `{literal}`"), TokenKind::Open(Delim::Brace) => write!(f, "`{{`"), TokenKind::Open(Delim::Bracket) => write!(f, "`[`"), @@ -165,6 +166,7 @@ impl Display for TokenKind { 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}`"), @@ -173,6 +175,7 @@ impl Display for TokenKind { TokenKind::Tilde => write!(f, "`~`"), TokenKind::Identifier => write!(f, "identifier"), TokenKind::HardwareQubit => write!(f, "hardware bit"), + TokenKind::Eof => f.write_str("EOF"), } } } @@ -562,15 +565,7 @@ impl<'a> Lexer<'a> { Ok(self.closed_bin_op(ClosedBinOp::Amp)) } } - Single::At => { - let complete = TokenKind::Annotation; - self.expect(raw::TokenKind::Ident, complete)?; - self.kleen_star( - &[raw::TokenKind::Single(Single::Dot), raw::TokenKind::Ident], - complete, - )?; - Ok(complete) - } + Single::At => Ok(TokenKind::At), Single::Bang => { if self.next_if_eq_single(Single::Eq) { Ok(TokenKind::ComparisonOp(ComparisonOp::BangEq)) @@ -664,7 +659,6 @@ impl<'a> Lexer<'a> { "delay" => TokenKind::Delay, "reset" => TokenKind::Reset, "measure" => TokenKind::Measure, - "barrier" => TokenKind::Barrier, "false" | "true" => TokenKind::Literal(Literal::Boolean), ident => { if let Ok(keyword) = ident.parse::() { diff --git a/compiler/qsc_qasm3/src/lex/cooked/tests.rs b/compiler/qsc_qasm3/src/lex/cooked/tests.rs index 7002c957b7..66d0f0c9c1 100644 --- a/compiler/qsc_qasm3/src/lex/cooked/tests.rs +++ b/compiler/qsc_qasm3/src/lex/cooked/tests.rs @@ -38,7 +38,6 @@ fn op_string(kind: TokenKind) -> Option { TokenKind::Delay => Some("delay".to_string()), TokenKind::Reset => Some("reset".to_string()), TokenKind::Measure => Some("measure".to_string()), - TokenKind::Barrier => Some("barrier".to_string()), TokenKind::Semicolon => Some(";".to_string()), TokenKind::Arrow => Some("->".to_string()), TokenKind::ClosedBinOp(op) => Some(op.to_string()), @@ -49,10 +48,13 @@ fn op_string(kind: TokenKind) -> Option { TokenKind::ComparisonOp(op) => Some(op.to_string()), TokenKind::Identifier => Some("foo".to_string()), TokenKind::HardwareQubit => Some("$1".to_string()), + TokenKind::At => Some("@".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 { diff --git a/compiler/qsc_qasm3/src/lib.rs b/compiler/qsc_qasm3/src/lib.rs index 9c1f806371..624bfbaae0 100644 --- a/compiler/qsc_qasm3/src/lib.rs +++ b/compiler/qsc_qasm3/src/lib.rs @@ -2,6 +2,7 @@ // Licensed under the MIT License. mod angle; +mod ast; mod ast_builder; mod compile; pub use compile::qasm_to_program; @@ -11,6 +12,7 @@ mod lex; mod oqasm_helpers; mod oqasm_types; pub mod parse; +pub mod parser; mod runtime; mod symbols; mod types; diff --git a/compiler/qsc_qasm3/src/parser.rs b/compiler/qsc_qasm3/src/parser.rs new file mode 100644 index 0000000000..9c74e68e40 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser.rs @@ -0,0 +1,265 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::ast::{Program, StmtKind}; +use crate::io::SourceResolver; +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; +mod expr; +mod prgm; +mod prim; +mod scan; +mod stmt; + +pub struct QasmParseResult { + pub source: QasmSource, + pub source_map: SourceMap, +} + +impl QasmParseResult { + #[must_use] + pub fn new(source: QasmSource) -> QasmParseResult { + let source_map = create_source_map(&source); + QasmParseResult { source, source_map } + } + + #[must_use] + pub fn has_errors(&self) -> bool { + self.source.has_errors() + } + + pub fn all_errors(&self) -> Vec> { + let mut self_errors = self.errors(); + let include_errors = self + .source + .includes() + .iter() + .flat_map(QasmSource::all_errors) + .map(|e| self.map_error(e)); + + self_errors.extend(include_errors); + self_errors + } + + #[must_use] + pub fn errors(&self) -> Vec> { + self.source + .errors() + .iter() + .map(|e| self.map_error(e.clone())) + .collect() + } + + fn map_error( + &self, + error: crate::parser::error::Error, + ) -> WithSource { + let path = self.source.path().display().to_string(); + let source = self.source_map.find_by_name(&path); + let offset = source.map_or(0, |source| source.offset); + + let offset_error = error.with_offset(offset); + + WithSource::from_map(&self.source_map, offset_error) + } +} + +/// Parse a QASM file and return the parse result. +/// This function will resolve includes using the provided resolver. +/// If an include file cannot be resolved, an error will be returned. +/// If a file is included recursively, a stack overflow occurs. +pub fn parse_source(source: S, path: P, resolver: &R) -> miette::Result +where + S: AsRef, + P: AsRef, + R: SourceResolver, +{ + let res = parse_qasm_source(source, path, resolver)?; + Ok(QasmParseResult::new(res)) +} + +/// Creates a Q# source map from a QASM parse output. The `QasmSource` +/// has all of the recursive includes resolved with their own source +/// and parse results. +fn create_source_map(source: &QasmSource) -> SourceMap { + let mut files: Vec<(Arc, Arc)> = Vec::new(); + files.push(( + Arc::from(source.path().to_string_lossy().to_string()), + Arc::from(source.source()), + )); + // Collect all source files from the includes, this + // begins the recursive process of collecting all source files. + for include in source.includes() { + collect_source_files(include, &mut files); + } + // Map the main source file to the entry point expression + // This may be incorrect, but it's the best we can do for now. + SourceMap::new(files, Some(Arc::from(source.source()))) +} + +/// Recursively collect all source files from the includes +fn collect_source_files(source: &QasmSource, files: &mut Vec<(Arc, Arc)>) { + files.push(( + Arc::from(source.path().to_string_lossy().to_string()), + Arc::from(source.source()), + )); + for include in source.includes() { + collect_source_files(include, files); + } +} + +/// Represents a QASM source file that has been parsed. +#[derive(Clone, Debug)] +pub struct QasmSource { + /// The path to the source file. This is used for error reporting. + /// This path is just a name, it does not have to exist on disk. + path: PathBuf, + /// The source code of the file. + source: Arc, + /// The parsed AST of the source file or any parse errors. + 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 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) -> miette::Result +where + P: AsRef, + R: SourceResolver, +{ + let (path, source) = resolver.resolve(&path)?; + parse_qasm_source(source, path, resolver) +} + +fn parse_qasm_source(source: S, path: P, resolver: &R) -> miette::Result +where + S: AsRef, + P: AsRef, + R: SourceResolver, +{ + let (program, errors, includes) = parse_source_and_includes(source.as_ref(), resolver)?; + Ok(QasmSource::new(source, path, program, errors, includes)) +} + +fn parse_source_and_includes, R>( + source: P, + resolver: &R, +) -> miette::Result<(Program, Vec, Vec)> +where + R: SourceResolver, +{ + let (program, errors) = parse(source.as_ref())?; + let included = parse_includes(&program, resolver)?; + Ok((program, errors, included)) +} + +fn parse_includes(program: &crate::ast::Program, resolver: &R) -> miette::Result> +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); + } + } + + Ok(includes) +} + +pub(crate) type Result = std::result::Result; + +pub(crate) trait Parser: FnMut(&mut ParserContext) -> Result {} + +impl Result> Parser for F {} + +pub fn parse(input: &str) -> Result<(Program, Vec)> { + let mut scanner = ParserContext::new(input); + let program = prgm::parse(&mut scanner)?; + Ok((program, scanner.into_errors())) +} diff --git a/compiler/qsc_qasm3/src/parser/completion.rs b/compiler/qsc_qasm3/src/parser/completion.rs new file mode 100644 index 0000000000..17b37d8507 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/completion.rs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// while we work through the conversion, allow dead code to avoid warnings +#![allow(dead_code)] + +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..396380748a --- /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 | Include | OpenQASM | Pragma, + ) + "]], + ); +} + +#[test] +fn end_of_version() { + check_valid_words( + "OPENQASM 3;|", + &expect![[r" + WordKinds( + Annotation | Include | Pragma, + ) + "]], + ); +} 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..44b441338a --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/completion/word_kinds.rs @@ -0,0 +1,179 @@ +// 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 Array = keyword_bit(Keyword::Array); + 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), + WordKinds::PrimitiveClass => Some(NameKind::PrimitiveClass), + _ => 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 primitive class, like Eq, Exp, or Add. + PrimitiveClass, +} + +/// 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..34ca512e21 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/error.rs @@ -0,0 +1,137 @@ +// 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("expected statement after annotation")] + #[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("expected an item or closing brace, found {0}")] + #[diagnostic(code("Qasm3.Parse.ExpectedItem"))] + ExpectedItem(TokenKind, #[label] Span), +} + +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::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::ExpectedItem(token, span) => Self::ExpectedItem(token, span + offset), + } + } +} diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs new file mode 100644 index 0000000000..e1b7cfd57c --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -0,0 +1,292 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// while we work through the conversion, allow dead code to avoid warnings +#![allow(dead_code)] + +//! Expression parsing makes use of Pratt parsing (or “top-down operator-precedence parsing”) to handle +//! relative precedence of operators. + +use crate::{ + ast::{BinOp, Expr, Lit, LiteralKind, StmtKind, UnOp, Version}, + keyword::Keyword, + lex::{cooked::Literal, ClosedBinOp, Radix, Token, TokenKind}, + parser::{ + completion::WordKinds, + prim::{shorten, token}, + scan::ParserContext, + }, +}; + +use crate::parser::Result; + +use super::error::{Error, ErrorKind}; + +struct PrefixOp { + kind: UnOp, + precedence: u8, +} + +struct MixfixOp { + kind: OpKind, + precedence: u8, +} + +enum OpKind { + Postfix(UnOp), + Binary(BinOp, Assoc), + Assign, + AssignUpdate, + AssignBinary(BinOp), +} + +#[derive(Clone, Copy)] +enum OpName { + Token(TokenKind), + Keyword(Keyword), +} + +#[derive(Clone, Copy)] +enum OpContext { + Precedence(u8), + Stmt, +} + +#[derive(Clone, Copy)] +enum Assoc { + Left, + Right, +} + +const RANGE_PRECEDENCE: u8 = 1; + +pub(super) fn expr(s: &mut ParserContext) -> Result> { + Err(Error::new(ErrorKind::Rule( + "expression", + s.peek().kind, + s.peek().span, + ))) +} + +pub(super) fn expr_eof(s: &mut ParserContext) -> Result> { + let expr = expr(s)?; + token(s, TokenKind::Eof)?; + Ok(expr) +} + +/// Returns true if the expression kind is statement-final. When a statement-final expression occurs +/// at the top level of an expression statement, it indicates the end of the statement, and any +/// operators following it will not be parsed as part of the expression. Statement-final expressions +/// in a top level position also do not require a semicolon when they are followed by another +/// statement. +pub(super) fn is_stmt_final(kind: &StmtKind) -> bool { + matches!( + kind, + StmtKind::Block(_) + | StmtKind::Box(_) + | StmtKind::Cal(_) + | StmtKind::DefCal(_) + | StmtKind::Def(_) + | StmtKind::If(_) + | StmtKind::For(_) + | StmtKind::Switch(_) + | StmtKind::WhileLoop(_) + ) +} + +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()) + .ok_or(Error::new(ErrorKind::Lit("integer", token.span)))?; + Ok(Some(Lit { + kind: LiteralKind::Integer(value), + span: 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 lit = shorten(1, 1, lexeme); + Ok(Some(Lit { + kind: LiteralKind::String(lit.into()), + span: token.span, + })) + } + Literal::Bitstring => todo!("bitstring literal"), + Literal::Boolean => todo!("boolean literal"), + Literal::Imaginary => todo!("imaginary literal"), + Literal::Timing(_timing_literal_kind) => todo!("timing literal"), + }, + TokenKind::Keyword(Keyword::True) => Ok(Some(Lit { + kind: LiteralKind::Boolean(true), + span: token.span, + })), + TokenKind::Keyword(Keyword::False) => Ok(Some(Lit { + kind: LiteralKind::Boolean(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 prefix_op(name: OpName) -> Option { + match name { + OpName::Token(TokenKind::Bang) => Some(PrefixOp { + kind: UnOp::NotL, + precedence: 11, + }), + OpName::Token(TokenKind::Tilde) => Some(PrefixOp { + kind: UnOp::NotB, + precedence: 11, + }), + OpName::Token(TokenKind::ClosedBinOp(ClosedBinOp::Minus)) => Some(PrefixOp { + kind: UnOp::Neg, + precedence: 11, + }), + + _ => None, + } +} + +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, + } +} diff --git a/compiler/qsc_qasm3/src/parser/prgm.rs b/compiler/qsc_qasm3/src/parser/prgm.rs new file mode 100644 index 0000000000..5de4907612 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/prgm.rs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use super::{ + prim::{many, opt, recovering, recovering_semi, recovering_token, token}, + stmt, Result, +}; +use crate::{ + ast::{Program, Stmt, StmtKind, Version}, + lex::{Delim, TokenKind}, + parser::{completion::WordKinds, expr}, +}; + +use super::ParserContext; + +pub(super) fn parse(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let version = opt(s, parse_version)?; + let stmts = parse_top_level_nodes(s)?; + + Ok(Program { + span: s.span(lo), + version, + statements: stmts + .into_iter() + .map(Box::new) + .collect::>() + .into_boxed_slice(), + }) +} + +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..6ae8353a17 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/prim.rs @@ -0,0 +1,326 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// while we work through the conversion, allow dead code to avoid warnings +#![allow(dead_code)] + +#[cfg(test)] +pub(crate) mod tests; + +use super::{ + error::{Error, ErrorKind}, + scan::ParserContext, + Parser, Result, +}; +use crate::{ + ast::{Ident, IncompletePath, Path, PathKind}, + lex::TokenKind, + parser::completion::WordKinds, +}; + +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(Box::new(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..cacf228ebb --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/prim/tests.rs @@ -0,0 +1,265 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use super::{ident, opt, seq}; +use crate::{ + ast::PathKind, + keyword::Keyword, + lex::TokenKind, + 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] (Ident [0-3] "Foo")"#]]); +} + +#[test] +fn path_double() { + check( + path, + "Foo.Bar", + &expect![[r#" + Path [0-7]: + Ident [0-3] "Foo" + Ident [4-7] "Bar""#]], + ); +} + +#[test] +fn path_triple() { + check( + path, + "Foo.Bar.Baz", + &expect![[r#" + Path [0-11]: + Ident [0-3] "Foo" + Ident [4-7] "Bar" + Ident [8-11] "Baz""#]], + ); +} + +#[test] +fn path_trailing_dot() { + check( + path, + "Foo.Bar.", + &expect![[r#" + Err IncompletePath [0-8]: + 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]: + 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]: + Ident [0-3] "Foo" + Ident [4-7] "Bar""#]], + ); +} + +#[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]: + 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..775c20373f --- /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::Error; +use super::error::ErrorKind; + +#[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..d5011eeefb --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use qsc_data_structures::span::Span; + +use super::{ + completion::WordKinds, + error::{Error, ErrorKind}, + expr::{self}, + prim::{barrier, many, opt, recovering, recovering_semi, recovering_token}, + Result, +}; +use crate::{ + ast::{Annotation, Block, IncludeStmt, LiteralKind, PathKind, Pragma, Stmt, StmtKind}, + lex::{cooked::Literal, Delim, TokenKind}, +}; + +use super::{prim::token, ParserContext}; + +pub(super) fn parse(s: &mut ParserContext) -> Result> { + let lo = s.peek().span.lo; + if let Some(pragma) = opt(s, parse_pragma)? { + return Ok(Box::new(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() { + Box::new(StmtKind::Empty) + } else { + let err_item = default(s.span(lo)); + s.push_error(Error::new(ErrorKind::FloatingAnnotation(err_item.span))); + return Ok(err_item); + } + } else if let Some(v) = opt(s, parse_include)? { + Box::new(v) + } else { + return Err(Error::new(ErrorKind::Rule( + "statement", + s.peek().kind, + s.peek().span, + ))); + }; + + Ok(Box::new(Stmt { + span: s.span(lo), + annotations: attrs.into_boxed_slice(), + kind, + })) +} + +#[allow(clippy::vec_box)] +pub(super) fn parse_many(s: &mut ParserContext) -> Result>> { + many(s, |s| { + recovering(s, default, &[TokenKind::Semicolon], parse) + }) +} + +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(Box::new(Block { + span: s.span(lo), + stmts: stmts.into_boxed_slice(), + })) +} + +#[allow(clippy::unnecessary_box_returns)] +fn default(span: Span) -> Box { + Box::new(Stmt { + span, + annotations: Vec::new().into_boxed_slice(), + kind: Box::new(StmtKind::Err), + }) +} + +fn parse_annotation(s: &mut ParserContext) -> Result> { + let lo = s.peek().span.lo; + s.expect(WordKinds::Annotation); + token(s, TokenKind::At)?; + // parse name + // parse value + recovering_semi(s); + Ok(Box::new(Annotation { + span: s.span(lo), + name: Box::new(PathKind::default()), + value: None, + })) +} + +fn parse_include(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + s.expect(WordKinds::Include); + token(s, TokenKind::Keyword(crate::keyword::Keyword::Include))?; + let next = s.peek(); + + let v = expr::lit(s)?; + if let Some(v) = v { + if let LiteralKind::String(v) = v.kind { + let r = IncludeStmt { + span: s.span(lo), + filename: v.to_string(), + }; + token(s, TokenKind::Semicolon)?; + return Ok(StmtKind::Include(r)); + } + }; + Err(Error::new(ErrorKind::Rule( + "include statement", + TokenKind::Literal(Literal::String), + next.span, + ))) +} + +fn parse_pragma(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + s.expect(WordKinds::Pragma); + token(s, TokenKind::Keyword(crate::keyword::Keyword::Pragma))?; + // parse name + // parse value + + Ok(Pragma { + span: s.span(lo), + name: Box::new(PathKind::default()), + value: None, + }) +} diff --git a/compiler/qsc_qasm3/src/parser/tests.rs b/compiler/qsc_qasm3/src/parser/tests.rs new file mode 100644 index 0000000000..1f36a9bff9 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/tests.rs @@ -0,0 +1,172 @@ +// 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 source = resolver.resolve(path.as_ref()).map_err(|e| vec![e])?.1; + let res = crate::parser::parse_source(source, path, &resolver).map_err(|e| vec![e])?; + if res.source.has_errors() { + let errors = res + .errors() + .into_iter() + .map(|e| Report::new(e.clone())) + .collect(); + Err(errors) + } else { + Ok(res) + } +} + +pub(crate) fn parse(source: S) -> miette::Result> +where + S: AsRef, +{ + let resolver = InMemorySourceResolver::from_iter([("test".into(), source.as_ref().into())]); + let res = parse_source(source, "test", &resolver).map_err(|e| vec![e])?; + if res.source.has_errors() { + let errors = res + .errors() + .into_iter() + .map(|e| Report::new(e.clone())) + .collect(); + return Err(errors); + } + Ok(res) +} + +pub(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); + println!("{}", res.source.program); + 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(()) +} From 83332e64699c677789e5c7ac491ae0fb7a626885 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Wed, 5 Feb 2025 09:59:10 -0800 Subject: [PATCH 03/98] Add ability to parse quantum decls and lit exprs (#2160) Co-authored-by: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> --- compiler/qsc_qasm3/src/ast.rs | 47 +- compiler/qsc_qasm3/src/lex/cooked.rs | 68 +- compiler/qsc_qasm3/src/lex/cooked/tests.rs | 264 +++++++- compiler/qsc_qasm3/src/lex/raw.rs | 126 ++-- compiler/qsc_qasm3/src/lex/raw/tests.rs | 148 ++++- .../qsc_qasm3/src/parser/completion/tests.rs | 4 +- compiler/qsc_qasm3/src/parser/expr.rs | 181 +++-- compiler/qsc_qasm3/src/parser/expr/tests.rs | 617 ++++++++++++++++++ compiler/qsc_qasm3/src/parser/prim/tests.rs | 4 +- compiler/qsc_qasm3/src/parser/stmt.rs | 31 +- compiler/qsc_qasm3/src/parser/stmt/tests.rs | 67 ++ 11 files changed, 1417 insertions(+), 140 deletions(-) create mode 100644 compiler/qsc_qasm3/src/parser/expr/tests.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests.rs diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index b63abb55d3..18fb0d2c91 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -5,6 +5,7 @@ #![allow(dead_code)] use indenter::{indented, Indented}; +use num_bigint::BigInt; use qsc_data_structures::span::{Span, WithSpan}; use std::{ fmt::{self, Display, Formatter, Write}, @@ -1037,7 +1038,7 @@ impl Display for IncludeStmt { #[derive(Clone, Debug)] pub struct QubitDeclaration { pub span: Span, - pub qubit: Identifier, + pub qubit: Ident, pub size: Option, } @@ -1571,7 +1572,7 @@ pub enum ExprKind { Ident(Ident), UnaryExpr(UnaryExpr), BinaryExpr(BinaryExpr), - Literal(Lit), + Lit(Lit), FunctionCall(FunctionCall), Cast(Cast), Concatenation(Concatenation), @@ -1583,14 +1584,14 @@ impl Display for ExprKind { let indent = set_indentation(indented(f), 0); match self { ExprKind::Err => write!(f, "Err"), - ExprKind::Ident(id) => write!(f, "Ident {id}"), - ExprKind::UnaryExpr(expr) => write!(f, "UnaryExpr {expr}"), + ExprKind::Ident(id) => write!(f, "{id}"), + ExprKind::UnaryExpr(expr) => write!(f, "{expr}"), ExprKind::BinaryExpr(expr) => display_bin_op(indent, expr), - ExprKind::Literal(lit) => write!(f, "Literal {lit}"), - ExprKind::FunctionCall(call) => write!(f, "FunctionCall {call}"), - ExprKind::Cast(cast) => write!(f, "Cast {cast}"), - ExprKind::Concatenation(concat) => write!(f, "Concatenation {concat}"), - ExprKind::IndexExpr(index) => write!(f, "IndexExpr {index}"), + ExprKind::Lit(lit) => write!(f, "{lit}"), + ExprKind::FunctionCall(call) => write!(f, "{call}"), + ExprKind::Cast(cast) => write!(f, "{cast}"), + ExprKind::Concatenation(concat) => write!(f, "{concat}"), + ExprKind::IndexExpr(index) => write!(f, "{index}"), } } } @@ -1740,19 +1741,20 @@ pub struct Lit { impl Display for Lit { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "Lit {}: {}", self.span, self.kind) + write!(f, "Lit: {}", self.kind) } } #[derive(Clone, Debug)] pub enum LiteralKind { Array(List), - Bitstring { value: usize, width: u32 }, - Boolean(bool), + Bitstring(BigInt, usize), + Bool(bool), Duration { value: f64, unit: TimeUnit }, Float(f64), Imaginary(f64), - Integer(i64), + Int(i64), + BigInt(BigInt), String(Rc), } @@ -1764,21 +1766,22 @@ impl Display for LiteralKind { write!(indent, "Array:")?; indent = set_indentation(indent, 1); for expr in exprs { - write!(indent, "\n{expr}")?; + write!(indent, "\n{expr:?}")?; } Ok(()) } - LiteralKind::Bitstring { value, width } => { - write!(f, "Bitstring: {value} (width {width})") + LiteralKind::Bitstring(value, width) => { + write!(f, "Bitstring(\"{:0>width$}\")", value.to_str_radix(2)) } - LiteralKind::Boolean(b) => write!(f, "Boolean: {b}"), + LiteralKind::Bool(b) => write!(f, "Bool({b:?})"), LiteralKind::Duration { value, unit } => { - write!(f, "Duration: {value} {unit}") + write!(f, "Duration({value:?}, {unit:?})") } - LiteralKind::Float(value) => write!(f, "Float: {value}"), - LiteralKind::Imaginary(value) => write!(f, "Imaginary: {value} im"), - LiteralKind::Integer(i) => write!(f, "Integer: {i}"), - LiteralKind::String(s) => write!(f, "String: {s}"), + 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:?})"), } } } diff --git a/compiler/qsc_qasm3/src/lex/cooked.rs b/compiler/qsc_qasm3/src/lex/cooked.rs index afc1bbb62d..c4bfe12c28 100644 --- a/compiler/qsc_qasm3/src/lex/cooked.rs +++ b/compiler/qsc_qasm3/src/lex/cooked.rs @@ -49,6 +49,10 @@ pub enum Error { #[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), @@ -64,6 +68,7 @@ impl Error { 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), } } @@ -73,6 +78,7 @@ impl Error { Error::Incomplete(_, _, _, s) | Error::IncompleteEof(_, _, s) | Error::UnterminatedString(s) + | Error::InvalidEscapeSequence(s) | Error::Unknown(_, s) => s, } } @@ -82,6 +88,7 @@ impl Error { #[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] pub enum TokenKind { Annotation, + Pragma, Keyword(Keyword), Type(Type), @@ -118,7 +125,6 @@ pub enum TokenKind { PlusPlus, /// `->` Arrow, - At, // Operators, ClosedBinOp(ClosedBinOp), @@ -141,6 +147,7 @@ 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"), @@ -166,7 +173,6 @@ impl Display for TokenKind { 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}`"), @@ -273,7 +279,6 @@ impl FromStr for Type { #[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] pub enum Literal { Bitstring, - Boolean, Float, Imaginary, Integer(Radix), @@ -285,7 +290,6 @@ impl Display for Literal { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(match self { Literal::Bitstring => "bitstring", - Literal::Boolean => "boolean", Literal::Float => "float", Literal::Imaginary => "imaginary", Literal::Integer(_) => "integer", @@ -459,6 +463,23 @@ impl<'a> Lexer<'a> { tokens.next().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); + self.next_if_eq(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(); @@ -471,6 +492,7 @@ impl<'a> Lexer<'a> { 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 } => { @@ -487,7 +509,13 @@ impl<'a> Lexer<'a> { } raw::TokenKind::Ident => { let ident = &self.input[(token.offset as usize)..(self.offset() as usize)]; - Ok(Some(Self::ident(ident))) + let cooked_ident = Self::ident(ident); + if matches!(cooked_ident, TokenKind::Keyword(Keyword::Pragma)) { + self.eat_to_end_of_line(); + Ok(Some(TokenKind::Pragma)) + } else { + Ok(Some(cooked_ident)) + } } raw::TokenKind::HardwareQubit => Ok(Some(TokenKind::HardwareQubit)), raw::TokenKind::LiteralFragment(_) => { @@ -525,6 +553,26 @@ impl<'a> Lexer<'a> { _ => Ok(Some(number.into())), } } + raw::TokenKind::Single(Single::Sharp) => { + let complete = TokenKind::Pragma; + self.expect(raw::TokenKind::Ident, complete)?; + let ident = &self.input[(token.offset as usize + 1)..(self.offset() as usize)]; + if matches!(Self::ident(ident), TokenKind::Keyword(Keyword::Pragma)) { + self.eat_to_end_of_line(); + Ok(Some(complete)) + } else { + let span = Span { + lo: token.offset, + hi: self.offset(), + }; + Err(Error::Incomplete( + raw::TokenKind::Ident, + complete, + raw::TokenKind::Ident, + span, + )) + } + } raw::TokenKind::Single(single) => self.single(single).map(Some), raw::TokenKind::String { terminated: true } => { Ok(Some(TokenKind::Literal(Literal::String))) @@ -565,7 +613,13 @@ impl<'a> Lexer<'a> { Ok(self.closed_bin_op(ClosedBinOp::Amp)) } } - Single::At => Ok(TokenKind::At), + Single::At => { + // AnnotationKeyword: '@' Identifier ('.' Identifier)* -> pushMode(EAT_TO_LINE_END); + let complete = TokenKind::Annotation; + self.expect(raw::TokenKind::Ident, complete); + self.eat_to_end_of_line(); + Ok(complete) + } Single::Bang => { if self.next_if_eq_single(Single::Eq) { Ok(TokenKind::ComparisonOp(ComparisonOp::BangEq)) @@ -627,6 +681,7 @@ impl<'a> Lexer<'a> { } } 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) { @@ -659,7 +714,6 @@ impl<'a> Lexer<'a> { "delay" => TokenKind::Delay, "reset" => TokenKind::Reset, "measure" => TokenKind::Measure, - "false" | "true" => TokenKind::Literal(Literal::Boolean), ident => { if let Ok(keyword) = ident.parse::() { TokenKind::Keyword(keyword) diff --git a/compiler/qsc_qasm3/src/lex/cooked/tests.rs b/compiler/qsc_qasm3/src/lex/cooked/tests.rs index 66d0f0c9c1..5b6df0c593 100644 --- a/compiler/qsc_qasm3/src/lex/cooked/tests.rs +++ b/compiler/qsc_qasm3/src/lex/cooked/tests.rs @@ -43,12 +43,12 @@ fn op_string(kind: TokenKind) -> Option { TokenKind::ClosedBinOp(op) => Some(op.to_string()), TokenKind::BinOpEq(super::ClosedBinOp::AmpAmp | super::ClosedBinOp::BarBar) | TokenKind::Literal(_) - | TokenKind::Annotation => None, + | 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::At => Some("@".to_string()), TokenKind::Eof => Some("EOF".to_string()), } } @@ -833,6 +833,147 @@ fn string_missing_ending() { ); } +#[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( @@ -860,19 +1001,24 @@ fn unknown() { &expect![[r#" [ Err( - Unknown( - '#', + Incomplete( + Ident, + Pragma, + Single( + Sharp, + ), Span { - lo: 0, - hi: 1, + lo: 1, + hi: 2, }, ), ), Err( - Unknown( - '#', + IncompleteEof( + Ident, + Pragma, Span { - lo: 1, + lo: 2, hi: 2, }, ), @@ -941,3 +1087,103 @@ fn comment_four_slashes() { "#]], ); } + +#[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, + }, + }, + ), + ] + "#]], + ); +} diff --git a/compiler/qsc_qasm3/src/lex/raw.rs b/compiler/qsc_qasm3/src/lex/raw.rs index 65bf3d7fea..7b36bad167 100644 --- a/compiler/qsc_qasm3/src/lex/raw.rs +++ b/compiler/qsc_qasm3/src/lex/raw.rs @@ -25,10 +25,11 @@ use std::{ /// 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 LexError { - /// An incomplete token was parsed, e.g., a string missing - /// the closing quote or a number ending in an underscore. - Incomplete(T), +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, @@ -115,6 +116,8 @@ pub enum Single { Plus, /// `;` Semi, + /// `#` Used for pragmas. + Sharp, /// `/` Slash, /// `*` @@ -147,6 +150,7 @@ impl Display for Single { Single::Percent => '%', Single::Plus => '+', Single::Semi => ';', + Single::Sharp => '#', Single::Slash => '/', Single::Star => '*', Single::Tilde => '~', @@ -318,59 +322,74 @@ impl<'a> Lexer<'a> { } } - fn number(&mut self, c: char) -> Result> { - self.leading_zero(c) - .or_else(|_| self.leading_dot(c)) - .or_else(|_| self.decimal_or_float(c)) + 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> { - let first = self.first(); - if c == '.' && first.is_some_and(|c| c == '_' || c.is_ascii_digit()) { - self.chars.next(); - let c1 = first.expect("first.is_some_and() succeeded"); + 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(LexError::None) => Ok(Number::Float), - Err(_) => Err(LexError::Incomplete(Number::Float)), + Ok(()) | Err(NumberLexError::None) => Ok(Number::Float), + Err(err) => Err(err), } } else { - Err(LexError::None) + 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> { + fn mid_dot(&mut self, c: char) -> Result { if c == '.' { match self.first() { - Some(c1) if c1 == '_' || c1.is_ascii_digit() => { + Some(c1) if c1.is_ascii_digit() => { self.chars.next(); match self.decimal(c1) { - Err(LexError::Incomplete(_)) => Err(LexError::Incomplete(Number::Float)), - Ok(_) | Err(LexError::None) => match self.exp() { - Ok(()) | Err(LexError::None) => Ok(Number::Float), - Err(_) => Err(LexError::Incomplete(Number::Float)), + 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') => match self.exp() { + Ok(()) => Ok(Number::Float), + Err(_) => todo!(), + }, None | Some(_) => Ok(Number::Float), } } else { - Err(LexError::None) + 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> { + fn leading_zero(&mut self, c: char) -> Result { if c != '0' { - return Err(LexError::None); + return Err(NumberLexError::None); } let radix = if self.next_if_eq('b') || self.next_if_eq('B') { @@ -387,7 +406,8 @@ impl<'a> Lexer<'a> { match radix { Radix::Binary | Radix::Octal | Radix::Hexadecimal => match last_eaten { - None | Some('_') => Err(LexError::Incomplete(Number::Int(radix))), + None => Err(NumberLexError::Incomplete), + Some('_') => Err(NumberLexError::EndsInUnderscore), _ => Ok(Number::Int(radix)), }, Radix::Decimal => match self.first() { @@ -395,6 +415,11 @@ impl<'a> Lexer<'a> { self.chars.next(); self.mid_dot(c1) } + Some('e') => match self.exp() { + Ok(()) => Ok(Number::Float), + Err(NumberLexError::None) => unreachable!(), + Err(_) => Err(NumberLexError::EndsInUnderscore), + }, None | Some(_) => Ok(Number::Int(Radix::Decimal)), }, } @@ -404,23 +429,23 @@ impl<'a> Lexer<'a> { /// 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> { + fn decimal(&mut self, c: char) -> Result { if !c.is_ascii_digit() { - return Err(LexError::None); + return Err(NumberLexError::None); } let last_eaten = self.eat_while(|c| c == '_' || c.is_ascii_digit()); match last_eaten { - None if c == '_' => Err(LexError::None), - Some('_') => Err(LexError::Incomplete(Number::Int(Radix::Decimal))), + 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> { + fn decimal_or_float(&mut self, c: char) -> Result { self.decimal(c)?; match self.first() { None => Ok(Number::Int(Radix::Decimal)), @@ -430,36 +455,42 @@ impl<'a> Lexer<'a> { } _ => match self.exp() { Ok(()) => Ok(Number::Float), - Err(LexError::None) => Ok(Number::Int(Radix::Decimal)), - Err(_) => Err(LexError::Incomplete(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 was missing or incomplete. + /// 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<(), LexError> { + 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 the next character isn't a digit or an - // underscore we issue an error without consuming it. - let first = self.first().ok_or(LexError::Incomplete(Number::Float))?; - if first != '_' && !first.is_ascii_digit() { - Err(LexError::Incomplete(Number::Float)) - } else { + // 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(_) => Err(LexError::Incomplete(Number::Float)), + Err(NumberLexError::EndsInUnderscore) => Err(NumberLexError::EndsInUnderscore), + Err(NumberLexError::None | NumberLexError::Incomplete) => unreachable!(), } + } else { + Ok(()) } } else { - Err(LexError::None) + Err(NumberLexError::None) } } @@ -477,6 +508,8 @@ impl<'a> Lexer<'a> { 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('\\') { @@ -543,8 +576,10 @@ impl Iterator for Lexer<'_> { } else { match self.number(c) { Ok(number) => TokenKind::Number(number), - Err(LexError::Incomplete(_)) => TokenKind::Unknown, - Err(LexError::None) => self + Err(NumberLexError::EndsInUnderscore | NumberLexError::Incomplete) => { + TokenKind::Unknown + } + Err(NumberLexError::None) => self .string(c) .or_else(|| single(c).map(TokenKind::Single)) .unwrap_or(TokenKind::Unknown), @@ -584,6 +619,7 @@ fn single(c: char) -> Option { '>' => Some(Single::Gt), '|' => Some(Single::Bar), '~' => Some(Single::Tilde), + '#' => Some(Single::Sharp), _ => None, } } diff --git a/compiler/qsc_qasm3/src/lex/raw/tests.rs b/compiler/qsc_qasm3/src/lex/raw/tests.rs index 96ff8383ec..3850b06485 100644 --- a/compiler/qsc_qasm3/src/lex/raw/tests.rs +++ b/compiler/qsc_qasm3/src/lex/raw/tests.rs @@ -176,10 +176,27 @@ fn string() { ); } +#[test] +fn string_missing_ending() { + check( + r#""string"#, + &expect![[r#" + [ + Token { + kind: String { + terminated: false, + }, + offset: 0, + }, + ] + "#]], + ); +} + #[test] fn string_escape_quote() { check( - r#""str\"ing""#, + r#""\"""#, &expect![[r#" [ Token { @@ -194,14 +211,82 @@ fn string_escape_quote() { } #[test] -fn string_missing_ending() { +fn string_escape_single_quote() { check( - r#""string"#, + r#""\'""#, &expect![[r#" [ Token { kind: String { - terminated: false, + 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, }, @@ -532,6 +617,23 @@ fn float() { ); } +#[test] +fn incomplete_exponent_lexed_as_float() { + check( + "1.e", + &expect![[r#" + [ + Token { + kind: Number( + Float, + ), + offset: 0, + }, + ] + "#]], + ); +} + #[test] fn leading_zero() { check( @@ -666,6 +768,36 @@ fn leading_point_exp() { ); } +#[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 leading_zero_point() { check( @@ -724,11 +856,15 @@ fn unknown() { &expect![[r#" [ Token { - kind: Unknown, + kind: Single( + Sharp, + ), offset: 0, }, Token { - kind: Unknown, + kind: Single( + Sharp, + ), offset: 1, }, ] diff --git a/compiler/qsc_qasm3/src/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs index 396380748a..dddafe1a79 100644 --- a/compiler/qsc_qasm3/src/parser/completion/tests.rs +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -36,7 +36,7 @@ fn begin_document() { "|OPENQASM 3;", &expect![[r" WordKinds( - Annotation | Include | OpenQASM | Pragma, + Annotation | Include | OpenQASM | Pragma | Qubit, ) "]], ); @@ -48,7 +48,7 @@ fn end_of_version() { "OPENQASM 3;|", &expect![[r" WordKinds( - Annotation | Include | Pragma, + Annotation | Include | Pragma | Qubit, ) "]], ); diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index e1b7cfd57c..489211fecd 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -7,10 +7,17 @@ //! 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::{ - ast::{BinOp, Expr, Lit, LiteralKind, StmtKind, UnOp, Version}, + ast::{BinOp, Expr, ExprKind, ExprStmt, Lit, LiteralKind, UnOp, Version}, keyword::Keyword, - lex::{cooked::Literal, ClosedBinOp, Radix, Token, TokenKind}, + lex::{cooked::Literal, ClosedBinOp, Delim, Radix, Token, TokenKind}, parser::{ completion::WordKinds, prim::{shorten, token}, @@ -61,37 +68,34 @@ enum Assoc { const RANGE_PRECEDENCE: u8 = 1; pub(super) fn expr(s: &mut ParserContext) -> Result> { - Err(Error::new(ErrorKind::Rule( - "expression", - s.peek().kind, - s.peek().span, - ))) + expr_op(s, OpContext::Precedence(0)) } -pub(super) fn expr_eof(s: &mut ParserContext) -> Result> { - let expr = expr(s)?; - token(s, TokenKind::Eof)?; - Ok(expr) +pub(super) fn expr_stmt(s: &mut ParserContext) -> Result> { + expr_op(s, OpContext::Stmt) +} + +fn expr_op(s: &mut ParserContext, _context: OpContext) -> Result> { + let lhs = expr_base(s)?; + Ok(lhs) } -/// Returns true if the expression kind is statement-final. When a statement-final expression occurs -/// at the top level of an expression statement, it indicates the end of the statement, and any -/// operators following it will not be parsed as part of the expression. Statement-final expressions -/// in a top level position also do not require a semicolon when they are followed by another -/// statement. -pub(super) fn is_stmt_final(kind: &StmtKind) -> bool { - matches!( +fn expr_base(s: &mut ParserContext) -> Result> { + let lo = s.peek().span.lo; + let kind = if let Some(l) = lit(s)? { + Ok(Box::new(ExprKind::Lit(l))) + } else { + Err(Error::new(ErrorKind::Rule( + "expression", + s.peek().kind, + s.peek().span, + ))) + }?; + + Ok(Box::new(Expr { + span: s.span(lo), kind, - StmtKind::Block(_) - | StmtKind::Box(_) - | StmtKind::Cal(_) - | StmtKind::DefCal(_) - | StmtKind::Def(_) - | StmtKind::If(_) - | StmtKind::For(_) - | StmtKind::Switch(_) - | StmtKind::WhileLoop(_) - ) + })) } pub(super) fn lit(s: &mut ParserContext) -> Result> { @@ -136,12 +140,20 @@ fn lit_token(lexeme: &str, token: Token) -> Result> { 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()) - .ok_or(Error::new(ErrorKind::Lit("integer", token.span)))?; - Ok(Some(Lit { - kind: LiteralKind::Integer(value), - span: token.span, - })) + 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('_', ""); @@ -153,25 +165,65 @@ fn lit_token(lexeme: &str, token: Token) -> Result> { span: token.span, })) } - Literal::String => { - let lit = shorten(1, 1, lexeme); + 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 = lexeme + .to_string() + .chars() + .filter(|c| *c == '0' || *c == '1') + .count(); + // 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 { - kind: LiteralKind::String(lit.into()), span: token.span, + kind: LiteralKind::Bitstring(value, width), })) } - Literal::Bitstring => todo!("bitstring literal"), - Literal::Boolean => todo!("boolean literal"), - Literal::Imaginary => todo!("imaginary literal"), - Literal::Timing(_timing_literal_kind) => todo!("timing literal"), + 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(_timing_literal_kind) => Err(Error::new(ErrorKind::Lit( + "unimplemented: timing literal", + token.span, + ))), }, TokenKind::Keyword(Keyword::True) => Ok(Some(Lit { - kind: LiteralKind::Boolean(true), + kind: LiteralKind::Bool(true), span: token.span, })), TokenKind::Keyword(Keyword::False) => Ok(Some(Lit { - kind: LiteralKind::Boolean(false), + kind: LiteralKind::Bool(false), span: token.span, })), _ => Ok(None), @@ -254,6 +306,15 @@ fn lit_int(lexeme: &str, radix: u32) -> Option { .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 prefix_op(name: OpName) -> Option { match name { OpName::Token(TokenKind::Bang) => Some(PrefixOp { @@ -290,3 +351,37 @@ fn closed_bin_op(op: ClosedBinOp) -> BinOp { 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) +} + +pub(super) fn designator(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Open(Delim::Bracket))?; + let expr = expr(s)?; + token(s, TokenKind::Close(Delim::Bracket))?; + Ok(ExprStmt { + span: s.span(lo), + expr, + }) +} 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..3fe0d6d034 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -0,0 +1,617 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::parser::tests::check; + +use super::expr; + +use expect_test::expect; + +#[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] +#[ignore = "Re-enable when we support unary ops"] +fn lit_int_min() { + check( + expr, + "-9_223_372_036_854_775_808", + &expect![[r#" + Expr [0-26]: UnOp (Neg): + 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] +#[ignore = "Re-enable when we support more than literals"] +fn lit_int_hexadecimal_dot() { + check( + expr, + "0x123.45", + &expect![[r#" + Expr [0-6]: Field: + Expr [0-5]: Lit: Int(291) + Err + + [ + Error( + Rule( + "identifier", + Int( + Decimal, + ), + Span { + lo: 6, + hi: 8, + }, + ), + ), + ]"#]], + ); +} + +#[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#"10"#, &expect!["Expr [0-2]: Lit: Int(10)"]); +} + +#[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)"]); +} diff --git a/compiler/qsc_qasm3/src/parser/prim/tests.rs b/compiler/qsc_qasm3/src/parser/prim/tests.rs index cacf228ebb..d21861f1cd 100644 --- a/compiler/qsc_qasm3/src/parser/prim/tests.rs +++ b/compiler/qsc_qasm3/src/parser/prim/tests.rs @@ -188,7 +188,7 @@ fn opt_fail_no_consume() { fn opt_fail_consume() { check_opt( |s| opt(s, path), - "Foo.#", + "Foo.$", &expect![[r#" Err IncompletePath [0-5]: Ident [0-3] "Foo" @@ -197,7 +197,7 @@ fn opt_fail_consume() { Error( Lex( Unknown( - '#', + '$', Span { lo: 4, hi: 5, diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index d5011eeefb..13620e342b 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -1,17 +1,23 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#[cfg(test)] +pub(crate) mod tests; + use qsc_data_structures::span::Span; use super::{ completion::WordKinds, error::{Error, ErrorKind}, - expr::{self}, - prim::{barrier, many, opt, recovering, recovering_semi, recovering_token}, + expr::{self, designator}, + prim::{self, barrier, many, opt, recovering, recovering_semi, recovering_token}, Result, }; use crate::{ - ast::{Annotation, Block, IncludeStmt, LiteralKind, PathKind, Pragma, Stmt, StmtKind}, + ast::{ + Annotation, Block, IncludeStmt, LiteralKind, PathKind, Pragma, QubitDeclaration, Stmt, + StmtKind, + }, lex::{cooked::Literal, Delim, TokenKind}, }; @@ -37,6 +43,8 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { } } else if let Some(v) = opt(s, parse_include)? { Box::new(v) + } else if let Some(decl) = opt(s, parse_quantum_decl)? { + Box::new(decl) } else { return Err(Error::new(ErrorKind::Rule( "statement", @@ -82,7 +90,7 @@ fn default(span: Span) -> Box { fn parse_annotation(s: &mut ParserContext) -> Result> { let lo = s.peek().span.lo; s.expect(WordKinds::Annotation); - token(s, TokenKind::At)?; + token(s, TokenKind::Annotation)?; // parse name // parse value recovering_semi(s); @@ -130,3 +138,18 @@ fn parse_pragma(s: &mut ParserContext) -> Result { value: None, }) } + +fn parse_quantum_decl(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + s.expect(WordKinds::Qubit); + token(s, TokenKind::Keyword(crate::keyword::Keyword::Qubit))?; + let size = opt(s, designator)?; + let name = prim::ident(s)?; + + recovering_semi(s); + Ok(StmtKind::QuantumDecl(QubitDeclaration { + span: s.span(lo), + qubit: *name, + size, + })) +} 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..a3fc5aa008 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests.rs @@ -0,0 +1,67 @@ +use expect_test::expect; + +use crate::parser::tests::check; + +use super::parse; + +#[test] +fn quantum_decl() { + check( + parse, + "qubit q;", + &expect![[r#" + Stmt [0-8] + StmtKind: QubitDeclaration [0-8]: Ident [6-7] "q""#]], + ); +} + +#[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] + StmtKind: QubitDeclaration [0-16]: Ident [9-15] "qubits", ExprStmt [5-8]: 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, + }, + ), + ) + "#]], + ); +} From c73cdbc876e7bdcba0775f53255e24d3f16257d3 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Fri, 7 Feb 2025 12:52:29 -0800 Subject: [PATCH 04/98] Adding classical & io decls to the parser, stubbing out array decls (#2165) --- compiler/qsc_qasm3/src/ast.rs | 124 ++-- compiler/qsc_qasm3/src/lex/cooked.rs | 1 - .../qsc_qasm3/src/parser/completion/tests.rs | 12 +- compiler/qsc_qasm3/src/parser/expr.rs | 17 +- compiler/qsc_qasm3/src/parser/stmt.rs | 483 ++++++++++++- compiler/qsc_qasm3/src/parser/stmt/tests.rs | 75 +- .../src/parser/stmt/tests/annotation.rs | 49 ++ .../src/parser/stmt/tests/classical_decl.rs | 670 ++++++++++++++++++ .../src/parser/stmt/tests/io_decl.rs | 338 +++++++++ .../qsc_qasm3/src/parser/stmt/tests/pragma.rs | 114 +++ .../src/parser/stmt/tests/quantum_decl.rs | 102 +++ 11 files changed, 1840 insertions(+), 145 deletions(-) create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/annotation.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/io_decl.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/pragma.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/quantum_decl.rs diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index 18fb0d2c91..7e2314e4e7 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -62,10 +62,11 @@ pub struct Stmt { impl Display for Stmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut indent = set_indentation(indented(f), 0); + write!(indent, "Stmt {}", self.span)?; + indent = set_indentation(indent, 1); for annotation in &self.annotations { write!(indent, "\n{annotation}")?; } - write!(indent, "Stmt {}", self.span)?; write!(indent, "\n{}", self.kind)?; Ok(()) } @@ -74,15 +75,19 @@ impl Display for Stmt { #[derive(Clone, Debug)] pub struct Annotation { pub span: Span, - pub name: Box, + pub identifier: Rc, pub value: Option>, } impl Display for Annotation { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Some(value) = &self.value { - write!(f, "Annotation {}: {}, {}", self.span, self.name, value) + write!( + f, + "Annotation {}: ({}, {})", + self.span, self.identifier, value + ) } else { - write!(f, "Annotation {}: {}", self.span, self.name) + write!(f, "Annotation {}: ({})", self.span, self.identifier) } } } @@ -712,7 +717,7 @@ impl Display for QuantumMeasurement { #[derive(Clone, Debug)] pub struct ClassicalArgument { pub span: Span, - pub r#type: ClassicalType, + pub r#type: ScalarType, pub name: Identifier, pub access: Option, } @@ -738,7 +743,7 @@ impl Display for ClassicalArgument { #[derive(Clone, Debug)] pub struct ExternArgument { pub span: Span, - pub r#type: ClassicalType, + pub r#type: ScalarType, pub access: Option, } @@ -757,46 +762,42 @@ impl Display for ExternArgument { } #[derive(Clone, Debug)] -pub struct ClassicalType { +pub struct ScalarType { pub span: Span, - pub kind: ClassicalTypeKind, + pub kind: ScalarTypeKind, } -impl Display for ClassicalType { +impl Display for ScalarType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "ClassicalType {}: {}", self.span, self.kind) } } #[derive(Clone, Debug)] -pub enum ClassicalTypeKind { +pub enum ScalarTypeKind { + Bit(BitType), Int(IntType), UInt(UIntType), Float(FloatType), Complex(ComplexType), Angle(AngleType), - Bit(BitType), BoolType, - Array(ArrayType), - ArrayReference(ArrayReferenceType), Duration, Stretch, } -impl Display for ClassicalTypeKind { +impl Display for ScalarTypeKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - ClassicalTypeKind::Int(int) => write!(f, "ClassicalTypeKind {int}"), - ClassicalTypeKind::UInt(uint) => write!(f, "ClassicalTypeKind {uint}"), - ClassicalTypeKind::Float(float) => write!(f, "ClassicalTypeKind {float}"), - ClassicalTypeKind::Complex(complex) => write!(f, "ClassicalTypeKind {complex}"), - ClassicalTypeKind::Angle(angle) => write!(f, "ClassicalTypeKind {angle}"), - ClassicalTypeKind::Bit(bit) => write!(f, "ClassicalTypeKind {bit}"), - ClassicalTypeKind::BoolType => write!(f, "ClassicalTypeKind BoolType"), - ClassicalTypeKind::Array(array) => write!(f, "ClassicalTypeKind {array}"), - ClassicalTypeKind::ArrayReference(array) => write!(f, "ClassicalTypeKind {array}"), - ClassicalTypeKind::Duration => write!(f, "ClassicalTypeKind Duration"), - ClassicalTypeKind::Stretch => write!(f, "ClassicalTypeKind Stretch"), + 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"), } } } @@ -808,8 +809,8 @@ pub enum ArrayBaseTypeKind { Float(FloatType), Complex(ComplexType), Angle(AngleType), - Bit(BitType), BoolType, + Duration, } impl Display for ArrayBaseTypeKind { @@ -820,7 +821,7 @@ impl Display for ArrayBaseTypeKind { ArrayBaseTypeKind::Float(float) => write!(f, "ArrayBaseTypeKind {float}"), ArrayBaseTypeKind::Complex(complex) => write!(f, "ArrayBaseTypeKind {complex}"), ArrayBaseTypeKind::Angle(angle) => write!(f, "ArrayBaseTypeKind {angle}"), - ArrayBaseTypeKind::Bit(bit) => write!(f, "ArrayBaseTypeKind {bit}"), + ArrayBaseTypeKind::Duration => write!(f, "ArrayBaseTypeKind DurationType"), ArrayBaseTypeKind::BoolType => write!(f, "ArrayBaseTypeKind BoolType"), } } @@ -835,9 +836,9 @@ pub struct IntType { impl Display for IntType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Some(size) = &self.size { - write!(f, "IntType {}: {}", self.span, size) + write!(f, "IntType[{}]: {}", size, self.span) } else { - write!(f, "IntType") + write!(f, "IntType {}", self.span) } } } @@ -851,9 +852,9 @@ pub struct UIntType { impl Display for UIntType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Some(size) = &self.size { - write!(f, "UIntType {}: {}", self.span, size) + write!(f, "UIntType[{}]: {}", size, self.span) } else { - write!(f, "UIntType") + write!(f, "UIntType {}", self.span) } } } @@ -867,9 +868,9 @@ pub struct FloatType { impl Display for FloatType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Some(size) = &self.size { - write!(f, "FloatType {}: {}", self.span, size) + write!(f, "FloatType[{}]: {}", size, self.span) } else { - write!(f, "FloatType") + write!(f, "FloatType {}", self.span) } } } @@ -883,9 +884,9 @@ pub struct ComplexType { impl Display for ComplexType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Some(size) = &self.base_size { - write!(f, "ComplexType {}: {}", self.span, size) + write!(f, "ComplexType[float[{}]]: {}", size, self.span) } else { - write!(f, "ComplexType") + write!(f, "ComplexType {}", self.span) } } } @@ -901,7 +902,7 @@ impl Display for AngleType { if let Some(size) = &self.size { write!(f, "AngleType {}: {}", self.span, size) } else { - write!(f, "AngleType") + write!(f, "AngleType {}", self.span) } } } @@ -922,11 +923,28 @@ impl Display for BitType { } } +#[derive(Clone, Debug)] +pub enum TypeDef { + Scalar(ScalarType), + Array(ArrayType), + ArrayReference(ArrayReferenceType), +} + +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, + pub dimensions: List, } impl Display for ArrayType { @@ -992,16 +1010,16 @@ impl Display for QuantumArgument { #[derive(Clone, Debug)] pub struct Pragma { pub span: Span, - pub name: Box, + pub identifier: Rc, pub value: Option>, } impl Display for Pragma { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Some(value) = &self.value { - write!(f, "Pragma {}: {}, {}", self.span, self.name, value) + write!(f, "Pragma {}: ({}, {})", self.span, self.identifier, value) } else { - write!(f, "Pragma {}: {}", self.span, self.name) + write!(f, "Pragma {}: ({})", self.span, self.identifier) } } } @@ -1087,7 +1105,7 @@ pub struct ExternDecl { pub span: Span, pub name: Identifier, pub arguments: List, - pub return_type: Option, + pub return_type: Option, } impl Display for ExternDecl { @@ -1245,8 +1263,8 @@ impl Display for MeasureStmt { #[derive(Clone, Debug)] pub struct ClassicalDeclarationStmt { pub span: Span, - pub r#type: ClassicalType, - pub identifier: Identifier, + pub r#type: TypeDef, + pub identifier: Box, pub init_expr: Option>, } @@ -1287,8 +1305,8 @@ impl Display for ValueExpression { pub struct IODeclaration { pub span: Span, pub io_identifier: IOKeyword, - pub r#type: ClassicalType, - pub identifier: Identifier, + pub r#type: TypeDef, + pub identifier: Box, } impl Display for IODeclaration { @@ -1303,10 +1321,10 @@ impl Display for IODeclaration { #[derive(Clone, Debug)] pub struct ConstantDeclaration { - span: Span, - r#type: ClassicalType, - identifier: Identifier, - init_expr: ExprStmt, + pub span: Span, + pub r#type: TypeDef, + pub identifier: Box, + pub init_expr: Box, } impl Display for ConstantDeclaration { @@ -1353,7 +1371,7 @@ pub struct CalibrationDefinition { name: Identifier, args: List, qubits: List, - return_type: Option, + return_type: Option, body: String, } @@ -1395,7 +1413,7 @@ pub struct DefStmt { name: Identifier, args: List>, body: List>, - return_type: Option, + return_type: Option, } impl Display for DefStmt { @@ -1489,7 +1507,7 @@ impl Display for WhileLoop { #[derive(Clone, Debug)] pub struct ForStmt { span: Span, - r#type: ClassicalType, + r#type: ScalarType, identifier: Identifier, set_declaration: Box, block: List, @@ -1647,7 +1665,7 @@ impl Display for FunctionCall { #[derive(Clone, Debug)] pub struct Cast { pub span: Span, - pub r#type: ClassicalType, + pub r#type: ScalarType, pub arg: ExprStmt, } diff --git a/compiler/qsc_qasm3/src/lex/cooked.rs b/compiler/qsc_qasm3/src/lex/cooked.rs index c4bfe12c28..d4f83c2c94 100644 --- a/compiler/qsc_qasm3/src/lex/cooked.rs +++ b/compiler/qsc_qasm3/src/lex/cooked.rs @@ -477,7 +477,6 @@ impl<'a> Lexer<'a> { fn eat_to_end_of_line(&mut self) { self.eat_while(|t| t != raw::TokenKind::Newline); - self.next_if_eq(raw::TokenKind::Newline); } /// Consumes a list of tokens zero or more times. diff --git a/compiler/qsc_qasm3/src/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs index dddafe1a79..127c972cd1 100644 --- a/compiler/qsc_qasm3/src/parser/completion/tests.rs +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -34,11 +34,11 @@ fn check_valid_words_no_source_name(input: &str, expect: &expect_test::Expect) { fn begin_document() { check_valid_words( "|OPENQASM 3;", - &expect![[r" + &expect![[r#" WordKinds( - Annotation | Include | OpenQASM | Pragma | Qubit, + Annotation | Include | Input | OpenQASM | Output | Pragma | Qubit, ) - "]], + "#]], ); } @@ -46,10 +46,10 @@ fn begin_document() { fn end_of_version() { check_valid_words( "OPENQASM 3;|", - &expect![[r" + &expect![[r#" WordKinds( - Annotation | Include | Pragma | Qubit, + Annotation | Include | Input | Output | Pragma | Qubit, ) - "]], + "#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 489211fecd..7bc73665a6 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -15,7 +15,7 @@ use num_traits::Num; use qsc_data_structures::span::Span; use crate::{ - ast::{BinOp, Expr, ExprKind, ExprStmt, Lit, LiteralKind, UnOp, Version}, + ast::{BinOp, Expr, ExprKind, ExprStmt, Lit, LiteralKind, UnOp, ValueExpression, Version}, keyword::Keyword, lex::{cooked::Literal, ClosedBinOp, Delim, Radix, Token, TokenKind}, parser::{ @@ -385,3 +385,18 @@ pub(super) fn designator(s: &mut ParserContext) -> Result { expr, }) } + +pub(super) fn value_expr(s: &mut ParserContext) -> Result> { + let lo = s.peek().span.lo; + let expr = expr_stmt(s)?; + let stmt = ExprStmt { + span: s.span(lo), + expr, + }; + // todo: measurement + Ok(Box::new(ValueExpression::Expr(stmt))) +} + +pub(crate) fn expr_list(_s: &mut ParserContext<'_>) -> Result> { + todo!("expr_list") +} diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 13620e342b..1de4ac2533 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -4,21 +4,28 @@ #[cfg(test)] pub(crate) mod tests; +use std::rc::Rc; + use qsc_data_structures::span::Span; use super::{ completion::WordKinds, error::{Error, ErrorKind}, expr::{self, designator}, - prim::{self, barrier, many, opt, recovering, recovering_semi, recovering_token}, + prim::{self, barrier, many, opt, recovering, recovering_semi, recovering_token, shorten}, Result, }; use crate::{ ast::{ - Annotation, Block, IncludeStmt, LiteralKind, PathKind, Pragma, QubitDeclaration, Stmt, - StmtKind, + AngleType, Annotation, ArrayBaseTypeKind, ArrayType, BitType, Block, + ClassicalDeclarationStmt, ComplexType, ConstantDeclaration, ExprStmt, FloatType, + IODeclaration, IOKeyword, IncludeStmt, IntType, LiteralKind, Pragma, QubitDeclaration, + ScalarType, ScalarTypeKind, Stmt, StmtKind, TypeDef, UIntType, + }, + lex::{ + cooked::{Literal, Type}, + Delim, TokenKind, }, - lex::{cooked::Literal, Delim, TokenKind}, }; use super::{prim::token, ParserContext}; @@ -43,7 +50,7 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { } } else if let Some(v) = opt(s, parse_include)? { Box::new(v) - } else if let Some(decl) = opt(s, parse_quantum_decl)? { + } else if let Some(decl) = opt(s, parse_local)? { Box::new(decl) } else { return Err(Error::new(ErrorKind::Rule( @@ -87,17 +94,54 @@ fn default(span: Span) -> Box { }) } -fn parse_annotation(s: &mut ParserContext) -> Result> { +pub fn parse_annotation(s: &mut ParserContext) -> Result> { let lo = s.peek().span.lo; s.expect(WordKinds::Annotation); - token(s, TokenKind::Annotation)?; - // parse name - // parse value - recovering_semi(s); + + 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), - name: Box::new(PathKind::default()), - value: None, + identifier, + value, })) } @@ -128,17 +172,66 @@ fn parse_include(s: &mut ParserContext) -> Result { fn parse_pragma(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; s.expect(WordKinds::Pragma); - token(s, TokenKind::Keyword(crate::keyword::Keyword::Pragma))?; - // parse name - // parse value + + 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), - name: Box::new(PathKind::default()), - value: None, + identifier, + value, }) } +fn parse_local(s: &mut ParserContext) -> Result { + if let Some(decl) = opt(s, parse_classical_decl)? { + Ok(decl) + } else if let Some(decl) = opt(s, parse_quantum_decl)? { + Ok(decl) + } else if let Some(decl) = opt(s, parse_io_decl)? { + Ok(decl) + } else { + Err(Error::new(ErrorKind::Rule( + "local declaration", + s.peek().kind, + s.peek().span, + ))) + } +} + fn parse_quantum_decl(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; s.expect(WordKinds::Qubit); @@ -153,3 +246,359 @@ fn parse_quantum_decl(s: &mut ParserContext) -> Result { size, })) } + +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 identifier = prim::ident(s)?; + recovering_semi(s); + let decl = IODeclaration { + span: s.span(lo), + io_identifier: kind, + r#type: ty, + identifier, + }; + Ok(StmtKind::IODeclaration(decl)) +} + +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, + ))) +} + +fn parse_classical_decl(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let is_const = if s.peek().kind == TokenKind::Keyword(crate::keyword::Keyword::Const) { + s.advance(); + true + } else { + false + }; + let ty = scalar_or_array_type(s)?; + + let identifier = prim::ident(s)?; + + let stmt = if is_const { + token(s, TokenKind::Eq)?; + let init_expr = expr::expr(s)?; + recovering_semi(s); + let decl = ConstantDeclaration { + span: s.span(lo), + r#type: ty, + identifier, + init_expr: Box::new(ExprStmt { + span: init_expr.span, + expr: init_expr, + }), + }; + StmtKind::ConstDecl(decl) + } else { + let init_expr = if s.peek().kind == TokenKind::Eq { + s.advance(); + Some(expr::value_expr(s)?) + } else { + None + }; + recovering_semi(s); + let decl = ClassicalDeclarationStmt { + span: s.span(lo), + r#type: ty, + identifier, + init_expr, + }; + StmtKind::ClassicalDecl(decl) + }; + + Ok(stmt) +} + +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: expr_list + .into_iter() + .map(Box::new) + .collect::>() + .into_boxed_slice(), + }) +} + +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, + ))) +} + +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 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 { + token(s, TokenKind::Type(Type::Int))?; + 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 { + token(s, TokenKind::Type(Type::UInt))?; + 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 { + token(s, TokenKind::Type(Type::Float))?; + 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 { + token(s, TokenKind::Type(Type::Angle))?; + 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) +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests.rs b/compiler/qsc_qasm3/src/parser/stmt/tests.rs index a3fc5aa008..fed929c480 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests.rs @@ -1,67 +1,8 @@ -use expect_test::expect; - -use crate::parser::tests::check; - -use super::parse; - -#[test] -fn quantum_decl() { - check( - parse, - "qubit q;", - &expect![[r#" - Stmt [0-8] - StmtKind: QubitDeclaration [0-8]: Ident [6-7] "q""#]], - ); -} - -#[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] - StmtKind: QubitDeclaration [0-16]: Ident [9-15] "qubits", ExprStmt [5-8]: 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, - }, - ), - ) - "#]], - ); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +mod annotation; +mod classical_decl; +mod io_decl; +mod pragma; +mod quantum_decl; 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..d2f5eb4942 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/annotation.rs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::parser::tests::check; + +use crate::parser::stmt::parse_annotation; + +#[test] +fn annotation() { + check( + parse_annotation, + "@a.b.d 23", + &expect!["Annotation [0-9]: (a.b.d, 23)"], + ); +} + +#[test] +fn annotation_ident_only() { + check( + parse_annotation, + "@a.b.d", + &expect!["Annotation [0-6]: (a.b.d)"], + ); +} + +#[test] +fn annotation_missing_ident() { + check( + parse_annotation, + "@", + &expect![[r#" + Annotation [0-1]: () + + [ + Error( + Rule( + "annotation missing identifier", + Annotation, + Span { + lo: 0, + hi: 1, + }, + ), + ), + ]"#]], + ); +} 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..9fb8775554 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs @@ -0,0 +1,670 @@ +// 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] + StmtKind: ClassicalDeclarationStmt [0-6]: ClassicalType [0-3]: BitType, Ident [4-5] "b""#]], + ); +} + +#[test] +fn bit_decl_bit_lit() { + check( + parse, + "bit b = 1;", + &expect![[r#" + Stmt [0-10] + StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-3]: BitType, Ident [4-5] "b", ValueExpression ExprStmt [8-9]: Expr [8-9]: Lit: Int(1)"#]], + ); +} + +#[test] +fn const_bit_decl_bit_lit() { + check( + parse, + "const bit b = 1;", + &expect![[r#" + Stmt [0-16] + StmtKind: ConstantDeclaration [0-16]: ClassicalType [6-9]: BitType, Ident [10-11] "b", ExprStmt [14-15]: Expr [14-15]: Lit: Int(1)"#]], + ); +} + +#[test] +fn bit_array_decl() { + check( + parse, + "bit[2] b;", + &expect![[r#" + Stmt [0-9] + StmtKind: ClassicalDeclarationStmt [0-9]: ClassicalType [0-6]: BitType [0-6]: ExprStmt [3-6]: Expr [4-5]: Lit: Int(2), Ident [7-8] "b""#]], + ); +} + +#[test] +fn bit_array_decl_bit_lit() { + check( + parse, + "bit[2] b = \"11\";", + &expect![[r#" + Stmt [0-16] + StmtKind: ClassicalDeclarationStmt [0-16]: ClassicalType [0-6]: BitType [0-6]: ExprStmt [3-6]: Expr [4-5]: Lit: Int(2), Ident [7-8] "b", ValueExpression ExprStmt [11-15]: 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] + StmtKind: ConstantDeclaration [0-22]: ClassicalType [6-12]: BitType [6-12]: ExprStmt [9-12]: Expr [10-11]: Lit: Int(2), Ident [13-14] "b", ExprStmt [17-21]: Expr [17-21]: Lit: Bitstring("11")"#]], + ); +} + +#[test] +fn bool_decl() { + check( + parse, + "bool b;", + &expect![[r#" + Stmt [0-7] + StmtKind: ClassicalDeclarationStmt [0-7]: ClassicalType [0-4]: BoolType, Ident [5-6] "b""#]], + ); +} + +#[test] +fn bool_decl_int_lit() { + check( + parse, + "bool b = 1;", + &expect![[r#" + Stmt [0-11] + StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-4]: BoolType, Ident [5-6] "b", ValueExpression ExprStmt [9-10]: Expr [9-10]: Lit: Int(1)"#]], + ); +} + +#[test] +fn const_bool_decl_bool_lit() { + check( + parse, + "const bool b = true;", + &expect![[r#" + Stmt [0-20] + StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-10]: BoolType, Ident [11-12] "b", ExprStmt [15-19]: Expr [15-19]: Lit: Bool(true)"#]], + ); +} + +#[test] +fn complex_decl() { + check( + parse, + "complex c;", + &expect![[r#" + Stmt [0-10] + StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-7]: ComplexType [0-7], Ident [8-9] "c""#]], + ); +} + +#[test] +fn complex_decl_complex_lit() { + check( + parse, + "complex c = 1im;", + &expect![[r#" + Stmt [0-16] + StmtKind: ClassicalDeclarationStmt [0-16]: ClassicalType [0-7]: ComplexType [0-7], Ident [8-9] "c", ValueExpression ExprStmt [12-15]: 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] + StmtKind: ConstantDeclaration [0-22]: ClassicalType [6-13]: ComplexType [6-13], Ident [14-15] "c", ExprStmt [18-21]: Expr [18-21]: Lit: Imaginary(1.0)"#]], + ); +} + +#[test] +#[ignore = "need binary operator support for const complex number exprs"] +fn const_complex_decl_complex_binary_lit() { + check( + parse, + "const complex c = 23.5 + 1.7im;", + &expect![[r#" + Stmt [0-22] + StmtKind: ConstantDeclaration [0-22]: ClassicalType [6-13]: ComplexType [6-13], Ident [14-15] "c", ExprStmt [18-22]: Expr [18-22]: Lit: Float(23.5) + + [ + Error( + Token( + Semicolon, + ClosedBinOp( + Plus, + ), + Span { + lo: 23, + hi: 24, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn complex_sized_decl() { + check( + parse, + "complex[float[32]] c;", + &expect![[r#" + Stmt [0-21] + StmtKind: ClassicalDeclarationStmt [0-21]: ClassicalType [0-18]: ComplexType[float[FloatType[ExprStmt [13-17]: Expr [14-16]: Lit: Int(32)]: [8-17]]]: [0-18], Ident [19-20] "c""#]], + ); +} + +#[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] + StmtKind: ClassicalDeclarationStmt [0-27]: ClassicalType [0-18]: ComplexType[float[FloatType[ExprStmt [13-17]: Expr [14-16]: Lit: Int(32)]: [8-17]]]: [0-18], Ident [19-20] "c", ValueExpression ExprStmt [23-26]: 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] + StmtKind: ConstantDeclaration [0-33]: ClassicalType [6-24]: ComplexType[float[FloatType[ExprStmt [19-23]: Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24], Ident [25-26] "c", ExprStmt [29-32]: Expr [29-32]: Lit: Imaginary(1.0)"#]], + ); +} + +#[test] +fn int_decl() { + check( + parse, + "int i;", + &expect![[r#" + Stmt [0-6] + StmtKind: ClassicalDeclarationStmt [0-6]: ClassicalType [0-3]: IntType [0-3], Ident [4-5] "i""#]], + ); +} + +#[test] +fn int_decl_int_lit() { + check( + parse, + "int i = 1;", + &expect![[r#" + Stmt [0-10] + StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-3]: IntType [0-3], Ident [4-5] "i", ValueExpression ExprStmt [8-9]: Expr [8-9]: Lit: Int(1)"#]], + ); +} + +#[test] +fn const_int_decl_int_lit() { + check( + parse, + "const int i = 1;", + &expect![[r#" + Stmt [0-16] + StmtKind: ConstantDeclaration [0-16]: ClassicalType [6-9]: IntType [6-9], Ident [10-11] "i", ExprStmt [14-15]: Expr [14-15]: Lit: Int(1)"#]], + ); +} + +#[test] +fn int_sized_decl() { + check( + parse, + "int[32] i;", + &expect![[r#" + Stmt [0-10] + StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-7]: IntType[ExprStmt [3-7]: Expr [4-6]: Lit: Int(32)]: [0-7], Ident [8-9] "i""#]], + ); +} + +#[test] +fn int_sized_decl_int_lit() { + check( + parse, + "int[32] i = 1;", + &expect![[r#" + Stmt [0-14] + StmtKind: ClassicalDeclarationStmt [0-14]: ClassicalType [0-7]: IntType[ExprStmt [3-7]: Expr [4-6]: Lit: Int(32)]: [0-7], Ident [8-9] "i", ValueExpression ExprStmt [12-13]: 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] + StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-13]: IntType[ExprStmt [9-13]: Expr [10-12]: Lit: Int(32)]: [6-13], Ident [14-15] "i", ExprStmt [18-19]: Expr [18-19]: Lit: Int(1)"#]], + ); +} + +#[test] +fn uint_decl() { + check( + parse, + "uint i;", + &expect![[r#" + Stmt [0-7] + StmtKind: ClassicalDeclarationStmt [0-7]: ClassicalType [0-4]: UIntType [0-4], Ident [5-6] "i""#]], + ); +} + +#[test] +fn uint_decl_uint_lit() { + check( + parse, + "uint i = 1;", + &expect![[r#" + Stmt [0-11] + StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-4]: UIntType [0-4], Ident [5-6] "i", ValueExpression ExprStmt [9-10]: Expr [9-10]: Lit: Int(1)"#]], + ); +} + +#[test] +fn const_uint_decl_uint_lit() { + check( + parse, + "const uint i = 1;", + &expect![[r#" + Stmt [0-17] + StmtKind: ConstantDeclaration [0-17]: ClassicalType [6-10]: UIntType [6-10], Ident [11-12] "i", ExprStmt [15-16]: Expr [15-16]: Lit: Int(1)"#]], + ); +} + +#[test] +fn uint_sized_decl() { + check( + parse, + "uint[32] i;", + &expect![[r#" + Stmt [0-11] + StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-8]: UIntType[ExprStmt [4-8]: Expr [5-7]: Lit: Int(32)]: [0-8], Ident [9-10] "i""#]], + ); +} + +#[test] +fn uint_sized_decl_uint_lit() { + check( + parse, + "uint[32] i = 1;", + &expect![[r#" + Stmt [0-15] + StmtKind: ClassicalDeclarationStmt [0-15]: ClassicalType [0-8]: UIntType[ExprStmt [4-8]: Expr [5-7]: Lit: Int(32)]: [0-8], Ident [9-10] "i", ValueExpression ExprStmt [13-14]: 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] + StmtKind: ConstantDeclaration [0-21]: ClassicalType [6-14]: UIntType[ExprStmt [10-14]: Expr [11-13]: Lit: Int(32)]: [6-14], Ident [15-16] "i", ExprStmt [19-20]: Expr [19-20]: Lit: Int(1)"#]], + ); +} + +#[test] +fn float_decl() { + check( + parse, + "float f;", + &expect![[r#" + Stmt [0-8] + StmtKind: ClassicalDeclarationStmt [0-8]: ClassicalType [0-5]: FloatType [0-5], Ident [6-7] "f""#]], + ); +} + +#[test] +fn float_decl_float_lit() { + check( + parse, + "float f = 1;", + &expect![[r#" + Stmt [0-12] + StmtKind: ClassicalDeclarationStmt [0-12]: ClassicalType [0-5]: FloatType [0-5], Ident [6-7] "f", ValueExpression ExprStmt [10-11]: 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] + StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-11]: FloatType [6-11], Ident [12-13] "f", ExprStmt [16-19]: Expr [16-19]: Lit: Float(1.0)"#]], + ); +} + +#[test] +fn float_sized_decl() { + check( + parse, + "float[32] f;", + &expect![[r#" + Stmt [0-12] + StmtKind: ClassicalDeclarationStmt [0-12]: ClassicalType [0-9]: FloatType[ExprStmt [5-9]: Expr [6-8]: Lit: Int(32)]: [0-9], Ident [10-11] "f""#]], + ); +} + +#[test] +fn float_sized_decl_float_lit() { + check( + parse, + "float[32] f = 1.0;", + &expect![[r#" + Stmt [0-18] + StmtKind: ClassicalDeclarationStmt [0-18]: ClassicalType [0-9]: FloatType[ExprStmt [5-9]: Expr [6-8]: Lit: Int(32)]: [0-9], Ident [10-11] "f", ValueExpression ExprStmt [14-17]: 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] + StmtKind: ConstantDeclaration [0-22]: ClassicalType [6-15]: FloatType[ExprStmt [11-15]: Expr [12-14]: Lit: Int(32)]: [6-15], Ident [16-17] "f", ExprStmt [20-21]: Expr [20-21]: Lit: Int(1)"#]], + ); +} + +#[test] +fn angle_decl() { + check( + parse, + "angle a;", + &expect![[r#" + Stmt [0-8] + StmtKind: ClassicalDeclarationStmt [0-8]: ClassicalType [0-5]: AngleType [0-5], Ident [6-7] "a""#]], + ); +} + +#[test] +fn angle_decl_angle_lit() { + check( + parse, + "angle a = 1.0;", + &expect![[r#" + Stmt [0-14] + StmtKind: ClassicalDeclarationStmt [0-14]: ClassicalType [0-5]: AngleType [0-5], Ident [6-7] "a", ValueExpression ExprStmt [10-13]: 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] + StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-11]: AngleType [6-11], Ident [12-13] "a", ExprStmt [16-19]: Expr [16-19]: Lit: Float(1.0)"#]], + ); +} + +#[test] +fn angle_sized_decl() { + check( + parse, + "angle[32] a;", + &expect![[r#" + Stmt [0-12] + StmtKind: ClassicalDeclarationStmt [0-12]: ClassicalType [0-9]: AngleType [0-9]: ExprStmt [5-9]: Expr [6-8]: Lit: Int(32), Ident [10-11] "a""#]], + ); +} + +#[test] +fn angle_sized_decl_angle_lit() { + check( + parse, + "angle[32] a = 1.0;", + &expect![[r#" + Stmt [0-18] + StmtKind: ClassicalDeclarationStmt [0-18]: ClassicalType [0-9]: AngleType [0-9]: ExprStmt [5-9]: Expr [6-8]: Lit: Int(32), Ident [10-11] "a", ValueExpression ExprStmt [14-17]: 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] + StmtKind: ConstantDeclaration [0-24]: ClassicalType [6-15]: AngleType [6-15]: ExprStmt [11-15]: Expr [12-14]: Lit: Int(32), Ident [16-17] "a", ExprStmt [20-23]: Expr [20-23]: Lit: Float(1.0)"#]], + ); +} + +#[test] +fn duration_decl() { + check( + parse, + "duration d;", + &expect![[r#" + Stmt [0-11] + StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-8]: Duration, Ident [9-10] "d""#]], + ); +} + +#[test] +#[ignore = "unimplemented: timing literal"] +fn duration_decl_ns_lit() { + check( + parse, + "duration d = 1000ns;", + &expect![[r#" + Error( + Lit( + "unimplemented: timing literal", + Span { + lo: 13, + hi: 19, + }, + ), + ) + "#]], + ); +} + +#[test] +#[ignore = "unimplemented: timing literal"] +fn duration_decl_us_lit() { + check( + parse, + "duration d = 1000us;", + &expect![[r#" + Error( + Lit( + "unimplemented: timing literal", + Span { + lo: 13, + hi: 19, + }, + ), + ) + "#]], + ); +} + +#[test] +#[ignore = "unimplemented: timing literal"] +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#" + Error( + Lit( + "unimplemented: timing literal", + Span { + lo: 13, + hi: 20, + }, + ), + ) + "#]], + ); +} + +#[test] +#[ignore = "unimplemented: timing literal"] +fn duration_decl_ms_lit() { + check( + parse, + "duration d = 1000ms;", + &expect![[r#" + Error( + Lit( + "unimplemented: timing literal", + Span { + lo: 13, + hi: 19, + }, + ), + ) + "#]], + ); +} + +#[test] +#[ignore = "unimplemented: timing literal"] +fn duration_decl_s_lit() { + check( + parse, + "duration d = 1000s;", + &expect![[r#" + Error( + Lit( + "unimplemented: timing literal", + Span { + lo: 13, + hi: 18, + }, + ), + ) + "#]], + ); +} + +#[test] +#[ignore = "unimplemented: timing literal"] +fn duration_decl_dt_lit() { + check( + parse, + "duration d = 1000dt;", + &expect![[r#" + Error( + Lit( + "unimplemented: timing literal", + Span { + lo: 13, + hi: 19, + }, + ), + ) + "#]], + ); +} + +#[test] +#[ignore = "unimplemented: timing literal"] +fn const_duration_decl_dt_lit() { + check( + parse, + "const duration d = 10dt;", + &expect![[r#" + Error( + Lit( + "unimplemented: timing literal", + Span { + lo: 19, + hi: 23, + }, + ), + ) + "#]], + ); +} + +#[test] +fn stretch_decl() { + check( + parse, + "stretch s;", + &expect![[r#" + Stmt [0-10] + StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-7]: Stretch, Ident [8-9] "s""#]], + ); +} 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..b291ee1c6e --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/io_decl.rs @@ -0,0 +1,338 @@ +// 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] + StmtKind: IODeclaration [0-12]: input, ClassicalType [6-9]: BitType, Ident [10-11] "b""#]], + ); +} + +#[test] +fn output_bit_decl() { + check( + parse, + "output bit b;", + &expect![[r#" + Stmt [0-13] + StmtKind: IODeclaration [0-13]: output, ClassicalType [7-10]: BitType, Ident [11-12] "b""#]], + ); +} + +#[test] +fn input_bit_array_decl() { + check( + parse, + "input bit[2] b;", + &expect![[r#" + Stmt [0-15] + StmtKind: IODeclaration [0-15]: input, ClassicalType [6-12]: BitType [6-12]: ExprStmt [9-12]: Expr [10-11]: Lit: Int(2), Ident [13-14] "b""#]], + ); +} + +#[test] +fn output_bit_array_decl() { + check( + parse, + "output bit[2] b;", + &expect![[r#" + Stmt [0-16] + StmtKind: IODeclaration [0-16]: output, ClassicalType [7-13]: BitType [7-13]: ExprStmt [10-13]: Expr [11-12]: Lit: Int(2), Ident [14-15] "b""#]], + ); +} + +#[test] +fn intput_bool_decl() { + check( + parse, + "input bool b;", + &expect![[r#" + Stmt [0-13] + StmtKind: IODeclaration [0-13]: input, ClassicalType [6-10]: BoolType, Ident [11-12] "b""#]], + ); +} + +#[test] +fn output_bool_decl() { + check( + parse, + "output bool b;", + &expect![[r#" + Stmt [0-14] + StmtKind: IODeclaration [0-14]: output, ClassicalType [7-11]: BoolType, Ident [12-13] "b""#]], + ); +} + +#[test] +fn input_complex_decl() { + check( + parse, + "input complex c;", + &expect![[r#" + Stmt [0-16] + StmtKind: IODeclaration [0-16]: input, ClassicalType [6-13]: ComplexType [6-13], Ident [14-15] "c""#]], + ); +} + +#[test] +fn output_complex_decl() { + check( + parse, + "output complex c;", + &expect![[r#" + Stmt [0-17] + StmtKind: IODeclaration [0-17]: output, ClassicalType [7-14]: ComplexType [7-14], Ident [15-16] "c""#]], + ); +} + +#[test] +fn input_complex_sized_decl() { + check( + parse, + "input complex[float[32]] c;", + &expect![[r#" + Stmt [0-27] + StmtKind: IODeclaration [0-27]: input, ClassicalType [6-24]: ComplexType[float[FloatType[ExprStmt [19-23]: Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24], Ident [25-26] "c""#]], + ); +} + +#[test] +fn output_complex_sized_decl() { + check( + parse, + "output complex[float[32]] c;", + &expect![[r#" + Stmt [0-28] + StmtKind: IODeclaration [0-28]: output, ClassicalType [7-25]: ComplexType[float[FloatType[ExprStmt [20-24]: Expr [21-23]: Lit: Int(32)]: [15-24]]]: [7-25], Ident [26-27] "c""#]], + ); +} + +#[test] +fn input_int_decl() { + check( + parse, + "input int i;", + &expect![[r#" + Stmt [0-12] + StmtKind: IODeclaration [0-12]: input, ClassicalType [6-9]: IntType [6-9], Ident [10-11] "i""#]], + ); +} + +#[test] +fn output_int_decl() { + check( + parse, + "output int i;", + &expect![[r#" + Stmt [0-13] + StmtKind: IODeclaration [0-13]: output, ClassicalType [7-10]: IntType [7-10], Ident [11-12] "i""#]], + ); +} + +#[test] +fn input_int_sized_decl() { + check( + parse, + "input int[32] i;", + &expect![[r#" + Stmt [0-16] + StmtKind: IODeclaration [0-16]: input, ClassicalType [6-13]: IntType[ExprStmt [9-13]: Expr [10-12]: Lit: Int(32)]: [6-13], Ident [14-15] "i""#]], + ); +} + +#[test] +fn output_int_sized_decl() { + check( + parse, + "output int[32] i;", + &expect![[r#" + Stmt [0-17] + StmtKind: IODeclaration [0-17]: output, ClassicalType [7-14]: IntType[ExprStmt [10-14]: Expr [11-13]: Lit: Int(32)]: [7-14], Ident [15-16] "i""#]], + ); +} + +#[test] +fn input_uint_decl() { + check( + parse, + "input uint i;", + &expect![[r#" + Stmt [0-13] + StmtKind: IODeclaration [0-13]: input, ClassicalType [6-10]: UIntType [6-10], Ident [11-12] "i""#]], + ); +} + +#[test] +fn output_uint_decl() { + check( + parse, + "output uint i;", + &expect![[r#" + Stmt [0-14] + StmtKind: IODeclaration [0-14]: output, ClassicalType [7-11]: UIntType [7-11], Ident [12-13] "i""#]], + ); +} + +#[test] +fn input_uint_sized_decl() { + check( + parse, + "input uint[32] i;", + &expect![[r#" + Stmt [0-17] + StmtKind: IODeclaration [0-17]: input, ClassicalType [6-14]: UIntType[ExprStmt [10-14]: Expr [11-13]: Lit: Int(32)]: [6-14], Ident [15-16] "i""#]], + ); +} + +#[test] +fn output_uint_sized_decl() { + check( + parse, + "output uint[32] i;", + &expect![[r#" + Stmt [0-18] + StmtKind: IODeclaration [0-18]: output, ClassicalType [7-15]: UIntType[ExprStmt [11-15]: Expr [12-14]: Lit: Int(32)]: [7-15], Ident [16-17] "i""#]], + ); +} + +#[test] +fn input_float_decl() { + check( + parse, + "input float f;", + &expect![[r#" + Stmt [0-14] + StmtKind: IODeclaration [0-14]: input, ClassicalType [6-11]: FloatType [6-11], Ident [12-13] "f""#]], + ); +} + +#[test] +fn output_float_decl() { + check( + parse, + "output float f;", + &expect![[r#" + Stmt [0-15] + StmtKind: IODeclaration [0-15]: output, ClassicalType [7-12]: FloatType [7-12], Ident [13-14] "f""#]], + ); +} + +#[test] +fn input_float_sized_decl() { + check( + parse, + "input float[32] f;", + &expect![[r#" + Stmt [0-18] + StmtKind: IODeclaration [0-18]: input, ClassicalType [6-15]: FloatType[ExprStmt [11-15]: Expr [12-14]: Lit: Int(32)]: [6-15], Ident [16-17] "f""#]], + ); +} + +#[test] +fn output_float_sized_decl() { + check( + parse, + "output float[32] f;", + &expect![[r#" + Stmt [0-19] + StmtKind: IODeclaration [0-19]: output, ClassicalType [7-16]: FloatType[ExprStmt [12-16]: Expr [13-15]: Lit: Int(32)]: [7-16], Ident [17-18] "f""#]], + ); +} + +#[test] +fn input_angle_decl() { + check( + parse, + "input angle a;", + &expect![[r#" + Stmt [0-14] + StmtKind: IODeclaration [0-14]: input, ClassicalType [6-11]: AngleType [6-11], Ident [12-13] "a""#]], + ); +} + +#[test] +fn output_angle_decl() { + check( + parse, + "output angle a;", + &expect![[r#" + Stmt [0-15] + StmtKind: IODeclaration [0-15]: output, ClassicalType [7-12]: AngleType [7-12], Ident [13-14] "a""#]], + ); +} + +#[test] +fn input_angle_sized_decl() { + check( + parse, + "input angle[32] a;", + &expect![[r#" + Stmt [0-18] + StmtKind: IODeclaration [0-18]: input, ClassicalType [6-15]: AngleType [6-15]: ExprStmt [11-15]: Expr [12-14]: Lit: Int(32), Ident [16-17] "a""#]], + ); +} + +#[test] +fn output_angle_sized_decl() { + check( + parse, + "output angle[32] a;", + &expect![[r#" + Stmt [0-19] + StmtKind: IODeclaration [0-19]: output, ClassicalType [7-16]: AngleType [7-16]: ExprStmt [12-16]: Expr [13-15]: Lit: Int(32), Ident [17-18] "a""#]], + ); +} + +#[test] +fn input_duration_decl() { + check( + parse, + "input duration d;", + &expect![[r#" + Stmt [0-17] + StmtKind: IODeclaration [0-17]: input, ClassicalType [6-14]: Duration, Ident [15-16] "d""#]], + ); +} + +#[test] +fn output_duration_decl() { + check( + parse, + "output duration d;", + &expect![[r#" + Stmt [0-18] + StmtKind: IODeclaration [0-18]: output, ClassicalType [7-15]: Duration, Ident [16-17] "d""#]], + ); +} + +#[test] +fn input_stretch_decl() { + check( + parse, + "input stretch s;", + &expect![[r#" + Stmt [0-16] + StmtKind: IODeclaration [0-16]: input, ClassicalType [6-13]: Stretch, Ident [14-15] "s""#]], + ); +} + +#[test] +fn output_stretch_decl() { + check( + parse, + "output stretch s;", + &expect![[r#" + Stmt [0-17] + StmtKind: IODeclaration [0-17]: output, ClassicalType [7-14]: Stretch, Ident [15-16] "s""#]], + ); +} 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..54c822619d --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/pragma.rs @@ -0,0 +1,114 @@ +// 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] + StmtKind: Pragma [0-15]: (a.b.d, 23)"#]], + ); +} + +#[test] +fn pragma_decl_ident_only() { + check( + parse, + "pragma a.b.d", + &expect![[r#" + Stmt [0-12] + StmtKind: Pragma [0-12]: (a.b.d)"#]], + ); +} + +#[test] +fn pragma_decl_missing_ident() { + check( + parse, + "pragma ", + &expect![[r#" + Stmt [0-7] + StmtKind: Pragma [0-7]: () + + [ + 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] + StmtKind: Pragma [0-16]: (a, a.b.d 23)"#]], + ); +} + +#[test] +fn legacy_pragma_decl_ident_only() { + check( + parse, + "#pragma a.b.d", + &expect![[r#" + Stmt [0-13] + StmtKind: Pragma [0-13]: (a, a.b.d)"#]], + ); +} + +#[test] +fn legacy_pragma_ws_after_hash() { + check( + parse, + "# pragma a.b.d", + &expect![[r#" + Stmt [2-14] + StmtKind: Pragma [2-14]: (a.b.d) + + [ + Error( + Lex( + Incomplete( + Ident, + Pragma, + Whitespace, + Span { + lo: 1, + hi: 2, + }, + ), + ), + ), + ]"#]], + ); +} + +#[test] +fn legacy_pragma_decl_missing_ident() { + check( + parse, + "#pragma ", + &expect![[r#" + Stmt [0-8] + StmtKind: Pragma [0-8]: (a, )"#]], + ); +} 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..c2d5e4337c --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/quantum_decl.rs @@ -0,0 +1,102 @@ +// 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] + StmtKind: QubitDeclaration [0-8]: Ident [6-7] "q""#]], + ); +} + +#[test] +fn annotated_quantum_decl() { + check( + parse, + r#" + @a.b.c 123 + qubit q;"#, + &expect![[r#" + Stmt [9-36] + Annotation [9-19]: (a.b.c, 123) + StmtKind: QubitDeclaration [28-36]: Ident [34-35] "q""#]], + ); +} + +#[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] + Annotation [9-57]: (g.h, dolor sit amet, consectetur adipiscing elit) + Annotation [66-72]: (d.e.f) + Annotation [81-91]: (a.b.c, 123) + StmtKind: QubitDeclaration [100-108]: Ident [106-107] "q""#]], + ); +} + +#[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] + StmtKind: QubitDeclaration [0-16]: Ident [9-15] "qubits", ExprStmt [5-8]: 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, + }, + ), + ) + "#]], + ); +} From 17495f78f92b9e9160753e7d4480e636046ab90f Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Mon, 10 Feb 2025 17:10:43 -0800 Subject: [PATCH 05/98] Fix panic in qasm3 lexer found by the fuzzer (#2170) Fixes #2168 --- compiler/qsc_qasm3/src/lex/raw.rs | 10 ++++++--- compiler/qsc_qasm3/src/lex/raw/tests.rs | 28 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/compiler/qsc_qasm3/src/lex/raw.rs b/compiler/qsc_qasm3/src/lex/raw.rs index 7b36bad167..4e0be3285d 100644 --- a/compiler/qsc_qasm3/src/lex/raw.rs +++ b/compiler/qsc_qasm3/src/lex/raw.rs @@ -373,9 +373,13 @@ impl<'a> Lexer<'a> { Err(NumberLexError::Incomplete) => unreachable!(), } } - Some('e') => match self.exp() { + Some('e' | 'E') => match self.exp() { Ok(()) => Ok(Number::Float), - Err(_) => todo!(), + 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), } @@ -415,7 +419,7 @@ impl<'a> Lexer<'a> { self.chars.next(); self.mid_dot(c1) } - Some('e') => match self.exp() { + Some('e' | 'E') => match self.exp() { Ok(()) => Ok(Number::Float), Err(NumberLexError::None) => unreachable!(), Err(_) => Err(NumberLexError::EndsInUnderscore), diff --git a/compiler/qsc_qasm3/src/lex/raw/tests.rs b/compiler/qsc_qasm3/src/lex/raw/tests.rs index 3850b06485..af9f480aff 100644 --- a/compiler/qsc_qasm3/src/lex/raw/tests.rs +++ b/compiler/qsc_qasm3/src/lex/raw/tests.rs @@ -798,6 +798,34 @@ fn incomplete_exp() { ); } +#[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( From c4343d0711a0b2d30571e8cb90f3229b650dc4f2 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 10 Feb 2025 17:32:51 -0800 Subject: [PATCH 06/98] Add fuzzing for qasm3 (#2167) --- .github/workflows/fuzz.yml | 38 ++++++++++--------- fuzz/Cargo.toml | 10 ++++- fuzz/fuzz_targets/qasm3.rs | 27 +++++++++++++ fuzz/fuzz_targets/{compile.rs => qsharp.rs} | 0 fuzz/seed_inputs/compile/list.txt | 1 - fuzz/seed_inputs/qasm3/input.qasm | 8 ++++ fuzz/seed_inputs/qasm3/list.txt | 1 + fuzz/seed_inputs/{compile => qsharp}/input.qs | 0 fuzz/seed_inputs/qsharp/list.txt | 1 + 9 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 fuzz/fuzz_targets/qasm3.rs rename fuzz/fuzz_targets/{compile.rs => qsharp.rs} (100%) delete mode 100644 fuzz/seed_inputs/compile/list.txt create mode 100644 fuzz/seed_inputs/qasm3/input.qasm create mode 100644 fuzz/seed_inputs/qasm3/list.txt rename fuzz/seed_inputs/{compile => qsharp}/input.qs (100%) create mode 100644 fuzz/seed_inputs/qsharp/list.txt 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/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 From 78885b754d5d5a9391d9f24764166955c3efac98 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Tue, 11 Feb 2025 13:13:05 -0800 Subject: [PATCH 07/98] Implement Pratt Parsing for Qasm3 parser (#2166) --- compiler/qsc_qasm3/src/ast.rs | 186 +++--- compiler/qsc_qasm3/src/keyword.rs | 3 - .../src/parser/completion/word_kinds.rs | 1 - compiler/qsc_qasm3/src/parser/expr.rs | 338 ++++++++++- compiler/qsc_qasm3/src/parser/expr/tests.rs | 553 ++++++++++++++++++ compiler/qsc_qasm3/src/parser/stmt.rs | 6 +- 6 files changed, 952 insertions(+), 135 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index 7e2314e4e7..0fa41bc626 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -204,7 +204,7 @@ impl Display for MeasureExpr { } } /// A binary operator. -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub enum BinOp { /// Addition: `+`. Add, @@ -273,8 +273,8 @@ impl Display for BinOp { } /// A unary operator. -#[derive(Clone, Debug)] -pub enum UnOp { +#[derive(Clone, Copy, Debug)] +pub enum UnaryOp { /// Negation: `-`. Neg, /// Bitwise NOT: `~`. @@ -283,12 +283,12 @@ pub enum UnOp { NotL, } -impl Display for UnOp { +impl Display for UnaryOp { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - UnOp::Neg => write!(f, "Neg"), - UnOp::NotB => write!(f, "NotB"), - UnOp::NotL => write!(f, "NotL"), + UnaryOp::Neg => write!(f, "Neg"), + UnaryOp::NotB => write!(f, "NotB"), + UnaryOp::NotL => write!(f, "NotL"), } } } @@ -637,7 +637,7 @@ impl Display for AliasStmt { #[derive(Clone, Debug)] pub struct ExprStmt { pub span: Span, - pub expr: Box, + pub expr: Expr, } impl Display for ExprStmt { @@ -667,7 +667,7 @@ impl Display for Expr { #[derive(Clone, Debug)] pub struct DiscreteSet { pub span: Span, - pub values: List, + pub values: Box<[Expr]>, } impl Display for DiscreteSet { @@ -685,9 +685,9 @@ impl Display for DiscreteSet { #[derive(Clone, Debug)] pub struct RangeDefinition { pub span: Span, - pub start: Option, - pub end: Option, - pub step: Option, + pub start: Option, + pub end: Option, + pub step: Option, } #[derive(Clone, Debug)] @@ -1584,17 +1584,20 @@ impl Display for ClassicalAssignment { #[derive(Clone, Debug, Default)] pub enum ExprKind { + Assign(AssignExpr), + AssignOp(AssignOpExpr), /// An expression with invalid syntax that can't be parsed. #[default] Err, Ident(Ident), - UnaryExpr(UnaryExpr), - BinaryExpr(BinaryExpr), + UnaryOp(UnaryOpExpr), + BinaryOp(BinaryOpExpr), Lit(Lit), FunctionCall(FunctionCall), Cast(Cast), Concatenation(Concatenation), IndexExpr(IndexExpr), + Paren(Expr), } impl Display for ExprKind { @@ -1603,25 +1606,54 @@ impl Display for ExprKind { match self { ExprKind::Err => write!(f, "Err"), ExprKind::Ident(id) => write!(f, "{id}"), - ExprKind::UnaryExpr(expr) => write!(f, "{expr}"), - ExprKind::BinaryExpr(expr) => display_bin_op(indent, expr), + ExprKind::UnaryOp(expr) => write!(f, "{expr}"), + ExprKind::BinaryOp(expr) => display_bin_op(indent, expr), ExprKind::Lit(lit) => write!(f, "{lit}"), ExprKind::FunctionCall(call) => write!(f, "{call}"), - ExprKind::Cast(cast) => write!(f, "{cast}"), + ExprKind::Cast(cast) => display_cast(indent, cast), ExprKind::Concatenation(concat) => write!(f, "{concat}"), ExprKind::IndexExpr(index) => write!(f, "{index}"), + ExprKind::Assign(expr) => write!(f, "{expr}"), + ExprKind::AssignOp(expr) => write!(f, "{expr}"), + ExprKind::Paren(expr) => display_paren(indent, expr), } } } #[derive(Clone, Debug)] -pub struct UnaryExpr { - pub span: Span, +pub struct AssignExpr { + pub lhs: Expr, + pub rhs: Expr, +} + +impl Display for AssignExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let indent = set_indentation(indented(f), 0); + display_assign(indent, &self.lhs, &self.rhs) + } +} + +#[derive(Clone, Debug)] +pub struct AssignOpExpr { + pub op: BinOp, + pub lhs: Expr, + pub rhs: Expr, +} + +impl Display for AssignOpExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let indent = set_indentation(indented(f), 0); + display_assign_op(indent, self.op, &self.lhs, &self.rhs) + } +} + +#[derive(Clone, Debug)] +pub struct UnaryOpExpr { pub op: UnaryOp, - pub expr: Box, + pub expr: Expr, } -impl Display for UnaryExpr { +impl Display for UnaryOpExpr { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let indent = set_indentation(indented(f), 0); display_un_op(indent, self.op, &self.expr) @@ -1629,14 +1661,13 @@ impl Display for UnaryExpr { } #[derive(Clone, Debug)] -pub struct BinaryExpr { - pub span: Span, - pub op: BinaryOp, - pub lhs: ExprStmt, - pub rhs: ExprStmt, +pub struct BinaryOpExpr { + pub op: BinOp, + pub lhs: Expr, + pub rhs: Expr, } -impl Display for BinaryExpr { +impl Display for BinaryOpExpr { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let indent = set_indentation(indented(f), 0); display_bin_op(indent, self) @@ -1647,7 +1678,7 @@ impl Display for BinaryExpr { pub struct FunctionCall { pub span: Span, pub name: Identifier, - pub args: List, + pub args: List, } impl Display for FunctionCall { @@ -1665,8 +1696,8 @@ impl Display for FunctionCall { #[derive(Clone, Debug)] pub struct Cast { pub span: Span, - pub r#type: ScalarType, - pub arg: ExprStmt, + pub r#type: TypeDef, + pub arg: Expr, } impl Display for Cast { @@ -1678,7 +1709,7 @@ impl Display for Cast { #[derive(Clone, Debug)] pub struct IndexExpr { pub span: Span, - pub collection: ExprStmt, + pub collection: Expr, pub index: IndexElement, } @@ -1692,65 +1723,6 @@ impl Display for IndexExpr { } } -#[derive(Clone, Copy, Debug)] -pub enum UnaryOp { - NegB, - NegL, - NegN, -} - -impl Display for UnaryOp { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - UnaryOp::NegB => write!(f, "NegB"), - UnaryOp::NegL => write!(f, "NegL"), - UnaryOp::NegN => write!(f, "NegN"), - } - } -} - -#[derive(Clone, Copy, Debug)] -pub enum BinaryOp { - /// `>` - Gt, - /// `<` - Lt, - /// `>=` - Gte, - /// `<=` - Lte, - /// `==` - Eq, - /// `!=` - Neq, - /// `&&` - AndL, - /// `||` - OrL, - /// `|` - OrB, - /// `^` - XorB, - /// `&` - AndB, - /// `<<` - ShL, - /// `>>` - ShR, - /// `+` - Add, - /// `-` - Sub, - /// `*` - Mul, - /// `/` - Div, - /// `%` - Mod, - /// `**` - Exp, -} - #[derive(Clone, Debug)] pub struct Lit { pub span: Span, @@ -1860,10 +1832,19 @@ impl Display for IndexElement { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub enum IndexSetItem { RangeDefinition(RangeDefinition), - Expr(ExprStmt), + 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 { + self + } } impl Display for IndexSetItem { @@ -1872,13 +1853,14 @@ impl Display for IndexSetItem { match self { IndexSetItem::RangeDefinition(range) => display_range(indent, range), IndexSetItem::Expr(expr) => write!(f, "IndexSetItem {expr}"), + IndexSetItem::Err => write!(f, "Err"), } } } #[derive(Clone, Debug)] pub enum AssignmentOp { - BinaryOp(BinaryOp), + BinaryOp(BinOp), /// `OpenQASM3` has the `~=` assignment operator. /// This enum variant is meant to capture that. UnaryOp(UnaryOp), @@ -1986,7 +1968,7 @@ fn display_assign(mut indent: Indented, lhs: &Expr, rhs: &Expr) -> fm fn display_assign_op( mut indent: Indented, - op: BinaryOp, + op: BinOp, lhs: &Expr, rhs: &Expr, ) -> fmt::Result { @@ -1997,7 +1979,7 @@ fn display_assign_op( Ok(()) } -fn display_bin_op(mut indent: Indented, expr: &BinaryExpr) -> fmt::Result { +fn display_bin_op(mut indent: Indented, expr: &BinaryOpExpr) -> fmt::Result { write!(indent, "BinOp ({:?}):", expr.op)?; indent = set_indentation(indent, 1); write!(indent, "\n{}", expr.lhs)?; @@ -2012,6 +1994,20 @@ fn display_un_op(mut indent: Indented, op: UnaryOp, expr: &Expr) -> f Ok(()) } +fn display_paren(mut indent: Indented, expr: &Expr) -> fmt::Result { + write!(indent, "Paren:")?; + indent = set_indentation(indent, 1); + write!(indent, "\n{expr}")?; + Ok(()) +} +fn display_cast(mut indent: Indented, cast: &Cast) -> fmt::Result { + let Cast { span, r#type, arg } = cast; + write!(indent, "Cast {span}:")?; + indent = set_indentation(indent, 1); + write!(indent, "\n{type}\n{arg}")?; + Ok(()) +} + fn display_while(mut indent: Indented, cond: &Expr, block: &Block) -> fmt::Result { write!(indent, "While:")?; indent = set_indentation(indent, 1); diff --git a/compiler/qsc_qasm3/src/keyword.rs b/compiler/qsc_qasm3/src/keyword.rs index 86f520b5c3..2a2a07e8e3 100644 --- a/compiler/qsc_qasm3/src/keyword.rs +++ b/compiler/qsc_qasm3/src/keyword.rs @@ -9,7 +9,6 @@ use std::{ #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Sequence)] pub enum Keyword { - Array, Barrier, Box, Break, @@ -58,7 +57,6 @@ pub enum Keyword { impl Keyword { pub(super) fn as_str(self) -> &'static str { match self { - Keyword::Array => "array", Keyword::Barrier => "barrier", Keyword::Box => "box", Keyword::Break => "break", @@ -120,7 +118,6 @@ impl FromStr for Keyword { // frequency in Q# so that fewer comparisons are needed on average. fn from_str(s: &str) -> Result { match s { - "array" => Ok(Self::Array), "barrier" => Ok(Self::Barrier), "box" => Ok(Self::Box), "break" => Ok(Self::Break), diff --git a/compiler/qsc_qasm3/src/parser/completion/word_kinds.rs b/compiler/qsc_qasm3/src/parser/completion/word_kinds.rs index 44b441338a..f300e4d26c 100644 --- a/compiler/qsc_qasm3/src/parser/completion/word_kinds.rs +++ b/compiler/qsc_qasm3/src/parser/completion/word_kinds.rs @@ -62,7 +62,6 @@ bitflags! { // Begin keywords. // - const Array = keyword_bit(Keyword::Array); const Barrier = keyword_bit(Keyword::Barrier); const Box = keyword_bit(Keyword::Box); const Break = keyword_bit(Keyword::Break); diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 7bc73665a6..7486d20083 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -15,9 +15,16 @@ use num_traits::Num; use qsc_data_structures::span::Span; use crate::{ - ast::{BinOp, Expr, ExprKind, ExprStmt, Lit, LiteralKind, UnOp, ValueExpression, Version}, + ast::{ + self, AssignExpr, AssignOpExpr, BinOp, BinaryOpExpr, Cast, DiscreteSet, Expr, ExprKind, + ExprStmt, FunctionCall, IndexElement, IndexExpr, IndexSetItem, Lit, LiteralKind, + RangeDefinition, TypeDef, UnaryOp, ValueExpression, Version, + }, keyword::Keyword, - lex::{cooked::Literal, ClosedBinOp, Delim, Radix, Token, TokenKind}, + lex::{ + cooked::{ComparisonOp, Literal}, + ClosedBinOp, Delim, Radix, Token, TokenKind, + }, parser::{ completion::WordKinds, prim::{shorten, token}, @@ -27,26 +34,31 @@ use crate::{ use crate::parser::Result; -use super::error::{Error, ErrorKind}; +use super::{ + error::{Error, ErrorKind}, + prim::{ident, opt, seq, FinalSep}, + stmt::scalar_or_array_type, +}; struct PrefixOp { - kind: UnOp, + kind: UnaryOp, precedence: u8, } -struct MixfixOp { +struct InfixOp { kind: OpKind, precedence: u8, } enum OpKind { - Postfix(UnOp), - Binary(BinOp, Assoc), Assign, - AssignUpdate, AssignBinary(BinOp), + Binary(BinOp, Assoc), + Funcall, + Index, } +// TODO: This seems to be an unnecessary wrapper. Consider removing. #[derive(Clone, Copy)] enum OpName { Token(TokenKind), @@ -67,35 +79,117 @@ enum Assoc { const RANGE_PRECEDENCE: u8 = 1; -pub(super) fn expr(s: &mut ParserContext) -> Result> { +pub(super) fn expr(s: &mut ParserContext) -> Result { expr_op(s, OpContext::Precedence(0)) } -pub(super) fn expr_stmt(s: &mut ParserContext) -> Result> { +pub(super) fn expr_stmt(s: &mut ParserContext) -> Result { expr_op(s, OpContext::Stmt) } -fn expr_op(s: &mut ParserContext, _context: OpContext) -> Result> { - let lhs = expr_base(s)?; +fn expr_op(s: &mut ParserContext, context: OpContext) -> Result { + let lo = s.peek().span.lo; + + let mut 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(ast::UnaryOpExpr { + op: op.kind, + expr: rhs, + })), + } + } else { + expr_base(s)? + }; + + let min_precedence = match context { + OpContext::Precedence(p) => p, + OpContext::Stmt => 0, + }; + + while let Some(op) = infix_op(op_name(s)) { + if op.precedence < min_precedence { + break; + } + + s.advance(); + let kind = match op.kind { + OpKind::Assign => { + let rhs = expr_op(s, OpContext::Precedence(op.precedence))?; + Box::new(ExprKind::Assign(AssignExpr { lhs, rhs })) + } + OpKind::AssignBinary(kind) => { + let rhs = expr_op(s, OpContext::Precedence(op.precedence))?; + Box::new(ExprKind::AssignOp(AssignOpExpr { op: kind, lhs, rhs })) + } + 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> { +fn expr_base(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; - let kind = if let Some(l) = lit(s)? { - Ok(Box::new(ExprKind::Lit(l))) + 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 { - Err(Error::new(ErrorKind::Rule( - "expression", - s.peek().kind, - s.peek().span, - ))) - }?; - - Ok(Box::new(Expr { - span: s.span(lo), - kind, - })) + 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. + token(s, TokenKind::Open(Delim::Paren))?; + let arg = paren_expr(s, lo)?; + Ok(Expr { + span: s.span(lo), + kind: Box::new(ExprKind::Cast(Cast { + span: s.span(lo), + r#type, + arg, + })), + }) + } + 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> { @@ -315,18 +409,147 @@ fn lit_bigint(lexeme: &str, radix: u32) -> Option { } } +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: ast::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: ast::Identifier::Ident(Box::new(ident)), + args: args.into_iter().map(Box::new).collect(), + })) +} + +fn cast_op(s: &mut ParserContext, r#type: TypeDef) -> Result { + let lo = match &r#type { + TypeDef::Scalar(ident) => ident.span.lo, + TypeDef::Array(array) => array.span.lo, + TypeDef::ArrayReference(array) => array.span.lo, + }; + let arg = paren_expr(s, lo)?; + token(s, TokenKind::Close(Delim::Paren))?; + Ok(ExprKind::Cast(Cast { + span: s.span(lo), + r#type, + arg, + })) +} + +fn index_expr(s: &mut ParserContext, lhs: Expr) -> Result { + let lo = s.span(0).hi - 1; + let index = index_element(s)?; + 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 (exprs, _) = seq(s, index_set_item)?; + let exprs = exprs + .into_iter() + .map(Box::new) + .collect::>() + .into_boxed_slice(); + IndexElement::IndexSet(exprs) + } + }; + Ok(index) +} + +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)); + } + + let end = opt(s, expr)?; + let step = opt(s, |s| { + token(s, TokenKind::Colon)?; + expr(s) + })?; + + Ok(IndexSetItem::RangeDefinition(RangeDefinition { + span: s.span(lo), + start, + end, + step, + })) +} + +fn set_expr(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Open(Delim::Brace))?; + let (exprs, _) = seq(s, expr)?; + token(s, TokenKind::Close(Delim::Brace))?; + Ok(DiscreteSet { + span: s.span(lo), + values: exprs.into_boxed_slice(), + }) +} + +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: UnOp::NotL, + kind: UnaryOp::NotL, precedence: 11, }), OpName::Token(TokenKind::Tilde) => Some(PrefixOp { - kind: UnOp::NotB, + kind: UnaryOp::NotB, precedence: 11, }), OpName::Token(TokenKind::ClosedBinOp(ClosedBinOp::Minus)) => Some(PrefixOp { - kind: UnOp::Neg, + kind: UnaryOp::Neg, precedence: 11, }), @@ -334,6 +557,59 @@ fn prefix_op(name: OpName) -> Option { } } +/// 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, + } +} + fn closed_bin_op(op: ClosedBinOp) -> BinOp { match op { ClosedBinOp::Amp => BinOp::AndB, @@ -397,6 +673,6 @@ pub(super) fn value_expr(s: &mut ParserContext) -> Result> Ok(Box::new(ValueExpression::Expr(stmt))) } -pub(crate) fn expr_list(_s: &mut ParserContext<'_>) -> Result> { - todo!("expr_list") +pub(crate) fn expr_list(s: &mut ParserContext<'_>) -> Result> { + seq(s, expr).map(|pair| pair.0) } diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index 3fe0d6d034..0569e32374 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -615,3 +615,556 @@ fn lit_float_imag_underscore() { fn lit_float_imag_leading_zero() { check(expr, "0.23im", &expect!["Expr [0-6]: Lit: Imaginary(0.23)"]); } + +#[test] +fn pratt_parsing_mul_add() { + check( + expr, + "1 + 2 * 3", + &expect![[r#" + Expr [0-9]: BinOp (Add): + Expr [0-1]: Lit: Int(1) + Expr [4-9]: BinOp (Mul): + Expr [4-5]: Lit: Int(2) + Expr [8-9]: Lit: Int(3)"#]], + ); +} + +#[test] +fn pratt_parsing_parens() { + check( + expr, + "(1 + 2) * 3", + &expect![[r#" + Expr [0-11]: BinOp (Mul): + Expr [0-7]: Paren: + Expr [1-6]: BinOp (Add): + Expr [1-2]: Lit: Int(1) + Expr [5-6]: Lit: Int(2) + Expr [10-11]: Lit: Int(3)"#]], + ); +} + +#[test] +fn prat_parsing_mul_unary() { + check( + expr, + "2 * -3", + &expect![[r#" + Expr [0-6]: BinOp (Mul): + Expr [0-1]: Lit: Int(2) + Expr [4-6]: UnOp (Neg): + Expr [5-6]: Lit: Int(3)"#]], + ); +} + +#[test] +fn prat_parsing_unary_mul() { + check( + expr, + "-2 * 3", + &expect![[r#" + Expr [0-6]: BinOp (Mul): + Expr [0-2]: UnOp (Neg): + Expr [1-2]: Lit: Int(2) + Expr [5-6]: Lit: Int(3)"#]], + ); +} + +#[test] +fn prat_parsing_exp_funcall() { + check( + expr, + "2 ** square(3)", + &expect![[r#" + Expr [0-14]: BinOp (Exp): + Expr [0-1]: Lit: Int(2) + Expr [5-14]: FunctionCall [5-14]: Ident [5-11] "square" + Expr [12-13]: Lit: Int(3)"#]], + ); +} + +#[test] +fn prat_parsing_funcall_exp() { + check( + expr, + "square(2) ** 3", + &expect![[r#" + Expr [0-14]: BinOp (Exp): + Expr [0-9]: FunctionCall [0-9]: Ident [0-6] "square" + Expr [7-8]: Lit: Int(2) + 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]: Ident [0-6] "square" + Expr [7-13]: BinOp (Exp): + Expr [7-8]: Lit: Int(2) + Expr [12-13]: Lit: Int(3)"#]], + ); +} + +#[test] +fn funcall() { + check( + expr, + "square(2)", + &expect![[r#" + Expr [0-9]: FunctionCall [0-9]: Ident [0-6] "square" + Expr [7-8]: Lit: Int(2)"#]], + ); +} + +#[test] +fn funcall_multiple_args() { + check( + expr, + "square(2, 3)", + &expect![[r#" + Expr [0-12]: FunctionCall [0-12]: Ident [0-6] "square" + 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]: Ident [0-6] "square" + 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]: + ClassicalType [0-3]: BitType + Expr [0-6]: Paren: + 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]: + ClassicalType [0-6]: BitType [0-6]: ExprStmt [3-6]: Expr [4-5]: Lit: Int(4) + Expr [0-9]: Paren: + Expr [7-8]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_int() { + check( + expr, + "int(0)", + &expect![[r#" + Expr [0-6]: Cast [0-6]: + ClassicalType [0-3]: IntType [0-3] + Expr [0-6]: Paren: + 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]: + ClassicalType [0-7]: IntType[ExprStmt [3-7]: Expr [4-6]: Lit: Int(64)]: [0-7] + Expr [0-10]: Paren: + Expr [8-9]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_uint() { + check( + expr, + "uint(0)", + &expect![[r#" + Expr [0-7]: Cast [0-7]: + ClassicalType [0-4]: UIntType [0-4] + Expr [0-7]: Paren: + 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]: + ClassicalType [0-8]: UIntType[ExprStmt [4-8]: Expr [5-7]: Lit: Int(64)]: [0-8] + Expr [0-11]: Paren: + Expr [9-10]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_float() { + check( + expr, + "float(0)", + &expect![[r#" + Expr [0-8]: Cast [0-8]: + ClassicalType [0-5]: FloatType [0-5] + Expr [0-8]: Paren: + 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]: + ClassicalType [0-9]: FloatType[ExprStmt [5-9]: Expr [6-8]: Lit: Int(64)]: [0-9] + Expr [0-12]: Paren: + Expr [10-11]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_complex() { + check( + expr, + "complex[float](0)", + &expect![[r#" + Expr [0-17]: Cast [0-17]: + ClassicalType [0-14]: ComplexType[float[FloatType [8-13]]]: [0-14] + Expr [0-17]: Paren: + 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]: + ClassicalType [0-18]: ComplexType[float[FloatType[ExprStmt [13-17]: Expr [14-16]: Lit: Int(64)]: [8-17]]]: [0-18] + Expr [0-21]: Paren: + Expr [19-20]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_bool() { + check( + expr, + "bool(0)", + &expect![[r#" + Expr [0-7]: Cast [0-7]: + ClassicalType [0-4]: BoolType + Expr [0-7]: Paren: + Expr [5-6]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_duration() { + check( + expr, + "duration(0)", + &expect![[r#" + Expr [0-11]: Cast [0-11]: + ClassicalType [0-8]: Duration + Expr [0-11]: Paren: + Expr [9-10]: Lit: Int(0)"#]], + ); +} + +#[test] +fn cast_to_stretch() { + check( + expr, + "stretch(0)", + &expect![[r#" + Expr [0-10]: Cast [0-10]: + ClassicalType [0-7]: Stretch + Expr [0-10]: Paren: + 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]: + ArrayType [0-17]: ArrayBaseTypeKind IntType[ExprStmt [9-13]: Expr [10-12]: Lit: Int(64)]: [6-13] + Expr [15-16]: Lit: Int(4) + Expr [0-20]: Paren: + 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]: + ArrayType [0-18]: ArrayBaseTypeKind UIntType[ExprStmt [10-14]: Expr [11-13]: Lit: Int(64)]: [6-14] + Expr [16-17]: Lit: Int(4) + Expr [0-21]: Paren: + 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]: + ArrayType [0-19]: ArrayBaseTypeKind FloatType[ExprStmt [11-15]: Expr [12-14]: Lit: Int(64)]: [6-15] + Expr [17-18]: Lit: Int(4) + Expr [0-22]: Paren: + 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]: + ArrayType [0-19]: ArrayBaseTypeKind AngleType [6-15]: ExprStmt [11-15]: Expr [12-14]: Lit: Int(64) + Expr [17-18]: Lit: Int(4) + Expr [0-22]: Paren: + 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]: + ArrayType [0-14]: ArrayBaseTypeKind BoolType + Expr [12-13]: Lit: Int(4) + Expr [0-17]: Paren: + 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]: + ArrayType [0-18]: ArrayBaseTypeKind DurationType + Expr [16-17]: Lit: Int(4) + Expr [0-21]: Paren: + 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]: + ArrayType [0-28]: ArrayBaseTypeKind ComplexType[float[FloatType[ExprStmt [19-23]: Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24] + Expr [26-27]: Lit: Int(4) + Expr [0-31]: Paren: + Expr [29-30]: Lit: Int(0)"#]], + ); +} + +#[test] +fn index_expr() { + check( + expr, + "foo[1]", + &expect![[r#" + Expr [0-6]: IndexExpr [3-6]: Expr [0-3]: Ident [0-3] "foo", IndexElement: + IndexSetItem Expr [4-5]: Lit: Int(1)"#]], + ); +} + +#[test] +fn index_set() { + check( + expr, + "foo[{1, 4, 5}]", + &expect![[r#" + Expr [0-14]: IndexExpr [3-14]: Expr [0-3]: Ident [0-3] "foo", IndexElement DiscreteSet [4-13]: + 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 [3-18]: Expr [0-3]: Ident [0-3] "foo", IndexElement: + Range: [4-7] + Expr [4-5]: Lit: Int(1) + + Expr [6-7]: Lit: Int(5) + Range: [9-12] + Expr [9-10]: Lit: Int(3) + + Expr [11-12]: Lit: Int(7) + Range: [14-17] + Expr [14-15]: Lit: Int(4) + + Expr [16-17]: Lit: Int(8)"#]], + ); +} + +#[test] +fn index_range() { + check( + expr, + "foo[1:5:2]", + &expect![[r#" + Expr [0-10]: IndexExpr [3-10]: Expr [0-3]: Ident [0-3] "foo", IndexElement: + Range: [4-9] + Expr [4-5]: Lit: Int(1) + Expr [8-9]: Lit: Int(2) + Expr [6-7]: Lit: Int(5)"#]], + ); +} + +#[test] +fn index_full_range() { + check( + expr, + "foo[:]", + &expect![[r#" + Expr [0-6]: IndexExpr [3-6]: Expr [0-3]: Ident [0-3] "foo", IndexElement: + Range: [4-5] + + + "#]], + ); +} + +#[test] +fn index_range_start() { + check( + expr, + "foo[1:]", + &expect![[r#" + Expr [0-7]: IndexExpr [3-7]: Expr [0-3]: Ident [0-3] "foo", IndexElement: + Range: [4-6] + Expr [4-5]: Lit: Int(1) + + "#]], + ); +} + +#[test] +fn index_range_end() { + check( + expr, + "foo[:5]", + &expect![[r#" + Expr [0-7]: IndexExpr [3-7]: Expr [0-3]: Ident [0-3] "foo", IndexElement: + Range: [4-6] + + + Expr [5-6]: Lit: Int(5)"#]], + ); +} + +#[test] +fn index_range_step() { + check( + expr, + "foo[::2]", + &expect![[r#" + Expr [0-8]: IndexExpr [3-8]: Expr [0-3]: Ident [0-3] "foo", IndexElement: + Range: [4-7] + + Expr [6-7]: Lit: Int(2) + "#]], + ); +} + +#[test] +fn set_expr() { + check( + super::set_expr, + "{2, 3, 4}", + &expect![[r#" + DiscreteSet [0-9]: + Expr [1-2]: Lit: Int(2) + Expr [4-5]: Lit: Int(3) + Expr [7-8]: Lit: Int(4)"#]], + ); +} + +#[test] +fn assignment_and_unop() { + check( + crate::parser::stmt::parse, + "bool c = a && !b;", + &expect![[r#" + Stmt [0-17] + StmtKind: ClassicalDeclarationStmt [0-17]: ClassicalType [0-4]: BoolType, Ident [5-6] "c", ValueExpression ExprStmt [9-16]: Expr [9-16]: BinOp (AndL): + Expr [9-10]: Ident [9-10] "a" + Expr [14-16]: UnOp (NotL): + Expr [15-16]: Ident [15-16] "b""#]], + ); +} + +#[test] +fn assignment_unop_and() { + check( + crate::parser::stmt::parse, + "bool d = !a && b;", + &expect![[r#" + Stmt [0-17] + StmtKind: ClassicalDeclarationStmt [0-17]: ClassicalType [0-4]: BoolType, Ident [5-6] "d", ValueExpression ExprStmt [9-16]: Expr [9-16]: BinOp (AndL): + Expr [9-11]: UnOp (NotL): + Expr [10-11]: Ident [10-11] "a" + Expr [15-16]: Ident [15-16] "b""#]], + ); +} diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 1de4ac2533..54de2376f3 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -276,7 +276,7 @@ fn parse_io_decl(s: &mut ParserContext) -> Result { Ok(StmtKind::IODeclaration(decl)) } -fn scalar_or_array_type(s: &mut ParserContext) -> Result { +pub fn scalar_or_array_type(s: &mut ParserContext) -> Result { if let Ok(v) = scalar_type(s) { return Ok(TypeDef::Scalar(v)); } @@ -444,7 +444,6 @@ fn scalar_int_type(s: &mut ParserContext) -> Result { } fn array_int_type(s: &mut ParserContext) -> Result { - token(s, TokenKind::Type(Type::Int))?; let ty = int_type(s)?; Ok(ArrayBaseTypeKind::Int(ty)) } @@ -468,7 +467,6 @@ fn scalar_uint_type(s: &mut ParserContext) -> Result { } fn array_uint_type(s: &mut ParserContext) -> Result { - token(s, TokenKind::Type(Type::UInt))?; let ty = uint_type(s)?; Ok(ArrayBaseTypeKind::UInt(ty)) } @@ -493,7 +491,6 @@ fn scalar_float_type(s: &mut ParserContext) -> Result { } fn array_float_type(s: &mut ParserContext) -> Result { - token(s, TokenKind::Type(Type::Float))?; let ty = float_type(s)?; Ok(ArrayBaseTypeKind::Float(ty)) } @@ -518,7 +515,6 @@ fn scalar_angle_type(s: &mut ParserContext) -> Result { } fn array_angle_type(s: &mut ParserContext) -> Result { - token(s, TokenKind::Type(Type::Angle))?; let ty = angle_type(s)?; Ok(ArrayBaseTypeKind::Angle(ty)) } From 4ea324d397449aa43f67e684adbc04a582d4d932 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:36:25 -0800 Subject: [PATCH 08/98] Parse qasm3 switch statements (#2178) --- compiler/qsc_qasm3/src/ast.rs | 56 +++++- compiler/qsc_qasm3/src/lex/cooked.rs | 49 +++--- compiler/qsc_qasm3/src/lex/cooked/tests.rs | 44 ++++- .../qsc_qasm3/src/parser/completion/tests.rs | 4 +- compiler/qsc_qasm3/src/parser/error.rs | 8 + compiler/qsc_qasm3/src/parser/expr.rs | 2 +- compiler/qsc_qasm3/src/parser/stmt.rs | 65 ++++++- compiler/qsc_qasm3/src/parser/stmt/tests.rs | 1 + .../qsc_qasm3/src/parser/stmt/tests/pragma.rs | 2 +- .../src/parser/stmt/tests/switch_stmt.rs | 165 ++++++++++++++++++ 10 files changed, 359 insertions(+), 37 deletions(-) create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index 0fa41bc626..9a16e08a0e 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -21,6 +21,7 @@ fn set_indentation<'a, 'b>( 0 => indent.with_str(""), 1 => indent.with_str(" "), 2 => indent.with_str(" "), + 3 => indent.with_str(" "), _ => unimplemented!("indentation level not supported"), } } @@ -28,7 +29,11 @@ fn set_indentation<'a, 'b>( // TODO: profile this with iai-callgrind in a large OpenQASM3 // sample to verify that is actually faster than using Vec. /// An alternative to `Vec` that uses less stack space. -type List = Box<[Box]>; +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 { @@ -1551,20 +1556,59 @@ impl Display for EnumerableSet { #[derive(Clone, Debug)] pub struct SwitchStmt { pub span: Span, - pub target: ExprStmt, - pub cases: List<(List, CompoundStmt)>, + pub target: Expr, + pub cases: List<(List, Block)>, /// 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, + pub default: Option, } impl Display for SwitchStmt { - fn fmt(&self, _f: &mut Formatter<'_>) -> fmt::Result { - todo!("SwitchStmt display"); + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "SwitchStmt {}:", self.span)?; + let mut indent = set_indentation(indented(f), 1); + write!(indent, "\nTarget: {}", self.target)?; + if self.cases.is_empty() { + write!(indent, "\n")?; + } else { + write!(indent, "\nCases:")?; + for elt in &self.cases { + let (labels, block) = &**elt; + indent = display_switch_case(indent, labels, block)?; + } + } + if let Some(default) = &self.default { + write!(indent, "\nDefault Case:")?; + indent = set_indentation(indent, 2); + write!(indent, "\n{default}")?; + } else { + write!(indent, "\n")?; + } + Ok(()) } } +fn display_switch_case<'a, 'b>( + mut indent: Indented<'a, Formatter<'b>>, + labels: &List, + block: &Block, +) -> Result>, core::fmt::Error> { + indent = set_indentation(indent, 2); + if labels.is_empty() { + write!(indent, "\n")?; + } else { + write!(indent, "\nLabels:")?; + indent = set_indentation(indent, 3); + for label in labels { + write!(indent, "\n{label}")?; + } + } + indent = set_indentation(indent, 2); + write!(indent, "\n{block}")?; + Ok(indent) +} + #[derive(Clone, Debug)] pub struct ClassicalAssignment { pub span: Span, diff --git a/compiler/qsc_qasm3/src/lex/cooked.rs b/compiler/qsc_qasm3/src/lex/cooked.rs index d4f83c2c94..8838cda2b7 100644 --- a/compiler/qsc_qasm3/src/lex/cooked.rs +++ b/compiler/qsc_qasm3/src/lex/cooked.rs @@ -509,11 +509,15 @@ impl<'a> Lexer<'a> { raw::TokenKind::Ident => { let ident = &self.input[(token.offset as usize)..(self.offset() as usize)]; let cooked_ident = Self::ident(ident); - if matches!(cooked_ident, TokenKind::Keyword(Keyword::Pragma)) { - self.eat_to_end_of_line(); - Ok(Some(TokenKind::Pragma)) - } else { - Ok(Some(cooked_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)), @@ -553,23 +557,26 @@ impl<'a> Lexer<'a> { } } raw::TokenKind::Single(Single::Sharp) => { - let complete = TokenKind::Pragma; - self.expect(raw::TokenKind::Ident, complete)?; + self.expect(raw::TokenKind::Ident, TokenKind::Identifier)?; let ident = &self.input[(token.offset as usize + 1)..(self.offset() as usize)]; - if matches!(Self::ident(ident), TokenKind::Keyword(Keyword::Pragma)) { - self.eat_to_end_of_line(); - Ok(Some(complete)) - } else { - let span = Span { - lo: token.offset, - hi: self.offset(), - }; - Err(Error::Incomplete( - raw::TokenKind::Ident, - complete, - raw::TokenKind::Ident, - span, - )) + 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), diff --git a/compiler/qsc_qasm3/src/lex/cooked/tests.rs b/compiler/qsc_qasm3/src/lex/cooked/tests.rs index 5b6df0c593..b025519177 100644 --- a/compiler/qsc_qasm3/src/lex/cooked/tests.rs +++ b/compiler/qsc_qasm3/src/lex/cooked/tests.rs @@ -1003,7 +1003,7 @@ fn unknown() { Err( Incomplete( Ident, - Pragma, + Identifier, Single( Sharp, ), @@ -1016,7 +1016,7 @@ fn unknown() { Err( IncompleteEof( Ident, - Pragma, + Identifier, Span { lo: 2, hi: 2, @@ -1187,3 +1187,43 @@ fn sharp_pragma_ident() { "#]], ); } + +#[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/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs index 127c972cd1..f15e41a7f2 100644 --- a/compiler/qsc_qasm3/src/parser/completion/tests.rs +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -36,7 +36,7 @@ fn begin_document() { "|OPENQASM 3;", &expect![[r#" WordKinds( - Annotation | Include | Input | OpenQASM | Output | Pragma | Qubit, + Annotation | Include | Input | OpenQASM | Output | Pragma | Qubit | Switch, ) "#]], ); @@ -48,7 +48,7 @@ fn end_of_version() { "OPENQASM 3;|", &expect![[r#" WordKinds( - Annotation | Include | Input | Output | Pragma | Qubit, + Annotation | Include | Input | Output | Pragma | Qubit | Switch, ) "#]], ); diff --git a/compiler/qsc_qasm3/src/parser/error.rs b/compiler/qsc_qasm3/src/parser/error.rs index 34ca512e21..a4853cc3b4 100644 --- a/compiler/qsc_qasm3/src/parser/error.rs +++ b/compiler/qsc_qasm3/src/parser/error.rs @@ -113,6 +113,12 @@ pub enum ErrorKind { #[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("expected an item or closing brace, found {0}")] #[diagnostic(code("Qasm3.Parse.ExpectedItem"))] ExpectedItem(TokenKind, #[label] Span), @@ -131,6 +137,8 @@ impl ErrorKind { 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::ExpectedItem(token, span) => Self::ExpectedItem(token, span + offset), } } diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 7486d20083..7940f11b52 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -409,7 +409,7 @@ fn lit_bigint(lexeme: &str, radix: u32) -> Option { } } -fn paren_expr(s: &mut ParserContext, lo: u32) -> Result { +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))?; diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 54de2376f3..34b5bf2a68 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -17,11 +17,13 @@ use super::{ }; use crate::{ ast::{ - AngleType, Annotation, ArrayBaseTypeKind, ArrayType, BitType, Block, - ClassicalDeclarationStmt, ComplexType, ConstantDeclaration, ExprStmt, FloatType, - IODeclaration, IOKeyword, IncludeStmt, IntType, LiteralKind, Pragma, QubitDeclaration, - ScalarType, ScalarTypeKind, Stmt, StmtKind, TypeDef, UIntType, + list_from_iter, AngleType, Annotation, ArrayBaseTypeKind, ArrayType, BitType, Block, + ClassicalDeclarationStmt, ComplexType, ConstantDeclaration, Expr, ExprStmt, FloatType, + IODeclaration, IOKeyword, IncludeStmt, IntType, List, LiteralKind, Pragma, + QubitDeclaration, ScalarType, ScalarTypeKind, Stmt, StmtKind, SwitchStmt, TypeDef, + UIntType, }, + keyword::Keyword, lex::{ cooked::{Literal, Type}, Delim, TokenKind, @@ -52,6 +54,8 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { Box::new(v) } else if let Some(decl) = opt(s, parse_local)? { Box::new(decl) + } else if let Some(switch) = opt(s, parse_switch_stmt)? { + Box::new(StmtKind::Switch(switch)) } else { return Err(Error::new(ErrorKind::Rule( "statement", @@ -598,3 +602,56 @@ pub(super) fn complex_subtype(s: &mut ParserContext) -> Result { 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 +/// . +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::paren_expr(s, lo)?; + + // 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, + }) +} + +fn case_stmt(s: &mut ParserContext) -> Result<(List, Block)> { + 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).map(|block| *block)?; + Ok((list_from_iter(controlling_label), block)) +} + +fn default_case_stmt(s: &mut ParserContext) -> Result { + token(s, TokenKind::Keyword(Keyword::Default))?; + parse_block(s).map(|block| *block) +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests.rs b/compiler/qsc_qasm3/src/parser/stmt/tests.rs index fed929c480..73372ce1d3 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests.rs @@ -6,3 +6,4 @@ mod classical_decl; mod io_decl; mod pragma; mod quantum_decl; +mod switch_stmt; diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/pragma.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/pragma.rs index 54c822619d..83319befae 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/pragma.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/pragma.rs @@ -89,7 +89,7 @@ fn legacy_pragma_ws_after_hash() { Lex( Incomplete( Ident, - Pragma, + Identifier, Whitespace, Span { lo: 1, 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..9968592f55 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs @@ -0,0 +1,165 @@ +use expect_test::expect; + +use crate::parser::{stmt::parse_switch_stmt, tests::check}; + +#[test] +fn simple_switch() { + check( + parse_switch_stmt, + " + switch (x) { + case 1 {} + default {} + } + ", + &expect![[r#" + SwitchStmt [9-72]: + Target: Expr [9-19]: Paren: + Expr [17-18]: Ident [17-18] "x" + Cases: + Labels: + Expr [37-38]: Lit: Int(1) + 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 [9-19]: Paren: + Expr [17-18]: Ident [17-18] "x" + + + + [ + 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 [9-19]: Paren: + Expr [17-18]: Ident [17-18] "x" + + 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 [9-19]: Paren: + Expr [17-18]: Ident [17-18] "x" + Cases: + Labels: + Expr [37-38]: Lit: Int(0) + Expr [40-41]: Lit: Int(1) + Block [42-44]: + "#]], + ); +} + +#[test] +fn case_with_no_labels() { + check( + parse_switch_stmt, + " + switch (x) { + case {} + } + ", + &expect![[r#" + SwitchStmt [9-49]: + Target: Expr [9-19]: Paren: + Expr [17-18]: Ident [17-18] "x" + Cases: + + Block [37-39]: + + + [ + 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 [9-19]: Paren: + Expr [17-18]: Ident [17-18] "x" + Cases: + Labels: + Expr [37-38]: Lit: Int(0) + Block [39-53]: + Stmt [41-51] + StmtKind: ClassicalDeclarationStmt [41-51]: ClassicalType [41-44]: IntType [41-44], Ident [45-46] "x", ValueExpression ExprStmt [49-50]: Expr [49-50]: Lit: Int(0) + Labels: + Expr [69-70]: Lit: Int(1) + Block [71-85]: + Stmt [73-83] + StmtKind: ClassicalDeclarationStmt [73-83]: ClassicalType [73-76]: IntType [73-76], Ident [77-78] "y", ValueExpression ExprStmt [81-82]: Expr [81-82]: Lit: Int(1) + "#]], + ); +} From 7ea14c128f7e70a5880a0e7273c32aeeb8483a90 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Wed, 12 Feb 2025 15:01:24 -0800 Subject: [PATCH 09/98] Add subroutine, return, and gate defs, externs, and old style decls (#2176) --- compiler/qsc_qasm3/src/ast.rs | 205 +++++++++---- .../qsc_qasm3/src/parser/completion/tests.rs | 4 +- compiler/qsc_qasm3/src/parser/expr.rs | 2 +- compiler/qsc_qasm3/src/parser/prim.rs | 10 +- compiler/qsc_qasm3/src/parser/stmt.rs | 278 ++++++++++++++++-- compiler/qsc_qasm3/src/parser/stmt/tests.rs | 4 + .../qsc_qasm3/src/parser/stmt/tests/def.rs | 165 +++++++++++ .../src/parser/stmt/tests/extern_decl.rs | 229 +++++++++++++++ .../src/parser/stmt/tests/gate_def.rs | 144 +++++++++ .../src/parser/stmt/tests/old_style_decl.rs | 52 ++++ 10 files changed, 1009 insertions(+), 84 deletions(-) create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/def.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/extern_decl.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/gate_def.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/old_style_decl.rs diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index 9a16e08a0e..5814afc38f 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -746,27 +746,45 @@ impl Display for ClassicalArgument { } #[derive(Clone, Debug)] -pub struct ExternArgument { - pub span: Span, - pub r#type: ScalarType, - pub access: Option, +pub enum ExternParameter { + Scalar(ScalarType, Span), + Quantum(Option, Span), + ArrayReference(ArrayReferenceType, Span), } -impl Display for ExternArgument { +impl Display for ExternParameter { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(access) = &self.access { - write!( - f, - "ExternArgument {}: {}, {}", - self.span, self.r#type, access - ) - } else { - write!(f, "ExternArgument {}: {}", self.span, self.r#type) + 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}") + } } } } -#[derive(Clone, Debug)] +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, @@ -778,7 +796,7 @@ impl Display for ScalarType { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub enum ScalarTypeKind { Bit(BitType), Int(IntType), @@ -789,6 +807,8 @@ pub enum ScalarTypeKind { BoolType, Duration, Stretch, + #[default] + Err, } impl Display for ScalarTypeKind { @@ -803,6 +823,7 @@ impl Display for ScalarTypeKind { ScalarTypeKind::BoolType => write!(f, "BoolType"), ScalarTypeKind::Duration => write!(f, "Duration"), ScalarTypeKind::Stretch => write!(f, "Stretch"), + ScalarTypeKind::Err => write!(f, "Err"), } } } @@ -966,8 +987,9 @@ impl Display for ArrayType { #[derive(Clone, Debug)] pub struct ArrayReferenceType { pub span: Span, + pub mutability: AccessControl, pub base_type: ArrayBaseTypeKind, - pub dimensions: List, + pub dimensions: List, } impl Display for ArrayReferenceType { @@ -1061,7 +1083,7 @@ impl Display for IncludeStmt { #[derive(Clone, Debug)] pub struct QubitDeclaration { pub span: Span, - pub qubit: Ident, + pub qubit: Box, pub size: Option, } @@ -1082,23 +1104,40 @@ impl Display for QubitDeclaration { #[derive(Clone, Debug)] pub struct QuantumGateDefinition { pub span: Span, - pub name: Identifier, - pub arguments: Vec, - pub qubits: Vec, - pub body: Vec, + pub ident: Box, + pub params: List, + pub qubits: List, + pub body: Box, } impl Display for QuantumGateDefinition { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut indent = set_indentation(indented(f), 0); - write!(indent, "QuantumGateDefinition {}: {}", self.span, self.name)?; - for arg in &self.arguments { - write!(indent, "\n{arg}")?; - } - for qubit in &self.qubits { - write!(indent, "\n{qubit}")?; - } - for stmt in &self.body { + write!(indent, "Gate {}: {}", self.span, self.ident)?; + write!(indent, "(")?; + if self.params.is_empty() { + write!(indent, "")?; + } else { + let param_str = self + .params + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(", "); + write!(indent, "{param_str}")?; + } + write!(indent, ") ")?; + + let qubit_str = self + .qubits + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(", "); + write!(indent, "{qubit_str}")?; + + writeln!(indent)?; + for stmt in &self.body.stmts { write!(indent, "\n{stmt}")?; } Ok(()) @@ -1108,16 +1147,16 @@ impl Display for QuantumGateDefinition { #[derive(Clone, Debug)] pub struct ExternDecl { pub span: Span, - pub name: Identifier, - pub arguments: List, + pub ident: Box, + pub params: List, pub return_type: Option, } impl Display for ExternDecl { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut indent = set_indentation(indented(f), 0); - write!(indent, "ExternDecl {}: {}", self.span, self.name)?; - for arg in &self.arguments { + write!(indent, "ExternDecl {}: {}", self.span, self.ident)?; + for arg in &self.params { write!(indent, "\n{arg}")?; } if let Some(return_type) = &self.return_type { @@ -1141,7 +1180,7 @@ impl Display for QuantumStmt { #[derive(Clone, Debug)] pub enum QuantumStmtKind { - Gate(QuantumGate), + Gate(GateCall), Phase(QuantumPhase), Barrier(List), Reset(List>), @@ -1152,18 +1191,18 @@ pub enum QuantumStmtKind { impl Display for QuantumStmtKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - QuantumStmtKind::Gate(gate) => write!(f, "QuantumStmtKind {gate}"), - QuantumStmtKind::Phase(phase) => write!(f, "QuantumStmtKind {phase}"), - QuantumStmtKind::Barrier(barrier) => write!(f, "QuantumStmtKind {barrier:?}"), - QuantumStmtKind::Reset(reset) => write!(f, "QuantumStmtKind {reset:?}"), - QuantumStmtKind::DelayInstruction(delay) => write!(f, "QuantumStmtKind {delay:?}"), - QuantumStmtKind::Box(box_stmt) => write!(f, "QuantumStmtKind {box_stmt:?}"), + QuantumStmtKind::Gate(gate) => write!(f, "{gate}"), + QuantumStmtKind::Phase(phase) => write!(f, "{phase}"), + QuantumStmtKind::Barrier(barrier) => write!(f, "{barrier:?}"), + QuantumStmtKind::Reset(reset) => write!(f, "{reset:?}"), + QuantumStmtKind::DelayInstruction(delay) => write!(f, "{delay:?}"), + QuantumStmtKind::Box(box_stmt) => write!(f, "{box_stmt:?}"), } } } #[derive(Clone, Debug)] -pub struct QuantumGate { +pub struct GateCall { pub span: Span, pub modifiers: List, pub name: Identifier, @@ -1172,10 +1211,10 @@ pub struct QuantumGate { pub duration: Option, } -impl Display for QuantumGate { +impl Display for GateCall { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut indent = set_indentation(indented(f), 0); - write!(indent, "QuantumGate {}: {}", self.span, self.name)?; + write!(indent, "GateCall {}: {}", self.span, self.name)?; for arg in &self.args { write!(indent, "\n{arg}")?; } @@ -1311,7 +1350,7 @@ pub struct IODeclaration { pub span: Span, pub io_identifier: IOKeyword, pub r#type: TypeDef, - pub identifier: Box, + pub ident: Box, } impl Display for IODeclaration { @@ -1319,7 +1358,7 @@ impl Display for IODeclaration { write!( f, "IODeclaration {}: {}, {}, {}", - self.span, self.io_identifier, self.r#type, self.identifier + self.span, self.io_identifier, self.r#type, self.ident ) } } @@ -1412,23 +1451,79 @@ impl Display for CalibrationArgument { } } +#[derive(Clone, Debug)] +pub enum TypedParameter { + Scalar(ScalarType, Box, Span), + Quantum(Option, Box, Span), + ArrayReference(ArrayReferenceType, Box, Span), +} + +impl WithSpan for TypedParameter { + fn with_span(self, span: Span) -> Self { + match self { + TypedParameter::Scalar(scalar, ident, _) => TypedParameter::Scalar(scalar, ident, span), + TypedParameter::Quantum(expr, ident, _) => TypedParameter::Quantum(expr, ident, span), + TypedParameter::ArrayReference(array, ident, _) => { + TypedParameter::ArrayReference(array, ident, span) + } + } + } +} + +impl Display for TypedParameter { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + TypedParameter::Scalar(scalar, ident, span) => { + write!(f, "{span} {ident}: {scalar}") + } + TypedParameter::Quantum(expr, ident, span) => { + if let Some(expr) = expr { + write!(f, "{span} {ident}: qubit[{expr}]") + } else { + write!(f, "{span} {ident}: qubit") + } + } + TypedParameter::ArrayReference(array, ident, span) => { + write!(f, "{span} {ident}: {array}") + } + } + } +} + +impl Default for TypedParameter { + fn default() -> Self { + TypedParameter::Scalar(ScalarType::default(), Box::default(), Span::default()) + } +} + #[derive(Clone, Debug)] pub struct DefStmt { - span: Span, - name: Identifier, - args: List>, - body: List>, - return_type: Option, + pub span: Span, + pub name: Box, + pub params: List, + pub body: Box, + pub return_type: Option, } impl Display for DefStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut indent = set_indentation(indented(f), 0); write!(indent, "DefStmt {}: {}", self.span, self.name)?; - for arg in &self.args { - write!(indent, "\n{arg}")?; + write!(indent, "(")?; + if self.params.is_empty() { + write!(indent, "")?; + } else { + let param_str = self + .params + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(", "); + write!(indent, "{param_str}")?; } - for stmt in &self.body { + write!(indent, ") ")?; + + for stmt in &self.body.stmts { write!(indent, "\n{stmt}")?; } if let Some(return_type) = &self.return_type { @@ -1455,8 +1550,8 @@ impl Display for Operand { #[derive(Clone, Debug)] pub struct ReturnStmt { - span: Span, - expr: Option>, + pub span: Span, + pub expr: Option>, } impl Display for ReturnStmt { diff --git a/compiler/qsc_qasm3/src/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs index f15e41a7f2..0917cf49ff 100644 --- a/compiler/qsc_qasm3/src/parser/completion/tests.rs +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -36,7 +36,7 @@ fn begin_document() { "|OPENQASM 3;", &expect![[r#" WordKinds( - Annotation | Include | Input | OpenQASM | Output | Pragma | Qubit | Switch, + Annotation | CReg | Def | Extern | Gate | Include | Input | OpenQASM | Output | Pragma | QReg | Qubit | Return | Switch, ) "#]], ); @@ -48,7 +48,7 @@ fn end_of_version() { "OPENQASM 3;|", &expect![[r#" WordKinds( - Annotation | Include | Input | Output | Pragma | Qubit | Switch, + Annotation | CReg | Def | Extern | Gate | Include | Input | Output | Pragma | QReg | Qubit | Return | Switch, ) "#]], ); diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 7940f11b52..b4953d0204 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -178,7 +178,7 @@ fn expr_base(s: &mut ParserContext) -> Result { if let Ok(id) = ident(s) { Ok(Expr { span: s.span(lo), - kind: Box::new(ExprKind::Ident(*id)), + kind: Box::new(ExprKind::Ident(id)), }) } else { Err(Error::new(ErrorKind::Rule( diff --git a/compiler/qsc_qasm3/src/parser/prim.rs b/compiler/qsc_qasm3/src/parser/prim.rs index 6ae8353a17..c3062c9bf4 100644 --- a/compiler/qsc_qasm3/src/parser/prim.rs +++ b/compiler/qsc_qasm3/src/parser/prim.rs @@ -58,15 +58,15 @@ pub(super) fn token(s: &mut ParserContext, t: TokenKind) -> Result<()> { } } -pub(super) fn ident(s: &mut ParserContext) -> Result> { +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(Box::new(Ident { + Ok(Ident { span: peek.span, name, - })) + }) } else { Err(Error::new(ErrorKind::Rule( "identifier", @@ -92,11 +92,11 @@ pub(super) fn path( let lo = s.peek().span.lo; let i = ident(s).map_err(|e| (e, None))?; - let mut parts = vec![*i]; + let mut parts = vec![i]; while token(s, TokenKind::Dot).is_ok() { s.expect(WordKinds::PathSegment); match ident(s) { - Ok(ident) => parts.push(*ident), + Ok(ident) => parts.push(ident), Err(error) => { let trivia_span = s.skip_trivia(); let keyword = trivia_span.hi == trivia_span.lo diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 34b5bf2a68..b01735615b 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -12,16 +12,17 @@ use super::{ completion::WordKinds, error::{Error, ErrorKind}, expr::{self, designator}, - prim::{self, barrier, many, opt, recovering, recovering_semi, recovering_token, shorten}, + prim::{self, barrier, many, opt, recovering, recovering_semi, recovering_token, seq, shorten}, Result, }; use crate::{ ast::{ - list_from_iter, AngleType, Annotation, ArrayBaseTypeKind, ArrayType, BitType, Block, - ClassicalDeclarationStmt, ComplexType, ConstantDeclaration, Expr, ExprStmt, FloatType, - IODeclaration, IOKeyword, IncludeStmt, IntType, List, LiteralKind, Pragma, - QubitDeclaration, ScalarType, ScalarTypeKind, Stmt, StmtKind, SwitchStmt, TypeDef, - UIntType, + list_from_iter, AccessControl, AngleType, Annotation, ArrayBaseTypeKind, + ArrayReferenceType, ArrayType, BitType, Block, ClassicalDeclarationStmt, ComplexType, + ConstantDeclaration, DefStmt, Expr, ExprStmt, ExternDecl, ExternParameter, FloatType, + IODeclaration, IOKeyword, Ident, IncludeStmt, IntType, List, LiteralKind, Pragma, + QuantumGateDefinition, QubitDeclaration, ReturnStmt, ScalarType, ScalarTypeKind, Stmt, + StmtKind, SwitchStmt, TypeDef, TypedParameter, UIntType, }, keyword::Keyword, lex::{ @@ -50,12 +51,20 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { s.push_error(Error::new(ErrorKind::FloatingAnnotation(err_item.span))); return Ok(err_item); } - } else if let Some(v) = opt(s, parse_include)? { - Box::new(v) + } else if let Some(decl) = opt(s, parse_gatedef)? { + Box::new(decl) + } else if let Some(decl) = opt(s, parse_def)? { + Box::new(decl) + } else if let Some(include) = opt(s, parse_include)? { + Box::new(include) } else if let Some(decl) = opt(s, parse_local)? { Box::new(decl) + } else if let Some(decl) = opt(s, parse_extern)? { + Box::new(decl) } else if let Some(switch) = opt(s, parse_switch_stmt)? { Box::new(StmtKind::Switch(switch)) + } else if let Some(decl) = opt(s, parse_return)? { + Box::new(decl) } else { return Err(Error::new(ErrorKind::Rule( "statement", @@ -220,6 +229,9 @@ fn parse_pragma(s: &mut ParserContext) -> Result { }) } +// qreg and creg are separate from classical and quantum declarations +// simply for performance reasons. The latter are more common and old +// style declarations should be rare. fn parse_local(s: &mut ParserContext) -> Result { if let Some(decl) = opt(s, parse_classical_decl)? { Ok(decl) @@ -227,6 +239,10 @@ fn parse_local(s: &mut ParserContext) -> Result { Ok(decl) } else if let Some(decl) = opt(s, parse_io_decl)? { Ok(decl) + } else if let Some(decl) = opt(s, qreg_decl)? { + Ok(decl) + } else if let Some(decl) = opt(s, creg_decl)? { + Ok(decl) } else { Err(Error::new(ErrorKind::Rule( "local declaration", @@ -236,21 +252,196 @@ fn parse_local(s: &mut ParserContext) -> Result { } } +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) +} + +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)?; + 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) +} + +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) +} + +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(ty, Box::new(ident), s.span(lo)) + } else if let Ok(size) = qubit_type(s) { + let ident = prim::ident(s)?; + TypedParameter::Quantum(size, Box::new(ident), s.span(lo)) + } 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(ty, ident, s.span(lo)) + } else if let Ok((ident, size)) = qreg_type(s) { + TypedParameter::Quantum(size, ident, s.span(lo)) + } else if let Ok(ty) = array_reference_ty(s) { + let ident = prim::ident(s)?; + TypedParameter::ArrayReference(ty, Box::new(ident), s.span(lo)) + } else { + return Err(Error::new(ErrorKind::Rule( + "argument definition", + s.peek().kind, + s.peek().span, + ))); + }; + Ok(kind) +} + +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), + }) +} + +fn return_sig(s: &mut ParserContext) -> Result { + token(s, TokenKind::Arrow)?; + scalar_type(s) +} + +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 = 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) +} + +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::value_expr)?; + recovering_semi(s); + Ok(StmtKind::Return(ReturnStmt { + span: s.span(lo), + expr, + })) +} + fn parse_quantum_decl(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; - s.expect(WordKinds::Qubit); - token(s, TokenKind::Keyword(crate::keyword::Keyword::Qubit))?; - let size = opt(s, designator)?; - let name = prim::ident(s)?; + let size = qubit_type(s)?; + let ident = prim::ident(s)?; recovering_semi(s); Ok(StmtKind::QuantumDecl(QubitDeclaration { span: s.span(lo), - qubit: *name, + qubit: Box::new(ident), size, })) } +fn qubit_type(s: &mut ParserContext<'_>) -> Result> { + token(s, TokenKind::Keyword(crate::keyword::Keyword::Qubit))?; + let size = opt(s, designator)?; + Ok(size) +} + fn parse_io_decl(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; @@ -269,13 +460,13 @@ fn parse_io_decl(s: &mut ParserContext) -> Result { let ty = scalar_or_array_type(s)?; - let identifier = prim::ident(s)?; + let ident = Box::new(prim::ident(s)?); recovering_semi(s); let decl = IODeclaration { span: s.span(lo), io_identifier: kind, r#type: ty, - identifier, + ident, }; Ok(StmtKind::IODeclaration(decl)) } @@ -304,7 +495,7 @@ fn parse_classical_decl(s: &mut ParserContext) -> Result { }; let ty = scalar_or_array_type(s)?; - let identifier = prim::ident(s)?; + let identifier = Box::new(prim::ident(s)?); let stmt = if is_const { token(s, TokenKind::Eq)?; @@ -352,11 +543,7 @@ pub(super) fn array_type(s: &mut ParserContext) -> Result { Ok(ArrayType { base_type: kind, span: s.span(lo), - dimensions: expr_list - .into_iter() - .map(Box::new) - .collect::>() - .into_boxed_slice(), + dimensions: list_from_iter(expr_list), }) } @@ -425,6 +612,55 @@ pub(super) fn scalar_type(s: &mut ParserContext) -> Result { ))) } +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), + r#type: 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), + 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<(Box, Option)> { + token(s, TokenKind::Keyword(crate::keyword::Keyword::CReg))?; + let name = Box::new(prim::ident(s)?); + let size = opt(s, designator)?; + Ok((name, size)) +} + +fn qreg_type(s: &mut ParserContext) -> Result<(Box, Option)> { + token(s, TokenKind::Keyword(crate::keyword::Keyword::QReg))?; + let name = Box::new(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))?; diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests.rs b/compiler/qsc_qasm3/src/parser/stmt/tests.rs index 73372ce1d3..f4312061ba 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests.rs @@ -3,7 +3,11 @@ mod annotation; mod classical_decl; +mod def; +mod extern_decl; +mod gate_def; mod io_decl; +mod old_style_decl; mod pragma; mod quantum_decl; mod switch_stmt; 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..a67bd8b88d --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs @@ -0,0 +1,165 @@ +// 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] + StmtKind: DefStmt [0-11]: Ident [4-5] "x"() "#]], + ); +} + +#[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] + StmtKind: DefStmt [0-12]: Ident [4-5] "x"([6-6] Ident [0-0] "": ClassicalType [0-0]: Err) + + [ + 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] + StmtKind: DefStmt [0-23]: Ident [4-5] "x"([6-11] Ident [10-11] "a": ClassicalType [6-9]: IntType [6-9], [12-12] Ident [0-0] "": ClassicalType [0-0]: Err, [13-18] Ident [17-18] "b": ClassicalType [13-16]: IntType [13-16]) + + [ + 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] + StmtKind: DefStmt [0-47]: Ident [4-10] "square"([11-20] Ident [19-20] "x": ClassicalType [11-18]: IntType[ExprStmt [14-18]: Expr [15-17]: Lit: Int(32)]: [11-18]) + Stmt [31-45] + StmtKind: ReturnStmt [31-45]: ValueExpression ExprStmt [38-44]: Expr [38-44]: BinOp (Exp): + Expr [38-39]: Ident [38-39] "x" + Expr [43-44]: Lit: Int(2) + ClassicalType [25-28]: IntType [25-28]"#]], + ); +} + +#[test] +fn quantum_args() { + check( + parse, + "def x(qubit q, qubit[n] qubits) { }", + &expect![[r#" + Stmt [0-35] + StmtKind: DefStmt [0-35]: Ident [4-5] "x"([6-13] Ident [12-13] "q": qubit, [15-30] Ident [24-30] "qubits": qubit[ExprStmt [20-23]: Expr [21-22]: Ident [21-22] "n"]) "#]], + ); +} + +#[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] + StmtKind: DefStmt [0-74]: Ident [4-8] "test"([9-15] Ident [14-15] "c": ClassicalType [9-15]: BitType, [17-23] Ident [22-23] "q": qubit, [25-35] Ident [30-32] "c2": ClassicalType [25-35]: BitType [25-35]: ExprStmt [32-35]: Expr [33-34]: Lit: Int(2), [37-47] Ident [42-44] "q4": qubit[ExprStmt [44-47]: Expr [45-46]: Lit: Int(4)]) + Stmt [58-72] + StmtKind: ReturnStmt [58-72]: ValueExpression ExprStmt [65-71]: Expr [65-71]: BinOp (Exp): + Expr [65-66]: Ident [65-66] "x" + Expr [70-71]: Lit: Int(2) + ClassicalType [52-55]: IntType [52-55]"#]], + ); +} + +#[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] + StmtKind: DefStmt [0-59]: Ident [4-17] "specified_sub"([18-55] Ident [48-55] "arr_arg": ArrayReferenceType [18-47]: ArrayBaseTypeKind IntType[ExprStmt [36-39]: Expr [37-38]: Lit: Int(8)]: [33-39] + Expr [41-42]: Lit: Int(2) + Expr [44-46]: Lit: Int(10)) "#]], + ); +} + +#[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] + StmtKind: DefStmt [0-63]: Ident [4-18] "arr_subroutine"([19-59] Ident [52-59] "arr_arg": ArrayReferenceType [19-51]: ArrayBaseTypeKind IntType[ExprStmt [37-40]: Expr [38-39]: Lit: Int(8)]: [34-40] + Expr [49-50]: Lit: Int(1)) "#]], + ); +} + +#[test] +fn mutable_array_arg() { + check( + parse, + "def mut_subroutine(mutable array[int[8], #dim = 1] arr_arg) {}", + &expect![[r#" + Stmt [0-62] + StmtKind: DefStmt [0-62]: Ident [4-18] "mut_subroutine"([19-58] Ident [51-58] "arr_arg": ArrayReferenceType [19-50]: ArrayBaseTypeKind IntType[ExprStmt [36-39]: Expr [37-38]: Lit: Int(8)]: [33-39] + Expr [48-49]: Lit: Int(1)) "#]], + ); +} 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..8f5eddc341 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/extern_decl.rs @@ -0,0 +1,229 @@ +// 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] + StmtKind: ExternDecl [0-10]: Ident [7-8] "x" + + [ + 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] + StmtKind: ExternDecl [0-21]: Ident [7-8] "x" + [9-12]: ClassicalType [9-12]: BitType + ClassicalType [17-20]: BitType"#]], + ); +} + +#[test] +fn sized_bit_param_bit_ret_decl() { + check( + parse, + "extern x(bit[n]) -> bit;", + &expect![[r#" + Stmt [0-24] + StmtKind: ExternDecl [0-24]: Ident [7-8] "x" + [9-15]: ClassicalType [9-15]: BitType [9-15]: ExprStmt [12-15]: Expr [13-14]: Ident [13-14] "n" + ClassicalType [20-23]: BitType"#]], + ); +} + +#[test] +fn sized_creg_param_bit_ret_decl() { + check( + parse, + "extern x(creg[n]) -> bit;", + &expect![[r#" + Stmt [0-25] + StmtKind: ExternDecl [0-25]: Ident [7-8] "x" + [9-16]: ClassicalType [9-16]: BitType [9-16]: ExprStmt [13-16]: Expr [14-15]: Ident [14-15] "n" + ClassicalType [21-24]: BitType"#]], + ); +} + +#[test] +fn creg_param_bit_ret_decl() { + check( + parse, + "extern x(creg) -> bit;", + &expect![[r#" + Stmt [0-22] + StmtKind: ExternDecl [0-22]: Ident [7-8] "x" + [9-13]: ClassicalType [9-13]: BitType + ClassicalType [18-21]: BitType"#]], + ); +} + +#[test] +fn readonly_array_arg_with_int_dims() { + check( + parse, + "extern x(readonly array[int[8], 2, 10]);", + &expect![[r#" + Stmt [0-40] + StmtKind: ExternDecl [0-40]: Ident [7-8] "x" + [9-38]: ArrayReferenceType [9-38]: ArrayBaseTypeKind IntType[ExprStmt [27-30]: Expr [28-29]: Lit: Int(8)]: [24-30] + Expr [32-33]: Lit: Int(2) + Expr [35-37]: Lit: Int(10)"#]], + ); +} + +#[test] +fn readonly_array_arg_with_dim() { + check( + parse, + "extern x(readonly array[int[8], #dim = 1]);", + &expect![[r#" + Stmt [0-43] + StmtKind: ExternDecl [0-43]: Ident [7-8] "x" + [9-41]: ArrayReferenceType [9-41]: ArrayBaseTypeKind IntType[ExprStmt [27-30]: Expr [28-29]: Lit: Int(8)]: [24-30] + Expr [39-40]: Lit: Int(1)"#]], + ); +} + +#[test] +fn mutable_array_arg() { + check( + parse, + "extern x(mutable array[int[8], #dim = 1]);", + &expect![[r#" + Stmt [0-42] + StmtKind: ExternDecl [0-42]: Ident [7-8] "x" + [9-40]: ArrayReferenceType [9-40]: ArrayBaseTypeKind IntType[ExprStmt [26-29]: Expr [27-28]: Lit: Int(8)]: [23-29] + Expr [38-39]: Lit: Int(1)"#]], + ); +} + +#[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] + Annotation [0-16]: (test.annotation) + StmtKind: ExternDecl [25-47]: Ident [32-33] "x" + [34-38]: ClassicalType [34-38]: BitType + ClassicalType [43-46]: BitType"#]], + ); +} + +#[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] + StmtKind: ExternDecl [0-12]: Ident [7-8] "x" + [9-9]: ClassicalType [0-0]: Err + + [ + 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] + StmtKind: ExternDecl [0-19]: Ident [7-8] "x" + [9-12]: ClassicalType [9-12]: IntType [9-12] + [13-13]: ClassicalType [0-0]: Err + [14-17]: ClassicalType [14-17]: IntType [14-17] + + [ + Error( + MissingSeqEntry( + Span { + lo: 13, + hi: 13, + }, + ), + ), + ]"#]], + ); +} 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..24b8fb4ce2 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_def.rs @@ -0,0 +1,144 @@ +// 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] + StmtKind: Gate [0-12]: Ident [5-9] "c0q0"() + "#]], + ); +} + +#[test] +fn no_qubits_no_classical_with_parens() { + check( + parse, + "gate c0q0() {}", + &expect![[r#" + Stmt [0-14] + StmtKind: Gate [0-14]: Ident [5-9] "c0q0"() + "#]], + ); +} + +#[test] +fn one_qubit_no_classical() { + check( + parse, + "gate c0q1 a {}", + &expect![[r#" + Stmt [0-14] + StmtKind: Gate [0-14]: Ident [5-9] "c0q1"() Ident [10-11] "a" + "#]], + ); +} + +#[test] +fn two_qubits_no_classical() { + check( + parse, + "gate c0q2 a, b {}", + &expect![[r#" + Stmt [0-17] + StmtKind: Gate [0-17]: Ident [5-9] "c0q2"() Ident [10-11] "a", Ident [13-14] "b" + "#]], + ); +} + +#[test] +fn three_qubits_trailing_comma_no_classical() { + check( + parse, + "gate c0q3 a, b, c, {}", + &expect![[r#" + Stmt [0-21] + StmtKind: Gate [0-21]: Ident [5-9] "c0q3"() Ident [10-11] "a", Ident [13-14] "b", Ident [16-17] "c" + "#]], + ); +} + +#[test] +fn no_qubits_one_classical() { + check( + parse, + "gate c1q0(a) {}", + &expect![[r#" + Stmt [0-15] + StmtKind: Gate [0-15]: Ident [5-9] "c1q0"(Ident [10-11] "a") + "#]], + ); +} + +#[test] +fn no_qubits_two_classical() { + check( + parse, + "gate c2q0(a, b) {}", + &expect![[r#" + Stmt [0-18] + StmtKind: Gate [0-18]: Ident [5-9] "c2q0"(Ident [10-11] "a", Ident [13-14] "b") + "#]], + ); +} + +#[test] +fn no_qubits_three_classical() { + check( + parse, + "gate c3q0(a, b, c) {}", + &expect![[r#" + Stmt [0-21] + StmtKind: Gate [0-21]: Ident [5-9] "c3q0"(Ident [10-11] "a", Ident [13-14] "b", Ident [16-17] "c") + "#]], + ); +} + +#[test] +fn one_qubit_one_classical() { + check( + parse, + "gate c1q1(a) b {}", + &expect![[r#" + Stmt [0-17] + StmtKind: Gate [0-17]: Ident [5-9] "c1q1"(Ident [10-11] "a") Ident [13-14] "b" + "#]], + ); +} + +#[test] +fn two_qubits_two_classical() { + check( + parse, + "gate c2q2(a, b) c, d {}", + &expect![[r#" + Stmt [0-23] + StmtKind: Gate [0-23]: Ident [5-9] "c2q2"(Ident [10-11] "a", Ident [13-14] "b") Ident [16-17] "c", Ident [19-20] "d" + "#]], + ); +} + +#[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] + StmtKind: Gate [0-45]: Ident [5-9] "c2q2"(Ident [10-11] "a", Ident [13-14] "b") Ident [16-17] "c", Ident [19-20] "d" + + Stmt [23-43] + StmtKind: ClassicalDeclarationStmt [23-43]: ClassicalType [23-32]: FloatType[ExprStmt [28-32]: Expr [29-31]: Lit: Int(32)]: [23-32], Ident [33-34] "x", ValueExpression ExprStmt [37-42]: Expr [37-42]: BinOp (Sub): + Expr [37-38]: Ident [37-38] "a" + Expr [41-42]: Ident [41-42] "b""#]], + ); +} 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..87db062e4c --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/old_style_decl.rs @@ -0,0 +1,52 @@ +// 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] + StmtKind: ClassicalDeclarationStmt [0-7]: ClassicalType [0-7]: BitType, Ident [5-6] "c""#]], + ); +} + +#[test] +fn creg_array_decl() { + check( + parse, + "creg c[n];", + &expect![[r#" + Stmt [0-10] + StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-10]: BitType [0-10]: ExprStmt [6-9]: Expr [7-8]: Ident [7-8] "n", Ident [5-6] "c""#]], + ); +} + +#[test] +fn qreg_decl() { + check( + parse, + "qreg q;", + &expect![[r#" + Stmt [0-7] + StmtKind: QubitDeclaration [0-7]: Ident [5-6] "q""#]], + ); +} + +#[test] +fn qreg_array_decl() { + check( + parse, + "qreg q[n];", + &expect![[r#" + Stmt [0-10] + StmtKind: QubitDeclaration [0-10]: Ident [5-6] "q", ExprStmt [6-9]: Expr [7-8]: Ident [7-8] "n""#]], + ); +} From 440a42c87dbd76c390fc29f6cbbdf64749fd7baa Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:19:52 -0800 Subject: [PATCH 10/98] Add array and measurement declaration statements (#2180) --- compiler/qsc_qasm3/src/ast.rs | 8 +- compiler/qsc_qasm3/src/parser/expr.rs | 96 +++++++++++++++++-- compiler/qsc_qasm3/src/parser/expr/tests.rs | 58 +++++++++++ .../src/parser/stmt/tests/classical_decl.rs | 69 +++++++++++++ 4 files changed, 219 insertions(+), 12 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index 5814afc38f..e56abed25e 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -300,14 +300,14 @@ impl Display for UnaryOp { #[derive(Clone, Debug)] pub enum GateOperand { - Ident(Box), + IndexedIdent(Box), HardwareQubit(Box), } impl Display for GateOperand { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - GateOperand::Ident(ident) => write!(f, "GateOperand {ident}"), + GateOperand::IndexedIdent(ident) => write!(f, "GateOperand {ident}"), GateOperand::HardwareQubit(qubit) => write!(f, "GateOperand {qubit}"), } } @@ -1333,7 +1333,7 @@ impl Display for ClassicalDeclarationStmt { #[derive(Clone, Debug)] pub enum ValueExpression { Expr(ExprStmt), - Measurement(QuantumMeasurement), + Measurement(MeasureExpr), } impl Display for ValueExpression { @@ -1876,7 +1876,7 @@ impl Display for Lit { #[derive(Clone, Debug)] pub enum LiteralKind { - Array(List), + Array(List), Bitstring(BigInt, usize), Bool(bool), Duration { value: f64, unit: TimeUnit }, diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index b4953d0204..fd47f68280 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -16,9 +16,10 @@ use qsc_data_structures::span::Span; use crate::{ ast::{ - self, AssignExpr, AssignOpExpr, BinOp, BinaryOpExpr, Cast, DiscreteSet, Expr, ExprKind, - ExprStmt, FunctionCall, IndexElement, IndexExpr, IndexSetItem, Lit, LiteralKind, - RangeDefinition, TypeDef, UnaryOp, ValueExpression, Version, + self, list_from_iter, AssignExpr, AssignOpExpr, BinOp, BinaryOpExpr, Cast, DiscreteSet, + Expr, ExprKind, ExprStmt, FunctionCall, GateOperand, HardwareQubit, Ident, IndexElement, + IndexExpr, IndexSetItem, IndexedIdent, Lit, LiteralKind, MeasureExpr, RangeDefinition, + TypeDef, UnaryOp, ValueExpression, Version, }, keyword::Keyword, lex::{ @@ -36,7 +37,7 @@ use crate::parser::Result; use super::{ error::{Error, ErrorKind}, - prim::{ident, opt, seq, FinalSep}, + prim::{ident, many, opt, recovering_token, seq, FinalSep}, stmt::scalar_or_array_type, }; @@ -458,7 +459,7 @@ fn cast_op(s: &mut ParserContext, r#type: TypeDef) -> Result { fn index_expr(s: &mut ParserContext, lhs: Expr) -> Result { let lo = s.span(0).hi - 1; let index = index_element(s)?; - token(s, TokenKind::Close(Delim::Bracket))?; + recovering_token(s, TokenKind::Close(Delim::Bracket)); Ok(ExprKind::IndexExpr(IndexExpr { span: s.span(lo), collection: lhs, @@ -662,17 +663,96 @@ pub(super) fn designator(s: &mut ParserContext) -> Result { }) } +/// 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) +} + pub(super) fn value_expr(s: &mut ParserContext) -> Result> { let lo = s.peek().span.lo; - let expr = expr_stmt(s)?; + if let Some(measurement) = opt(s, measure_expr)? { + return Ok(Box::new(ValueExpression::Measurement(measurement))); + } + + let expr = if let Some(expr) = opt(s, expr_stmt)? { + expr + } else { + lit_array(s)? + }; + let stmt = ExprStmt { span: s.span(lo), expr, }; - // todo: measurement + Ok(Box::new(ValueExpression::Expr(stmt))) } -pub(crate) fn expr_list(s: &mut ParserContext<'_>) -> Result> { +pub(crate) fn expr_list(s: &mut ParserContext) -> Result> { seq(s, expr).map(|pair| pair.0) } + +fn measure_expr(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Measure)?; + + Ok(MeasureExpr { + span: s.span(lo), + operand: gate_operand(s)?, + }) +} + +fn gate_operand(s: &mut ParserContext) -> Result { + if let Some(indexed_ident) = opt(s, indexed_identifier)? { + return Ok(GateOperand::IndexedIdent(Box::new(indexed_ident))); + } + Ok(GateOperand::HardwareQubit(Box::new(hardware_qubit(s)?))) +} + +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(), + }) +} + +fn indexed_identifier(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let name: Ident = ident(s)?; + let indices = list_from_iter(many(s, index_operand)?); + + Ok(IndexedIdent { + span: s.span(lo), + name, + indices, + }) +} + +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) +} diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index 0569e32374..cf8db7ab75 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -1141,6 +1141,18 @@ fn set_expr() { ); } +#[test] +fn lit_array() { + check( + super::lit_array, + "{{2, {5}}, 1 + z}", + &expect![[r#" + Expr [0-17]: Lit: Array: + Expr { span: Span { lo: 1, hi: 9 }, kind: Lit(Lit { span: Span { lo: 1, hi: 9 }, kind: Array([Expr { span: Span { lo: 2, hi: 3 }, kind: Lit(Lit { span: Span { lo: 2, hi: 3 }, kind: Int(2) }) }, Expr { span: Span { lo: 5, hi: 8 }, kind: Lit(Lit { span: Span { lo: 5, hi: 8 }, kind: Array([Expr { span: Span { lo: 6, hi: 7 }, kind: Lit(Lit { span: Span { lo: 6, hi: 7 }, kind: Int(5) }) }]) }) }]) }) } + Expr { span: Span { lo: 11, hi: 16 }, kind: BinaryOp(BinaryOpExpr { op: Add, lhs: Expr { span: Span { lo: 11, hi: 12 }, kind: Lit(Lit { span: Span { lo: 11, hi: 12 }, kind: Int(1) }) }, rhs: Expr { span: Span { lo: 15, hi: 16 }, kind: Ident(Ident { span: Span { lo: 15, hi: 16 }, name: "z" }) } }) }"#]], + ); +} + #[test] fn assignment_and_unop() { check( @@ -1168,3 +1180,49 @@ fn assignment_unop_and() { Expr [15-16]: Ident [15-16] "b""#]], ); } + +#[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]: Ident [0-3] "arr"[ + IndexElement: + IndexSetItem Expr [4-5]: Lit: Int(1) + IndexElement: + IndexSetItem Expr [7-8]: Lit: Int(2)]"#]], + ); +} + +#[test] +fn measure_hardware_qubit() { + check( + super::measure_expr, + "measure $12", + &expect!["MeasureExpr [0-7]: GateOperand HardwareQubit [8-11]: 12"], + ); +} + +#[test] +fn measure_indexed_identifier() { + check( + super::measure_expr, + "measure qubits[1][2]", + &expect![[r#" + MeasureExpr [0-7]: GateOperand IndexedIdent [8-20]: Ident [8-14] "qubits"[ + IndexElement: + IndexSetItem Expr [15-16]: Lit: Int(1) + IndexElement: + IndexSetItem Expr [18-19]: Lit: Int(2)]"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs index 9fb8775554..6159810966 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs @@ -668,3 +668,72 @@ fn stretch_decl() { StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-7]: Stretch, Ident [8-9] "s""#]], ); } + +#[test] +fn empty_array_decl() { + check( + parse, + "array[int, 0] arr = {};", + &expect![[r#" + Stmt [0-23] + StmtKind: ClassicalDeclarationStmt [0-23]: ArrayType [0-13]: ArrayBaseTypeKind IntType [6-9] + Expr [11-12]: Lit: Int(0), Ident [14-17] "arr", ValueExpression ExprStmt [20-22]: 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] + StmtKind: ClassicalDeclarationStmt [0-34]: ArrayType [0-17]: ArrayBaseTypeKind IntType[ExprStmt [9-13]: Expr [10-12]: Lit: Int(32)]: [6-13] + Expr [15-16]: Lit: Int(3), Ident [18-21] "arr", ValueExpression ExprStmt [24-33]: Expr [24-33]: Lit: Array: + Expr { span: Span { lo: 25, hi: 26 }, kind: Lit(Lit { span: Span { lo: 25, hi: 26 }, kind: Int(1) }) } + Expr { span: Span { lo: 28, hi: 29 }, kind: Lit(Lit { span: Span { lo: 28, hi: 29 }, kind: Int(2) }) } + Expr { span: Span { lo: 31, hi: 32 }, kind: Lit(Lit { span: Span { lo: 31, hi: 32 }, kind: 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] + StmtKind: ClassicalDeclarationStmt [0-52]: ArrayType [0-20]: ArrayBaseTypeKind IntType[ExprStmt [9-13]: Expr [10-12]: Lit: Int(32)]: [6-13] + Expr [15-16]: Lit: Int(3) + Expr [18-19]: Lit: Int(2), Ident [21-24] "arr", ValueExpression ExprStmt [27-51]: Expr [27-51]: Lit: Array: + Expr { span: Span { lo: 28, hi: 34 }, kind: Lit(Lit { span: Span { lo: 28, hi: 34 }, kind: Array([Expr { span: Span { lo: 29, hi: 30 }, kind: Lit(Lit { span: Span { lo: 29, hi: 30 }, kind: Int(1) }) }, Expr { span: Span { lo: 32, hi: 33 }, kind: Lit(Lit { span: Span { lo: 32, hi: 33 }, kind: Int(2) }) }]) }) } + Expr { span: Span { lo: 36, hi: 42 }, kind: Lit(Lit { span: Span { lo: 36, hi: 42 }, kind: Array([Expr { span: Span { lo: 37, hi: 38 }, kind: Lit(Lit { span: Span { lo: 37, hi: 38 }, kind: Int(3) }) }, Expr { span: Span { lo: 40, hi: 41 }, kind: Lit(Lit { span: Span { lo: 40, hi: 41 }, kind: Int(4) }) }]) }) } + Expr { span: Span { lo: 44, hi: 50 }, kind: Lit(Lit { span: Span { lo: 44, hi: 50 }, kind: Array([Expr { span: Span { lo: 45, hi: 46 }, kind: Lit(Lit { span: Span { lo: 45, hi: 46 }, kind: Int(5) }) }, Expr { span: Span { lo: 48, hi: 49 }, kind: Lit(Lit { span: Span { lo: 48, hi: 49 }, kind: Int(6) }) }]) }) }"#]], + ); +} + +#[test] +fn measure_hardware_qubit_decl() { + check( + parse, + "bit res = measure $12;", + &expect![[r#" + Stmt [0-22] + StmtKind: ClassicalDeclarationStmt [0-22]: ClassicalType [0-3]: BitType, Ident [4-7] "res", ValueExpression MeasureExpr [10-17]: GateOperand HardwareQubit [18-21]: 12"#]], + ); +} + +#[test] +fn measure_register_decl() { + check( + parse, + "bit res = measure qubits[2][3];", + &expect![[r#" + Stmt [0-31] + StmtKind: ClassicalDeclarationStmt [0-31]: ClassicalType [0-3]: BitType, Ident [4-7] "res", ValueExpression MeasureExpr [10-17]: GateOperand IndexedIdent [18-30]: Ident [18-24] "qubits"[ + IndexElement: + IndexSetItem Expr [25-26]: Lit: Int(2) + IndexElement: + IndexSetItem Expr [28-29]: Lit: Int(3)]"#]], + ); +} From 9c342f6f8e873324016ccfcca8992ee6f8fd3d96 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Wed, 19 Feb 2025 14:46:26 -0800 Subject: [PATCH 11/98] parse if, for, and while statements --- compiler/qsc_qasm3/src/ast.rs | 42 ++---- compiler/qsc_qasm3/src/parser/expr.rs | 10 +- compiler/qsc_qasm3/src/parser/stmt.rs | 138 +++++++++++++++++- compiler/qsc_qasm3/src/parser/stmt/tests.rs | 3 + .../src/parser/stmt/tests/for_loops.rs | 94 ++++++++++++ .../src/parser/stmt/tests/if_stmt.rs | 96 ++++++++++++ .../src/parser/stmt/tests/switch_stmt.rs | 3 +- .../src/parser/stmt/tests/while_loops.rs | 39 +++++ 8 files changed, 380 insertions(+), 45 deletions(-) create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index e56abed25e..2adfea88b5 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -479,7 +479,7 @@ impl Display for DefCalStmt { #[derive(Clone, Debug)] pub struct IfStmt { pub span: Span, - pub condition: ExprStmt, + pub condition: Expr, pub if_block: List, pub else_block: Option>, } @@ -1564,33 +1564,11 @@ impl Display for ReturnStmt { } } -#[derive(Clone, Debug)] -pub struct BranchingStmt { - span: Span, - condition: ExprStmt, - if_block: List, - else_block: List, -} - -impl Display for BranchingStmt { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "BranchingStmt {}: {}", self.span, self.condition)?; - for stmt in &self.if_block { - write!(indent, "\n{stmt}")?; - } - for stmt in &self.else_block { - write!(indent, "\n{stmt}")?; - } - Ok(()) - } -} - #[derive(Clone, Debug)] pub struct WhileLoop { - span: Span, - while_condition: ExprStmt, - block: List, + pub span: Span, + pub while_condition: Expr, + pub block: List, } impl Display for WhileLoop { @@ -1606,11 +1584,11 @@ impl Display for WhileLoop { #[derive(Clone, Debug)] pub struct ForStmt { - span: Span, - r#type: ScalarType, - identifier: Identifier, - set_declaration: Box, - block: List, + pub span: Span, + pub r#type: ScalarType, + pub identifier: Identifier, + pub set_declaration: Box, + pub block: List, } impl Display for ForStmt { @@ -1632,7 +1610,7 @@ impl Display for ForStmt { pub enum EnumerableSet { DiscreteSet(DiscreteSet), RangeDefinition(RangeDefinition), - Expr(ExprStmt), + Expr(Expr), } impl Display for EnumerableSet { diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index fd47f68280..9e1c4ba6bd 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -498,8 +498,8 @@ fn index_set_item(s: &mut ParserContext) -> Result { return Ok(IndexSetItem::Expr(expr)); } - let end = opt(s, expr)?; - let step = opt(s, |s| { + let step = opt(s, expr)?; + let end = opt(s, |s| { token(s, TokenKind::Colon)?; expr(s) })?; @@ -512,11 +512,11 @@ fn index_set_item(s: &mut ParserContext) -> Result { })) } -fn set_expr(s: &mut ParserContext) -> Result { +pub(crate) fn set_expr(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; token(s, TokenKind::Open(Delim::Brace))?; - let (exprs, _) = seq(s, expr)?; - token(s, TokenKind::Close(Delim::Brace))?; + let exprs = expr_list(s)?; + recovering_token(s, TokenKind::Close(Delim::Brace)); Ok(DiscreteSet { span: s.span(lo), values: exprs.into_boxed_slice(), diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index b01735615b..7400fa38de 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -19,10 +19,11 @@ use crate::{ ast::{ list_from_iter, AccessControl, AngleType, Annotation, ArrayBaseTypeKind, ArrayReferenceType, ArrayType, BitType, Block, ClassicalDeclarationStmt, ComplexType, - ConstantDeclaration, DefStmt, Expr, ExprStmt, ExternDecl, ExternParameter, FloatType, - IODeclaration, IOKeyword, Ident, IncludeStmt, IntType, List, LiteralKind, Pragma, - QuantumGateDefinition, QubitDeclaration, ReturnStmt, ScalarType, ScalarTypeKind, Stmt, - StmtKind, SwitchStmt, TypeDef, TypedParameter, UIntType, + ConstantDeclaration, DefStmt, EnumerableSet, Expr, ExprStmt, ExternDecl, ExternParameter, + FloatType, ForStmt, IODeclaration, IOKeyword, Ident, Identifier, IfStmt, IncludeStmt, + IntType, List, LiteralKind, Pragma, QuantumGateDefinition, QubitDeclaration, + RangeDefinition, ReturnStmt, ScalarType, ScalarTypeKind, Stmt, StmtKind, SwitchStmt, + TypeDef, TypedParameter, UIntType, WhileLoop, }, keyword::Keyword, lex::{ @@ -63,6 +64,12 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { Box::new(decl) } else if let Some(switch) = opt(s, parse_switch_stmt)? { Box::new(StmtKind::Switch(switch)) + } else if let Some(stmt) = opt(s, parse_if_stmt)? { + Box::new(StmtKind::If(stmt)) + } else if let Some(stmt) = opt(s, parse_for_loop)? { + Box::new(StmtKind::For(stmt)) + } else if let Some(stmt) = opt(s, parse_while_loop)? { + Box::new(StmtKind::WhileLoop(stmt)) } else if let Some(decl) = opt(s, parse_return)? { Box::new(decl) } else { @@ -840,8 +847,8 @@ pub(super) fn complex_subtype(s: &mut ParserContext) -> Result { } /// The Language Spec and the grammar for switch statements disagree. -/// We followed the Spec when writing the parser -/// . +/// 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))?; @@ -891,3 +898,122 @@ fn default_case_stmt(s: &mut ParserContext) -> Result { token(s, TokenKind::Keyword(Keyword::Default))?; parse_block(s).map(|block| *block) } + +/// Parses a block or a statement. This is a helper function +/// to be used in loops and if stmts, in which their bodies +/// can be a block expr or a single statement. +fn parse_block_or_stmt(s: &mut ParserContext) -> Result> { + if let Some(block) = opt(s, parse_block)? { + Ok(block.stmts) + } else { + Ok(Box::new([parse(s)?])) + } +} + +/// Grammar ` 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))?; + let paren_expr_lo = s.peek().span.lo; + token(s, TokenKind::Open(Delim::Paren))?; + let condition = expr::paren_expr(s, paren_expr_lo)?; + 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_block, + else_block, + }) +} + +/// 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)?); + + // If no colon, return the expr as a normal index. + 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 let Some(expr) = opt(s, |s| { + token(s, TokenKind::Colon)?; + expr::expr(s) + })? { + step = end; + end = Some(expr); + } + + 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_loop(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + token(s, TokenKind::Keyword(Keyword::For))?; + let r#type = scalar_type(s)?; + let identifier = Identifier::Ident(Box::new(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), + r#type, + identifier, + set_declaration, + 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))?; + let paren_expr_lo = s.peek().span.lo; + token(s, TokenKind::Open(Delim::Paren))?; + let while_condition = expr::paren_expr(s, paren_expr_lo)?; + let block = parse_block_or_stmt(s)?; + + Ok(WhileLoop { + span: s.span(lo), + while_condition, + block, + }) +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests.rs b/compiler/qsc_qasm3/src/parser/stmt/tests.rs index f4312061ba..7fbaeee543 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests.rs @@ -5,9 +5,12 @@ mod annotation; mod classical_decl; mod def; mod extern_decl; +mod for_loops; mod gate_def; +mod if_stmt; mod io_decl; mod old_style_decl; mod pragma; mod quantum_decl; mod switch_stmt; +mod while_loops; 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..c25866968e --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs @@ -0,0 +1,94 @@ +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn simple_for_loop() { + check( + parse, + " + for int x in {1, 2, 3} { + int a = 0; + }", + &expect![[r#" + Stmt [5-54] + StmtKind: ForStmt [5-54]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: + Expr [19-20]: Lit: Int(1) + Expr [22-23]: Lit: Int(2) + Expr [25-26]: Lit: Int(3) + Stmt [38-48] + StmtKind: ClassicalDeclarationStmt [38-48]: ClassicalType [38-41]: IntType [38-41], Ident [42-43] "a", ValueExpression ExprStmt [46-47]: Expr [46-47]: Lit: Int(0)"#]], + ); +} + +#[test] +fn simple_for_loop_stmt_body() { + check( + parse, + " + for int x in {1, 2, 3} + int a = 0; + ", + &expect![[r#" + Stmt [5-46] + StmtKind: ForStmt [5-46]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: + Expr [19-20]: Lit: Int(1) + Expr [22-23]: Lit: Int(2) + Expr [25-26]: Lit: Int(3) + Stmt [36-46] + StmtKind: ClassicalDeclarationStmt [36-46]: ClassicalType [36-39]: IntType [36-39], Ident [40-41] "a", ValueExpression ExprStmt [44-45]: Expr [44-45]: Lit: Int(0)"#]], + ); +} + +#[test] +fn for_loop_range() { + check( + parse, + " + for int x in [0:2:7] { + int a = 0; + }", + &expect![[r#" + Stmt [5-52] + StmtKind: ForStmt [5-52]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Range: [18-25] + Expr [19-20]: Lit: Int(0) + Expr [21-22]: Lit: Int(2) + Expr [23-24]: Lit: Int(7) + Stmt [36-46] + StmtKind: ClassicalDeclarationStmt [36-46]: ClassicalType [36-39]: IntType [36-39], Ident [40-41] "a", ValueExpression ExprStmt [44-45]: Expr [44-45]: Lit: Int(0)"#]], + ); +} + +#[test] +fn for_loop_range_no_step() { + check( + parse, + " + for int x in [0:7] { + int a = 0; + }", + &expect![[r#" + Stmt [5-50] + StmtKind: ForStmt [5-50]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Range: [18-23] + Expr [19-20]: Lit: Int(0) + + Expr [21-22]: Lit: Int(7) + Stmt [34-44] + StmtKind: ClassicalDeclarationStmt [34-44]: ClassicalType [34-37]: IntType [34-37], Ident [38-39] "a", ValueExpression ExprStmt [42-43]: Expr [42-43]: Lit: Int(0)"#]], + ); +} + +#[test] +fn for_loop_expr() { + check( + parse, + " + for int x in xs { + int a = 0; + }", + &expect![[r#" + Stmt [5-47] + StmtKind: ForStmt [5-47]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Expr [18-20]: Ident [18-20] "xs" + Stmt [31-41] + StmtKind: ClassicalDeclarationStmt [31-41]: ClassicalType [31-34]: IntType [31-34], Ident [35-36] "a", ValueExpression ExprStmt [39-40]: Expr [39-40]: Lit: Int(0)"#]], + ); +} 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..1e265ba04b --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs @@ -0,0 +1,96 @@ +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn simple_if_stmt() { + check( + parse, + " + if (x == y) { + int a = 0; + } else { + int a = 1; + } + ", + &expect![[r#" + Stmt [5-75] + StmtKind: IfStmt [5-75]: Expr [8-16]: Paren: + Expr [9-15]: BinOp (Eq): + Expr [9-10]: Ident [9-10] "x" + Expr [14-15]: Ident [14-15] "y" + Stmt [27-37] + StmtKind: ClassicalDeclarationStmt [27-37]: ClassicalType [27-30]: IntType [27-30], Ident [31-32] "a", ValueExpression ExprStmt [35-36]: Expr [35-36]: Lit: Int(0) + Else: + Stmt [59-69] + StmtKind: ClassicalDeclarationStmt [59-69]: ClassicalType [59-62]: IntType [59-62], Ident [63-64] "a", ValueExpression ExprStmt [67-68]: Expr [67-68]: Lit: Int(1)"#]], + ); +} + +#[test] +fn if_stmt_missing_else() { + check( + parse, + " + if (x == y) { + int a = 0; + } + ", + &expect![[r#" + Stmt [5-43] + StmtKind: IfStmt [5-43]: Expr [8-16]: Paren: + Expr [9-15]: BinOp (Eq): + Expr [9-10]: Ident [9-10] "x" + Expr [14-15]: Ident [14-15] "y" + Stmt [27-37] + StmtKind: ClassicalDeclarationStmt [27-37]: ClassicalType [27-30]: IntType [27-30], Ident [31-32] "a", ValueExpression ExprStmt [35-36]: Expr [35-36]: Lit: Int(0)"#]], + ); +} + +#[test] +fn nested_if_stmts() { + check( + parse, + " + if (x == y) { + if (x1 == y1) { + int a = 0; + } else { + int a = 1; + } + } else { + if (x2 == y2) { + int a = 2; + } else { + int a = 3; + } + } + ", + &expect![[r#" + Stmt [5-231] + StmtKind: IfStmt [5-231]: Expr [8-16]: Paren: + Expr [9-15]: BinOp (Eq): + Expr [9-10]: Ident [9-10] "x" + Expr [14-15]: Ident [14-15] "y" + Stmt [27-115] + StmtKind: IfStmt [27-115]: Expr [30-40]: Paren: + Expr [31-39]: BinOp (Eq): + Expr [31-33]: Ident [31-33] "x1" + Expr [37-39]: Ident [37-39] "y1" + Stmt [55-65] + StmtKind: ClassicalDeclarationStmt [55-65]: ClassicalType [55-58]: IntType [55-58], Ident [59-60] "a", ValueExpression ExprStmt [63-64]: Expr [63-64]: Lit: Int(0) + Else: + Stmt [95-105] + StmtKind: ClassicalDeclarationStmt [95-105]: ClassicalType [95-98]: IntType [95-98], Ident [99-100] "a", ValueExpression ExprStmt [103-104]: Expr [103-104]: Lit: Int(1) + Else: + Stmt [137-225] + StmtKind: IfStmt [137-225]: Expr [140-150]: Paren: + Expr [141-149]: BinOp (Eq): + Expr [141-143]: Ident [141-143] "x2" + Expr [147-149]: Ident [147-149] "y2" + Stmt [165-175] + StmtKind: ClassicalDeclarationStmt [165-175]: ClassicalType [165-168]: IntType [165-168], Ident [169-170] "a", ValueExpression ExprStmt [173-174]: Expr [173-174]: Lit: Int(2) + Else: + Stmt [205-215] + StmtKind: ClassicalDeclarationStmt [205-215]: ClassicalType [205-208]: IntType [205-208], Ident [209-210] "a", ValueExpression ExprStmt [213-214]: Expr [213-214]: Lit: Int(3)"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs index 9968592f55..6077fa366d 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs @@ -1,6 +1,5 @@ -use expect_test::expect; - use crate::parser::{stmt::parse_switch_stmt, tests::check}; +use expect_test::expect; #[test] fn simple_switch() { 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..f3bcc6f936 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs @@ -0,0 +1,39 @@ +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[test] +fn simple_while() { + check( + parse, + " + while (x != 2) { + int a = 0; + }", + &expect![[r#" + Stmt [5-46] + StmtKind: WhileLoop [5-46]: Expr [11-19]: Paren: + Expr [12-18]: BinOp (Neq): + Expr [12-13]: Ident [12-13] "x" + Expr [17-18]: Lit: Int(2) + Stmt [30-40] + StmtKind: ClassicalDeclarationStmt [30-40]: ClassicalType [30-33]: IntType [30-33], Ident [34-35] "a", ValueExpression ExprStmt [38-39]: Expr [38-39]: Lit: Int(0)"#]], + ); +} + +#[test] +fn while_stmt_body() { + check( + parse, + " + while (x != 2) + int a = 0;", + &expect![[r#" + Stmt [5-38] + StmtKind: WhileLoop [5-38]: Expr [11-19]: Paren: + Expr [12-18]: BinOp (Neq): + Expr [12-13]: Ident [12-13] "x" + Expr [17-18]: Lit: Int(2) + Stmt [28-38] + StmtKind: ClassicalDeclarationStmt [28-38]: ClassicalType [28-31]: IntType [28-31], Ident [32-33] "a", ValueExpression ExprStmt [36-37]: Expr [36-37]: Lit: Int(0)"#]], + ); +} From c3d754880936630ad1ad3fb5711cc5713e0fac0c Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:15:06 -0800 Subject: [PATCH 12/98] fix range expression --- compiler/qsc_qasm3/src/ast.rs | 6 ++--- .../qsc_qasm3/src/parser/completion/tests.rs | 4 +-- compiler/qsc_qasm3/src/parser/expr.rs | 27 +++++++++++++++---- compiler/qsc_qasm3/src/parser/expr/tests.rs | 26 +++++++++--------- compiler/qsc_qasm3/src/parser/stmt.rs | 3 +-- 5 files changed, 41 insertions(+), 25 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index 2adfea88b5..34efd72a13 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -2137,15 +2137,15 @@ fn display_range(mut indent: Indented, range: &RangeDefinition) -> fm write!(indent, "Range: {}", range.span)?; indent = set_indentation(indent, 1); match &range.start { - Some(e) => write!(indent, "\n{e}")?, + Some(e) => write!(indent, "\nstart: {e}")?, None => write!(indent, "\n")?, } match &range.step { - Some(e) => write!(indent, "\n{e}")?, + Some(e) => write!(indent, "\nstep: {e}")?, None => write!(indent, "\n")?, } match &range.end { - Some(e) => write!(indent, "\n{e}")?, + Some(e) => write!(indent, "\nend: {e}")?, None => write!(indent, "\n")?, } Ok(()) diff --git a/compiler/qsc_qasm3/src/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs index 0917cf49ff..fd22757573 100644 --- a/compiler/qsc_qasm3/src/parser/completion/tests.rs +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -36,7 +36,7 @@ fn begin_document() { "|OPENQASM 3;", &expect![[r#" WordKinds( - Annotation | CReg | Def | Extern | Gate | Include | Input | OpenQASM | Output | Pragma | QReg | Qubit | Return | Switch, + Annotation | CReg | Def | Extern | For | Gate | If | Include | Input | OpenQASM | Output | Pragma | QReg | Qubit | Return | Switch | While, ) "#]], ); @@ -48,7 +48,7 @@ fn end_of_version() { "OPENQASM 3;|", &expect![[r#" WordKinds( - Annotation | CReg | Def | Extern | Gate | Include | Input | Output | Pragma | QReg | Qubit | Return | Switch, + Annotation | CReg | Def | Extern | For | Gate | If | Include | Input | Output | Pragma | QReg | Qubit | Return | Switch | While, ) "#]], ); diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 9e1c4ba6bd..20320facd5 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -484,6 +484,12 @@ fn index_element(s: &mut ParserContext) -> Result { 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)?; @@ -498,11 +504,22 @@ fn index_set_item(s: &mut ParserContext) -> Result { return Ok(IndexSetItem::Expr(expr)); } - let step = opt(s, expr)?; - let end = opt(s, |s| { - token(s, TokenKind::Colon)?; - expr(s) - })?; + // 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 semicolon, the second expression was the step. + let step = end; + let end = opt(s, expr)?; Ok(IndexSetItem::RangeDefinition(RangeDefinition { span: s.span(lo), diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index cf8db7ab75..a547ea8284 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -1044,17 +1044,17 @@ fn index_multiple_ranges() { &expect![[r#" Expr [0-18]: IndexExpr [3-18]: Expr [0-3]: Ident [0-3] "foo", IndexElement: Range: [4-7] - Expr [4-5]: Lit: Int(1) + start: Expr [4-5]: Lit: Int(1) - Expr [6-7]: Lit: Int(5) + end: Expr [6-7]: Lit: Int(5) Range: [9-12] - Expr [9-10]: Lit: Int(3) + start: Expr [9-10]: Lit: Int(3) - Expr [11-12]: Lit: Int(7) + end: Expr [11-12]: Lit: Int(7) Range: [14-17] - Expr [14-15]: Lit: Int(4) + start: Expr [14-15]: Lit: Int(4) - Expr [16-17]: Lit: Int(8)"#]], + end: Expr [16-17]: Lit: Int(8)"#]], ); } @@ -1066,9 +1066,9 @@ fn index_range() { &expect![[r#" Expr [0-10]: IndexExpr [3-10]: Expr [0-3]: Ident [0-3] "foo", IndexElement: Range: [4-9] - Expr [4-5]: Lit: Int(1) - Expr [8-9]: Lit: Int(2) - Expr [6-7]: Lit: Int(5)"#]], + start: Expr [4-5]: Lit: Int(1) + step: Expr [6-7]: Lit: Int(5) + end: Expr [8-9]: Lit: Int(2)"#]], ); } @@ -1094,7 +1094,7 @@ fn index_range_start() { &expect![[r#" Expr [0-7]: IndexExpr [3-7]: Expr [0-3]: Ident [0-3] "foo", IndexElement: Range: [4-6] - Expr [4-5]: Lit: Int(1) + start: Expr [4-5]: Lit: Int(1) "#]], ); @@ -1110,7 +1110,7 @@ fn index_range_end() { Range: [4-6] - Expr [5-6]: Lit: Int(5)"#]], + end: Expr [5-6]: Lit: Int(5)"#]], ); } @@ -1118,12 +1118,12 @@ fn index_range_end() { fn index_range_step() { check( expr, - "foo[::2]", + "foo[:2:]", &expect![[r#" Expr [0-8]: IndexExpr [3-8]: Expr [0-3]: Ident [0-3] "foo", IndexElement: Range: [4-7] - Expr [6-7]: Lit: Int(2) + step: Expr [5-6]: Lit: Int(2) "#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 7400fa38de..098277303b 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -933,14 +933,13 @@ pub fn parse_if_stmt(s: &mut ParserContext) -> Result { }) } +/// 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)?); - - // If no colon, return the expr as a normal index. token(s, TokenKind::Colon)?; // QASM ranges have the pattern [start : (step :)? end] From d4fcdc8b7436a145ad90e57ad8f473c3ec2ccbab Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:21:28 -0800 Subject: [PATCH 13/98] update for loop unit tests --- compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs index c25866968e..2b1b500cca 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs @@ -50,9 +50,9 @@ fn for_loop_range() { &expect![[r#" Stmt [5-52] StmtKind: ForStmt [5-52]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Range: [18-25] - Expr [19-20]: Lit: Int(0) - Expr [21-22]: Lit: Int(2) - Expr [23-24]: Lit: Int(7) + start: Expr [19-20]: Lit: Int(0) + step: Expr [21-22]: Lit: Int(2) + end: Expr [23-24]: Lit: Int(7) Stmt [36-46] StmtKind: ClassicalDeclarationStmt [36-46]: ClassicalType [36-39]: IntType [36-39], Ident [40-41] "a", ValueExpression ExprStmt [44-45]: Expr [44-45]: Lit: Int(0)"#]], ); @@ -69,9 +69,9 @@ fn for_loop_range_no_step() { &expect![[r#" Stmt [5-50] StmtKind: ForStmt [5-50]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Range: [18-23] - Expr [19-20]: Lit: Int(0) + start: Expr [19-20]: Lit: Int(0) - Expr [21-22]: Lit: Int(7) + end: Expr [21-22]: Lit: Int(7) Stmt [34-44] StmtKind: ClassicalDeclarationStmt [34-44]: ClassicalType [34-37]: IntType [34-37], Ident [38-39] "a", ValueExpression ExprStmt [42-43]: Expr [42-43]: Lit: Int(0)"#]], ); From 2eb0759af5d6c76f6ff9529c8db7b429845bfbe3 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 20 Feb 2025 10:28:03 -0800 Subject: [PATCH 14/98] add continue statements --- compiler/qsc_qasm3/src/parser/stmt.rs | 23 +++++++++++++------ .../src/parser/stmt/tests/for_loops.rs | 22 ++++++++++++++++++ .../src/parser/stmt/tests/while_loops.rs | 22 ++++++++++++++++++ 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 098277303b..f005143484 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -4,9 +4,8 @@ #[cfg(test)] pub(crate) mod tests; -use std::rc::Rc; - use qsc_data_structures::span::Span; +use std::rc::Rc; use super::{ completion::WordKinds, @@ -19,9 +18,9 @@ use crate::{ ast::{ list_from_iter, AccessControl, AngleType, Annotation, ArrayBaseTypeKind, ArrayReferenceType, ArrayType, BitType, Block, ClassicalDeclarationStmt, ComplexType, - ConstantDeclaration, DefStmt, EnumerableSet, Expr, ExprStmt, ExternDecl, ExternParameter, - FloatType, ForStmt, IODeclaration, IOKeyword, Ident, Identifier, IfStmt, IncludeStmt, - IntType, List, LiteralKind, Pragma, QuantumGateDefinition, QubitDeclaration, + ConstantDeclaration, ContinueStmt, DefStmt, EnumerableSet, Expr, ExprStmt, ExternDecl, + ExternParameter, FloatType, ForStmt, IODeclaration, IOKeyword, Ident, Identifier, IfStmt, + IncludeStmt, IntType, List, LiteralKind, Pragma, QuantumGateDefinition, QubitDeclaration, RangeDefinition, ReturnStmt, ScalarType, ScalarTypeKind, Stmt, StmtKind, SwitchStmt, TypeDef, TypedParameter, UIntType, WhileLoop, }, @@ -70,8 +69,10 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { Box::new(StmtKind::For(stmt)) } else if let Some(stmt) = opt(s, parse_while_loop)? { Box::new(StmtKind::WhileLoop(stmt)) - } else if let Some(decl) = opt(s, parse_return)? { - Box::new(decl) + } else if let Some(stmt) = opt(s, parse_return)? { + Box::new(stmt) + } else if let Some(stmt) = opt(s, parse_continue_stmt)? { + Box::new(StmtKind::Continue(stmt)) } else { return Err(Error::new(ErrorKind::Rule( "statement", @@ -1016,3 +1017,11 @@ pub fn parse_while_loop(s: &mut ParserContext) -> Result { 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_token(s, TokenKind::Semicolon); + Ok(ContinueStmt { span: s.span(lo) }) +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs index 2b1b500cca..26582b9a7b 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs @@ -92,3 +92,25 @@ fn for_loop_expr() { StmtKind: ClassicalDeclarationStmt [31-41]: ClassicalType [31-34]: IntType [31-34], Ident [35-36] "a", ValueExpression ExprStmt [39-40]: Expr [39-40]: Lit: Int(0)"#]], ); } + +#[test] +fn for_loop_with_continue_stmt() { + check( + parse, + " + for int x in {1, 2, 3} { + int a = 0; + continue; + }", + &expect![[r#" + Stmt [5-72] + StmtKind: ForStmt [5-72]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: + Expr [19-20]: Lit: Int(1) + Expr [22-23]: Lit: Int(2) + Expr [25-26]: Lit: Int(3) + Stmt [38-48] + StmtKind: ClassicalDeclarationStmt [38-48]: ClassicalType [38-41]: IntType [38-41], Ident [42-43] "a", ValueExpression ExprStmt [46-47]: Expr [46-47]: Lit: Int(0) + Stmt [57-66] + StmtKind: Continue [57-66]"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs index f3bcc6f936..19648bb7da 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs @@ -37,3 +37,25 @@ fn while_stmt_body() { StmtKind: ClassicalDeclarationStmt [28-38]: ClassicalType [28-31]: IntType [28-31], Ident [32-33] "a", ValueExpression ExprStmt [36-37]: Expr [36-37]: Lit: Int(0)"#]], ); } + +#[test] +fn while_loop_with_continue_stmt() { + check( + parse, + " + while (x != 2) { + int a = 0; + continue; + }", + &expect![[r#" + Stmt [5-64] + StmtKind: WhileLoop [5-64]: Expr [11-19]: Paren: + Expr [12-18]: BinOp (Neq): + Expr [12-13]: Ident [12-13] "x" + Expr [17-18]: Lit: Int(2) + Stmt [30-40] + StmtKind: ClassicalDeclarationStmt [30-40]: ClassicalType [30-33]: IntType [30-33], Ident [34-35] "a", ValueExpression ExprStmt [38-39]: Expr [38-39]: Lit: Int(0) + Stmt [49-58] + StmtKind: Continue [49-58]"#]], + ); +} From ba14cb8d3257a118c44d20df45103a1b968c5d4a Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 20 Feb 2025 10:29:06 -0800 Subject: [PATCH 15/98] update completions unit tests --- compiler/qsc_qasm3/src/parser/completion/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs index fd22757573..b0f9dd0bf1 100644 --- a/compiler/qsc_qasm3/src/parser/completion/tests.rs +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -36,7 +36,7 @@ fn begin_document() { "|OPENQASM 3;", &expect![[r#" WordKinds( - Annotation | CReg | Def | Extern | For | Gate | If | Include | Input | OpenQASM | Output | Pragma | QReg | Qubit | Return | Switch | While, + Annotation | Continue | CReg | Def | Extern | For | Gate | If | Include | Input | OpenQASM | Output | Pragma | QReg | Qubit | Return | Switch | While, ) "#]], ); @@ -48,7 +48,7 @@ fn end_of_version() { "OPENQASM 3;|", &expect![[r#" WordKinds( - Annotation | CReg | Def | Extern | For | Gate | If | Include | Input | Output | Pragma | QReg | Qubit | Return | Switch | While, + Annotation | Continue | CReg | Def | Extern | For | Gate | If | Include | Input | Output | Pragma | QReg | Qubit | Return | Switch | While, ) "#]], ); From b28ac61d874fa8e9cd7768e38c6206fbe6963996 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 20 Feb 2025 10:36:19 -0800 Subject: [PATCH 16/98] add break statements --- .../qsc_qasm3/src/parser/completion/tests.rs | 4 ++-- compiler/qsc_qasm3/src/parser/stmt.rs | 24 +++++++++++++------ .../src/parser/stmt/tests/for_loops.rs | 22 +++++++++++++++++ .../src/parser/stmt/tests/while_loops.rs | 22 +++++++++++++++++ 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs index b0f9dd0bf1..bd89cafc63 100644 --- a/compiler/qsc_qasm3/src/parser/completion/tests.rs +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -36,7 +36,7 @@ fn begin_document() { "|OPENQASM 3;", &expect![[r#" WordKinds( - Annotation | Continue | CReg | Def | Extern | For | Gate | If | Include | Input | OpenQASM | Output | Pragma | QReg | Qubit | Return | Switch | While, + Annotation | Break | Continue | CReg | Def | Extern | For | Gate | If | Include | Input | OpenQASM | Output | Pragma | QReg | Qubit | Return | Switch | While, ) "#]], ); @@ -48,7 +48,7 @@ fn end_of_version() { "OPENQASM 3;|", &expect![[r#" WordKinds( - Annotation | Continue | CReg | Def | Extern | For | Gate | If | Include | Input | Output | Pragma | QReg | Qubit | Return | Switch | While, + Annotation | Break | Continue | CReg | Def | Extern | For | Gate | If | Include | Input | Output | Pragma | QReg | Qubit | Return | Switch | While, ) "#]], ); diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index f005143484..eaef067ffa 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -17,12 +17,12 @@ use super::{ use crate::{ ast::{ list_from_iter, AccessControl, AngleType, Annotation, ArrayBaseTypeKind, - ArrayReferenceType, ArrayType, BitType, Block, ClassicalDeclarationStmt, ComplexType, - ConstantDeclaration, ContinueStmt, DefStmt, EnumerableSet, Expr, ExprStmt, ExternDecl, - ExternParameter, FloatType, ForStmt, IODeclaration, IOKeyword, Ident, Identifier, IfStmt, - IncludeStmt, IntType, List, LiteralKind, Pragma, QuantumGateDefinition, QubitDeclaration, - RangeDefinition, ReturnStmt, ScalarType, ScalarTypeKind, Stmt, StmtKind, SwitchStmt, - TypeDef, TypedParameter, UIntType, WhileLoop, + ArrayReferenceType, ArrayType, BitType, Block, BreakStmt, ClassicalDeclarationStmt, + ComplexType, ConstantDeclaration, ContinueStmt, DefStmt, EnumerableSet, Expr, ExprStmt, + ExternDecl, ExternParameter, FloatType, ForStmt, IODeclaration, IOKeyword, Ident, + Identifier, IfStmt, IncludeStmt, IntType, List, LiteralKind, Pragma, QuantumGateDefinition, + QubitDeclaration, RangeDefinition, ReturnStmt, ScalarType, ScalarTypeKind, Stmt, StmtKind, + SwitchStmt, TypeDef, TypedParameter, UIntType, WhileLoop, }, keyword::Keyword, lex::{ @@ -73,6 +73,8 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { Box::new(stmt) } else if let Some(stmt) = opt(s, parse_continue_stmt)? { Box::new(StmtKind::Continue(stmt)) + } else if let Some(stmt) = opt(s, parse_break_stmt)? { + Box::new(StmtKind::Break(stmt)) } else { return Err(Error::new(ErrorKind::Rule( "statement", @@ -1018,10 +1020,18 @@ pub fn parse_while_loop(s: &mut ParserContext) -> Result { }) } -/// Grammar: CONTINUE SEMICOLON +/// Grammar: `CONTINUE SEMICOLON`. fn parse_continue_stmt(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; token(s, TokenKind::Keyword(Keyword::Continue))?; recovering_token(s, TokenKind::Semicolon); 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_token(s, TokenKind::Semicolon); + Ok(BreakStmt { span: s.span(lo) }) +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs index 26582b9a7b..04fde906fd 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs @@ -114,3 +114,25 @@ fn for_loop_with_continue_stmt() { StmtKind: Continue [57-66]"#]], ); } + +#[test] +fn for_loop_with_break_stmt() { + check( + parse, + " + for int x in {1, 2, 3} { + int a = 0; + break; + }", + &expect![[r#" + Stmt [5-69] + StmtKind: ForStmt [5-69]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: + Expr [19-20]: Lit: Int(1) + Expr [22-23]: Lit: Int(2) + Expr [25-26]: Lit: Int(3) + Stmt [38-48] + StmtKind: ClassicalDeclarationStmt [38-48]: ClassicalType [38-41]: IntType [38-41], Ident [42-43] "a", ValueExpression ExprStmt [46-47]: Expr [46-47]: Lit: Int(0) + Stmt [57-63] + StmtKind: Break [57-63]"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs index 19648bb7da..921448ce72 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs @@ -59,3 +59,25 @@ fn while_loop_with_continue_stmt() { StmtKind: Continue [49-58]"#]], ); } + +#[test] +fn while_loop_with_break_stmt() { + check( + parse, + " + while (x != 2) { + int a = 0; + break; + }", + &expect![[r#" + Stmt [5-61] + StmtKind: WhileLoop [5-61]: Expr [11-19]: Paren: + Expr [12-18]: BinOp (Neq): + Expr [12-13]: Ident [12-13] "x" + Expr [17-18]: Lit: Int(2) + Stmt [30-40] + StmtKind: ClassicalDeclarationStmt [30-40]: ClassicalType [30-33]: IntType [30-33], Ident [34-35] "a", ValueExpression ExprStmt [38-39]: Expr [38-39]: Lit: Int(0) + Stmt [49-55] + StmtKind: Break [49-55]"#]], + ); +} From 8b2011ff99058d7868657cc4e5c3059793bdd969 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 20 Feb 2025 10:58:25 -0800 Subject: [PATCH 17/98] fix assignment expressions and add expression statements --- .../qsc_qasm3/src/parser/completion/tests.rs | 4 +- compiler/qsc_qasm3/src/parser/expr.rs | 4 + compiler/qsc_qasm3/src/parser/expr/tests.rs | 30 +++--- compiler/qsc_qasm3/src/parser/stmt.rs | 16 +++- .../src/parser/stmt/tests/for_loops.rs | 92 +++++++++++-------- .../src/parser/stmt/tests/while_loops.rs | 56 ++++++----- 6 files changed, 121 insertions(+), 81 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs index bd89cafc63..eaac90607f 100644 --- a/compiler/qsc_qasm3/src/parser/completion/tests.rs +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -36,7 +36,7 @@ fn begin_document() { "|OPENQASM 3;", &expect![[r#" WordKinds( - Annotation | Break | Continue | CReg | Def | Extern | For | Gate | If | Include | Input | OpenQASM | Output | Pragma | QReg | Qubit | Return | Switch | While, + Annotation | Break | Continue | CReg | Def | Extern | False | For | Gate | If | Include | Input | OpenQASM | Output | Pragma | QReg | Qubit | True | Return | Switch | While, ) "#]], ); @@ -48,7 +48,7 @@ fn end_of_version() { "OPENQASM 3;|", &expect![[r#" WordKinds( - Annotation | Break | Continue | CReg | Def | Extern | For | Gate | If | Include | Input | Output | Pragma | QReg | Qubit | Return | Switch | While, + Annotation | Break | Continue | CReg | Def | Extern | False | For | Gate | If | Include | Input | Output | Pragma | QReg | Qubit | True | Return | Switch | While, ) "#]], ); diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 20320facd5..be8c1f1fa6 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -624,6 +624,10 @@ fn infix_op(name: OpName) -> Option { kind: OpKind::Index, precedence: 13, }), + TokenKind::Eq => Some(InfixOp { + kind: OpKind::Assign, + precedence: 0, + }), _ => None, } } diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index a547ea8284..808c3f6781 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -1156,28 +1156,30 @@ fn lit_array() { #[test] fn assignment_and_unop() { check( - crate::parser::stmt::parse, - "bool c = a && !b;", + expr, + "c = a && !b", &expect![[r#" - Stmt [0-17] - StmtKind: ClassicalDeclarationStmt [0-17]: ClassicalType [0-4]: BoolType, Ident [5-6] "c", ValueExpression ExprStmt [9-16]: Expr [9-16]: BinOp (AndL): - Expr [9-10]: Ident [9-10] "a" - Expr [14-16]: UnOp (NotL): - Expr [15-16]: Ident [15-16] "b""#]], + Expr [0-11]: Assign: + Expr [0-1]: Ident [0-1] "c" + Expr [4-11]: BinOp (AndL): + Expr [4-5]: Ident [4-5] "a" + Expr [9-11]: UnOp (NotL): + Expr [10-11]: Ident [10-11] "b""#]], ); } #[test] fn assignment_unop_and() { check( - crate::parser::stmt::parse, - "bool d = !a && b;", + expr, + "d = !a && b", &expect![[r#" - Stmt [0-17] - StmtKind: ClassicalDeclarationStmt [0-17]: ClassicalType [0-4]: BoolType, Ident [5-6] "d", ValueExpression ExprStmt [9-16]: Expr [9-16]: BinOp (AndL): - Expr [9-11]: UnOp (NotL): - Expr [10-11]: Ident [10-11] "a" - Expr [15-16]: Ident [15-16] "b""#]], + Expr [0-11]: Assign: + Expr [0-1]: Ident [0-1] "d" + Expr [4-11]: BinOp (AndL): + Expr [4-6]: UnOp (NotL): + Expr [5-6]: Ident [5-6] "a" + Expr [10-11]: Ident [10-11] "b""#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index eaef067ffa..5e71b5f46f 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -75,6 +75,8 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { Box::new(StmtKind::Continue(stmt)) } else if let Some(stmt) = opt(s, parse_break_stmt)? { Box::new(StmtKind::Break(stmt)) + } else if let Some(stmt) = opt(s, parse_expression_stmt)? { + Box::new(StmtKind::ExprStmt(stmt)) } else { return Err(Error::new(ErrorKind::Rule( "statement", @@ -1024,7 +1026,7 @@ pub fn parse_while_loop(s: &mut ParserContext) -> Result { fn parse_continue_stmt(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; token(s, TokenKind::Keyword(Keyword::Continue))?; - recovering_token(s, TokenKind::Semicolon); + recovering_semi(s); Ok(ContinueStmt { span: s.span(lo) }) } @@ -1032,6 +1034,16 @@ fn parse_continue_stmt(s: &mut ParserContext) -> Result { fn parse_break_stmt(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; token(s, TokenKind::Keyword(Keyword::Break))?; - recovering_token(s, TokenKind::Semicolon); + recovering_semi(s); Ok(BreakStmt { span: s.span(lo) }) } + +fn parse_expression_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + let expr = expr::expr(s)?; + recovering_semi(s); + Ok(ExprStmt { + span: s.span(lo), + expr, + }) +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs index 04fde906fd..1d6aa1f1e9 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs @@ -7,16 +7,18 @@ fn simple_for_loop() { parse, " for int x in {1, 2, 3} { - int a = 0; + a = 0; }", &expect![[r#" - Stmt [5-54] - StmtKind: ForStmt [5-54]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: + Stmt [5-50] + StmtKind: ForStmt [5-50]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: Expr [19-20]: Lit: Int(1) Expr [22-23]: Lit: Int(2) Expr [25-26]: Lit: Int(3) - Stmt [38-48] - StmtKind: ClassicalDeclarationStmt [38-48]: ClassicalType [38-41]: IntType [38-41], Ident [42-43] "a", ValueExpression ExprStmt [46-47]: Expr [46-47]: Lit: Int(0)"#]], + Stmt [38-44] + StmtKind: ExprStmt [38-44]: Expr [38-43]: Assign: + Expr [38-39]: Ident [38-39] "a" + Expr [42-43]: Lit: Int(0)"#]], ); } @@ -26,16 +28,18 @@ fn simple_for_loop_stmt_body() { parse, " for int x in {1, 2, 3} - int a = 0; + a = 0; ", &expect![[r#" - Stmt [5-46] - StmtKind: ForStmt [5-46]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: + Stmt [5-42] + StmtKind: ForStmt [5-42]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: Expr [19-20]: Lit: Int(1) Expr [22-23]: Lit: Int(2) Expr [25-26]: Lit: Int(3) - Stmt [36-46] - StmtKind: ClassicalDeclarationStmt [36-46]: ClassicalType [36-39]: IntType [36-39], Ident [40-41] "a", ValueExpression ExprStmt [44-45]: Expr [44-45]: Lit: Int(0)"#]], + Stmt [36-42] + StmtKind: ExprStmt [36-42]: Expr [36-41]: Assign: + Expr [36-37]: Ident [36-37] "a" + Expr [40-41]: Lit: Int(0)"#]], ); } @@ -45,16 +49,18 @@ fn for_loop_range() { parse, " for int x in [0:2:7] { - int a = 0; + a = 0; }", &expect![[r#" - Stmt [5-52] - StmtKind: ForStmt [5-52]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Range: [18-25] + Stmt [5-48] + StmtKind: ForStmt [5-48]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Range: [18-25] start: Expr [19-20]: Lit: Int(0) step: Expr [21-22]: Lit: Int(2) end: Expr [23-24]: Lit: Int(7) - Stmt [36-46] - StmtKind: ClassicalDeclarationStmt [36-46]: ClassicalType [36-39]: IntType [36-39], Ident [40-41] "a", ValueExpression ExprStmt [44-45]: Expr [44-45]: Lit: Int(0)"#]], + Stmt [36-42] + StmtKind: ExprStmt [36-42]: Expr [36-41]: Assign: + Expr [36-37]: Ident [36-37] "a" + Expr [40-41]: Lit: Int(0)"#]], ); } @@ -64,16 +70,18 @@ fn for_loop_range_no_step() { parse, " for int x in [0:7] { - int a = 0; + a = 0; }", &expect![[r#" - Stmt [5-50] - StmtKind: ForStmt [5-50]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Range: [18-23] + Stmt [5-46] + StmtKind: ForStmt [5-46]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Range: [18-23] start: Expr [19-20]: Lit: Int(0) end: Expr [21-22]: Lit: Int(7) - Stmt [34-44] - StmtKind: ClassicalDeclarationStmt [34-44]: ClassicalType [34-37]: IntType [34-37], Ident [38-39] "a", ValueExpression ExprStmt [42-43]: Expr [42-43]: Lit: Int(0)"#]], + Stmt [34-40] + StmtKind: ExprStmt [34-40]: Expr [34-39]: Assign: + Expr [34-35]: Ident [34-35] "a" + Expr [38-39]: Lit: Int(0)"#]], ); } @@ -83,13 +91,15 @@ fn for_loop_expr() { parse, " for int x in xs { - int a = 0; + a = 0; }", &expect![[r#" - Stmt [5-47] - StmtKind: ForStmt [5-47]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Expr [18-20]: Ident [18-20] "xs" - Stmt [31-41] - StmtKind: ClassicalDeclarationStmt [31-41]: ClassicalType [31-34]: IntType [31-34], Ident [35-36] "a", ValueExpression ExprStmt [39-40]: Expr [39-40]: Lit: Int(0)"#]], + Stmt [5-43] + StmtKind: ForStmt [5-43]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Expr [18-20]: Ident [18-20] "xs" + Stmt [31-37] + StmtKind: ExprStmt [31-37]: Expr [31-36]: Assign: + Expr [31-32]: Ident [31-32] "a" + Expr [35-36]: Lit: Int(0)"#]], ); } @@ -99,19 +109,21 @@ fn for_loop_with_continue_stmt() { parse, " for int x in {1, 2, 3} { - int a = 0; + a = 0; continue; }", &expect![[r#" - Stmt [5-72] - StmtKind: ForStmt [5-72]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: + Stmt [5-68] + StmtKind: ForStmt [5-68]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: Expr [19-20]: Lit: Int(1) Expr [22-23]: Lit: Int(2) Expr [25-26]: Lit: Int(3) - Stmt [38-48] - StmtKind: ClassicalDeclarationStmt [38-48]: ClassicalType [38-41]: IntType [38-41], Ident [42-43] "a", ValueExpression ExprStmt [46-47]: Expr [46-47]: Lit: Int(0) - Stmt [57-66] - StmtKind: Continue [57-66]"#]], + Stmt [38-44] + StmtKind: ExprStmt [38-44]: Expr [38-43]: Assign: + Expr [38-39]: Ident [38-39] "a" + Expr [42-43]: Lit: Int(0) + Stmt [53-62] + StmtKind: Continue [53-62]"#]], ); } @@ -121,18 +133,20 @@ fn for_loop_with_break_stmt() { parse, " for int x in {1, 2, 3} { - int a = 0; + a = 0; break; }", &expect![[r#" - Stmt [5-69] - StmtKind: ForStmt [5-69]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: + Stmt [5-65] + StmtKind: ForStmt [5-65]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: Expr [19-20]: Lit: Int(1) Expr [22-23]: Lit: Int(2) Expr [25-26]: Lit: Int(3) - Stmt [38-48] - StmtKind: ClassicalDeclarationStmt [38-48]: ClassicalType [38-41]: IntType [38-41], Ident [42-43] "a", ValueExpression ExprStmt [46-47]: Expr [46-47]: Lit: Int(0) - Stmt [57-63] - StmtKind: Break [57-63]"#]], + Stmt [38-44] + StmtKind: ExprStmt [38-44]: Expr [38-43]: Assign: + Expr [38-39]: Ident [38-39] "a" + Expr [42-43]: Lit: Int(0) + Stmt [53-59] + StmtKind: Break [53-59]"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs index 921448ce72..9e5bde86a8 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs @@ -7,16 +7,18 @@ fn simple_while() { parse, " while (x != 2) { - int a = 0; + a = 0; }", &expect![[r#" - Stmt [5-46] - StmtKind: WhileLoop [5-46]: Expr [11-19]: Paren: + Stmt [5-42] + StmtKind: WhileLoop [5-42]: Expr [11-19]: Paren: Expr [12-18]: BinOp (Neq): Expr [12-13]: Ident [12-13] "x" Expr [17-18]: Lit: Int(2) - Stmt [30-40] - StmtKind: ClassicalDeclarationStmt [30-40]: ClassicalType [30-33]: IntType [30-33], Ident [34-35] "a", ValueExpression ExprStmt [38-39]: Expr [38-39]: Lit: Int(0)"#]], + Stmt [30-36] + StmtKind: ExprStmt [30-36]: Expr [30-35]: Assign: + Expr [30-31]: Ident [30-31] "a" + Expr [34-35]: Lit: Int(0)"#]], ); } @@ -26,15 +28,17 @@ fn while_stmt_body() { parse, " while (x != 2) - int a = 0;", + a = 0;", &expect![[r#" - Stmt [5-38] - StmtKind: WhileLoop [5-38]: Expr [11-19]: Paren: + Stmt [5-34] + StmtKind: WhileLoop [5-34]: Expr [11-19]: Paren: Expr [12-18]: BinOp (Neq): Expr [12-13]: Ident [12-13] "x" Expr [17-18]: Lit: Int(2) - Stmt [28-38] - StmtKind: ClassicalDeclarationStmt [28-38]: ClassicalType [28-31]: IntType [28-31], Ident [32-33] "a", ValueExpression ExprStmt [36-37]: Expr [36-37]: Lit: Int(0)"#]], + Stmt [28-34] + StmtKind: ExprStmt [28-34]: Expr [28-33]: Assign: + Expr [28-29]: Ident [28-29] "a" + Expr [32-33]: Lit: Int(0)"#]], ); } @@ -44,19 +48,21 @@ fn while_loop_with_continue_stmt() { parse, " while (x != 2) { - int a = 0; + a = 0; continue; }", &expect![[r#" - Stmt [5-64] - StmtKind: WhileLoop [5-64]: Expr [11-19]: Paren: + Stmt [5-60] + StmtKind: WhileLoop [5-60]: Expr [11-19]: Paren: Expr [12-18]: BinOp (Neq): Expr [12-13]: Ident [12-13] "x" Expr [17-18]: Lit: Int(2) - Stmt [30-40] - StmtKind: ClassicalDeclarationStmt [30-40]: ClassicalType [30-33]: IntType [30-33], Ident [34-35] "a", ValueExpression ExprStmt [38-39]: Expr [38-39]: Lit: Int(0) - Stmt [49-58] - StmtKind: Continue [49-58]"#]], + Stmt [30-36] + StmtKind: ExprStmt [30-36]: Expr [30-35]: Assign: + Expr [30-31]: Ident [30-31] "a" + Expr [34-35]: Lit: Int(0) + Stmt [45-54] + StmtKind: Continue [45-54]"#]], ); } @@ -66,18 +72,20 @@ fn while_loop_with_break_stmt() { parse, " while (x != 2) { - int a = 0; + a = 0; break; }", &expect![[r#" - Stmt [5-61] - StmtKind: WhileLoop [5-61]: Expr [11-19]: Paren: + Stmt [5-57] + StmtKind: WhileLoop [5-57]: Expr [11-19]: Paren: Expr [12-18]: BinOp (Neq): Expr [12-13]: Ident [12-13] "x" Expr [17-18]: Lit: Int(2) - Stmt [30-40] - StmtKind: ClassicalDeclarationStmt [30-40]: ClassicalType [30-33]: IntType [30-33], Ident [34-35] "a", ValueExpression ExprStmt [38-39]: Expr [38-39]: Lit: Int(0) - Stmt [49-55] - StmtKind: Break [49-55]"#]], + Stmt [30-36] + StmtKind: ExprStmt [30-36]: Expr [30-35]: Assign: + Expr [30-31]: Ident [30-31] "a" + Expr [34-35]: Lit: Int(0) + Stmt [45-51] + StmtKind: Break [45-51]"#]], ); } From 6fc03cf789f7cce8534301cf6eb6a98d7844da09 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 20 Feb 2025 11:09:57 -0800 Subject: [PATCH 18/98] add end stmt --- compiler/qsc_qasm3/src/ast.rs | 13 +++++++++++++ compiler/qsc_qasm3/src/parser/completion/tests.rs | 4 ++-- compiler/qsc_qasm3/src/parser/stmt.rs | 15 +++++++++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index 34efd72a13..c27e701a7a 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -395,6 +395,7 @@ pub enum StmtKind { DelayStmt(DelayStmt), /// An empty statement. Empty, + End(EndStmt), ExprStmt(ExprStmt), ExternDecl(ExternDecl), For(ForStmt), @@ -435,6 +436,7 @@ impl Display for StmtKind { StmtKind::DefCal(defcal) => write!(f, "{defcal}"), StmtKind::DelayStmt(delay) => write!(f, "{delay}"), StmtKind::Empty => write!(f, "Empty"), + 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}"), @@ -2075,6 +2077,17 @@ impl Display for ContinueStmt { } } +#[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) + } +} + fn display_assign(mut indent: Indented, lhs: &Expr, rhs: &Expr) -> fmt::Result { write!(indent, "Assign:")?; indent = set_indentation(indent, 1); diff --git a/compiler/qsc_qasm3/src/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs index eaac90607f..515b53eec5 100644 --- a/compiler/qsc_qasm3/src/parser/completion/tests.rs +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -36,7 +36,7 @@ fn begin_document() { "|OPENQASM 3;", &expect![[r#" WordKinds( - Annotation | Break | Continue | CReg | Def | Extern | False | For | Gate | If | Include | Input | OpenQASM | Output | Pragma | QReg | Qubit | True | Return | Switch | While, + Annotation | Break | Continue | CReg | Def | End | Extern | False | For | Gate | If | Include | Input | OpenQASM | Output | Pragma | QReg | Qubit | True | Return | Switch | While, ) "#]], ); @@ -48,7 +48,7 @@ fn end_of_version() { "OPENQASM 3;|", &expect![[r#" WordKinds( - Annotation | Break | Continue | CReg | Def | Extern | False | For | Gate | If | Include | Input | Output | Pragma | QReg | Qubit | True | Return | Switch | While, + Annotation | Break | Continue | CReg | Def | End | Extern | False | For | Gate | If | Include | Input | Output | Pragma | QReg | Qubit | True | Return | Switch | While, ) "#]], ); diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 5e71b5f46f..d18a2e49a0 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -18,8 +18,8 @@ use crate::{ ast::{ list_from_iter, AccessControl, AngleType, Annotation, ArrayBaseTypeKind, ArrayReferenceType, ArrayType, BitType, Block, BreakStmt, ClassicalDeclarationStmt, - ComplexType, ConstantDeclaration, ContinueStmt, DefStmt, EnumerableSet, Expr, ExprStmt, - ExternDecl, ExternParameter, FloatType, ForStmt, IODeclaration, IOKeyword, Ident, + ComplexType, ConstantDeclaration, ContinueStmt, DefStmt, EndStmt, EnumerableSet, Expr, + ExprStmt, ExternDecl, ExternParameter, FloatType, ForStmt, IODeclaration, IOKeyword, Ident, Identifier, IfStmt, IncludeStmt, IntType, List, LiteralKind, Pragma, QuantumGateDefinition, QubitDeclaration, RangeDefinition, ReturnStmt, ScalarType, ScalarTypeKind, Stmt, StmtKind, SwitchStmt, TypeDef, TypedParameter, UIntType, WhileLoop, @@ -75,6 +75,8 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { Box::new(StmtKind::Continue(stmt)) } else if let Some(stmt) = opt(s, parse_break_stmt)? { Box::new(StmtKind::Break(stmt)) + } else if let Some(stmt) = opt(s, parse_end_stmt)? { + Box::new(StmtKind::End(stmt)) } else if let Some(stmt) = opt(s, parse_expression_stmt)? { Box::new(StmtKind::ExprStmt(stmt)) } else { @@ -1038,6 +1040,15 @@ fn parse_break_stmt(s: &mut ParserContext) -> Result { 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) -> Result { let lo = s.peek().span.lo; let expr = expr::expr(s)?; From 683d132ad515d9f8c79b780c11796ffbed8243ca Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 20 Feb 2025 12:45:46 -0800 Subject: [PATCH 19/98] add alias declarations --- compiler/qsc_qasm3/src/ast.rs | 24 +++++-------------- .../qsc_qasm3/src/parser/completion/tests.rs | 4 ++-- compiler/qsc_qasm3/src/parser/expr.rs | 16 +++++++++++-- compiler/qsc_qasm3/src/parser/stmt.rs | 18 +++++++++++++- compiler/qsc_qasm3/src/parser/stmt/tests.rs | 1 + .../qsc_qasm3/src/parser/stmt/tests/alias.rs | 24 +++++++++++++++++++ 6 files changed, 64 insertions(+), 23 deletions(-) create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index c27e701a7a..5b5e8405fc 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -326,18 +326,18 @@ impl Display for HardwareQubit { } #[derive(Clone, Debug)] -pub struct Alias { - pub ident: Box, - pub expr: Box>, +pub struct AliasDeclStmt { + pub ident: Identifier, + pub exprs: List, pub span: Span, } -impl Display for Alias { +impl Display for AliasDeclStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut indent = set_indentation(indented(f), 0); write!(indent, "Alias {}: {}", self.span, self.ident)?; indent = set_indentation(indent, 1); - for expr in &*self.expr { + for expr in &*self.exprs { write!(indent, "\n{expr}")?; } Ok(()) @@ -378,7 +378,7 @@ impl Display for AssignOp { /// A statement kind. #[derive(Clone, Debug, Default)] pub enum StmtKind { - Alias(Alias), + Alias(AliasDeclStmt), Assign(Assign), AssignOp(AssignOp), Barrier(BarrierStmt), @@ -629,18 +629,6 @@ impl Display for IndexedIdent { } } -#[derive(Clone, Debug)] -pub struct AliasStmt { - pub span: Span, - pub kind: Box, -} - -impl Display for AliasStmt { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "AliasStmt {}: {}", self.span, self.kind) - } -} - #[derive(Clone, Debug)] pub struct ExprStmt { pub span: Span, diff --git a/compiler/qsc_qasm3/src/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs index 515b53eec5..2abda50e4e 100644 --- a/compiler/qsc_qasm3/src/parser/completion/tests.rs +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -36,7 +36,7 @@ fn begin_document() { "|OPENQASM 3;", &expect![[r#" WordKinds( - Annotation | Break | Continue | CReg | Def | End | Extern | False | For | Gate | If | Include | Input | OpenQASM | Output | Pragma | QReg | Qubit | True | Return | Switch | While, + Annotation | Break | Continue | CReg | Def | End | Extern | False | For | Gate | If | Include | Input | Let | OpenQASM | Output | Pragma | QReg | Qubit | True | Return | Switch | While, ) "#]], ); @@ -48,7 +48,7 @@ fn end_of_version() { "OPENQASM 3;|", &expect![[r#" WordKinds( - Annotation | Break | Continue | CReg | Def | End | Extern | False | For | Gate | If | Include | Input | Output | Pragma | QReg | Qubit | True | Return | Switch | While, + Annotation | Break | Continue | CReg | Def | End | Extern | False | For | Gate | If | Include | Input | Let | Output | Pragma | QReg | Qubit | True | Return | Switch | While, ) "#]], ); diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index be8c1f1fa6..c67fb560de 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -18,8 +18,8 @@ use crate::{ ast::{ self, list_from_iter, AssignExpr, AssignOpExpr, BinOp, BinaryOpExpr, Cast, DiscreteSet, Expr, ExprKind, ExprStmt, FunctionCall, GateOperand, HardwareQubit, Ident, IndexElement, - IndexExpr, IndexSetItem, IndexedIdent, Lit, LiteralKind, MeasureExpr, RangeDefinition, - TypeDef, UnaryOp, ValueExpression, Version, + IndexExpr, IndexSetItem, IndexedIdent, List, Lit, LiteralKind, MeasureExpr, + RangeDefinition, TypeDef, UnaryOp, ValueExpression, Version, }, keyword::Keyword, lex::{ @@ -777,3 +777,15 @@ fn index_operand(s: &mut ParserContext) -> Result { 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: +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/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index d18a2e49a0..9ce6c2fd3a 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -16,7 +16,7 @@ use super::{ }; use crate::{ ast::{ - list_from_iter, AccessControl, AngleType, Annotation, ArrayBaseTypeKind, + list_from_iter, AccessControl, AliasDeclStmt, AngleType, Annotation, ArrayBaseTypeKind, ArrayReferenceType, ArrayType, BitType, Block, BreakStmt, ClassicalDeclarationStmt, ComplexType, ConstantDeclaration, ContinueStmt, DefStmt, EndStmt, EnumerableSet, Expr, ExprStmt, ExternDecl, ExternParameter, FloatType, ForStmt, IODeclaration, IOKeyword, Ident, @@ -79,6 +79,8 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { Box::new(StmtKind::End(stmt)) } else if let Some(stmt) = opt(s, parse_expression_stmt)? { Box::new(StmtKind::ExprStmt(stmt)) + } else if let Some(alias) = opt(s, parse_alias_stmt)? { + Box::new(StmtKind::Alias(alias)) } else { return Err(Error::new(ErrorKind::Rule( "statement", @@ -1058,3 +1060,17 @@ fn parse_expression_stmt(s: &mut ParserContext) -> Result { expr, }) } + +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)?; + + Ok(AliasDeclStmt { + ident, + exprs, + span: s.span(lo), + }) +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests.rs b/compiler/qsc_qasm3/src/parser/stmt/tests.rs index 7fbaeee543..e729691c4f 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +mod alias; mod annotation; mod classical_decl; mod def; 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..3f518e463e --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs @@ -0,0 +1,24 @@ +// 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 alias_decl_stmt() { + check(parse, "let x = a[1:2] ++ b ++ c[1:2:3]", &expect![[r#" + Stmt [0-31] + StmtKind: Alias [0-31]: Ident [4-5] "x" + Expr [8-14]: IndexExpr [9-14]: Expr [8-9]: Ident [8-9] "a", IndexElement: + Range: [10-13] + start: Expr [10-11]: Lit: Int(1) + + end: Expr [12-13]: Lit: Int(2) + Expr [18-19]: Ident [18-19] "b" + Expr [23-31]: IndexExpr [24-31]: Expr [23-24]: Ident [23-24] "c", IndexElement: + Range: [25-30] + start: Expr [25-26]: Lit: Int(1) + step: Expr [27-28]: Lit: Int(2) + end: Expr [29-30]: Lit: Int(3)"#]]); +} From 34c8599d2c06d0960813f4316faec081d51c747d Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 20 Feb 2025 12:48:26 -0800 Subject: [PATCH 20/98] format alias.rs file --- compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs index 3f518e463e..a7fa360090 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs @@ -7,7 +7,10 @@ use expect_test::expect; #[test] fn alias_decl_stmt() { - check(parse, "let x = a[1:2] ++ b ++ c[1:2:3]", &expect![[r#" + check( + parse, + "let x = a[1:2] ++ b ++ c[1:2:3]", + &expect![[r#" Stmt [0-31] StmtKind: Alias [0-31]: Ident [4-5] "x" Expr [8-14]: IndexExpr [9-14]: Expr [8-9]: Ident [8-9] "a", IndexElement: @@ -20,5 +23,6 @@ fn alias_decl_stmt() { Range: [25-30] start: Expr [25-26]: Lit: Int(1) step: Expr [27-28]: Lit: Int(2) - end: Expr [29-30]: Lit: Int(3)"#]]); + end: Expr [29-30]: Lit: Int(3)"#]], + ); } From 89b29b46f2082c7759a4318b35541e346b588df6 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 20 Feb 2025 14:28:15 -0800 Subject: [PATCH 21/98] fixes during PR review with swernli --- compiler/qsc_qasm3/src/parser/expr.rs | 4 +- compiler/qsc_qasm3/src/parser/stmt.rs | 13 ++- .../qsc_qasm3/src/parser/stmt/tests/alias.rs | 28 +++---- .../src/parser/stmt/tests/if_stmt.rs | 82 +++++++++++-------- 4 files changed, 70 insertions(+), 57 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index c67fb560de..f3772a7f36 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -517,7 +517,7 @@ fn index_set_item(s: &mut ParserContext) -> Result { })); } - // If there was a second semicolon, the second expression was the step. + // If there was a second colon, the second expression was the step. let step = end; let end = opt(s, expr)?; @@ -780,7 +780,7 @@ fn index_operand(s: &mut ParserContext) -> Result { /// This expressions are not part of the expression tree /// and are only used in alias statements. -/// Grammar: +/// Grammar: `expression (DOUBLE_PLUS expression)*`. pub fn alias_expr(s: &mut ParserContext) -> Result> { let mut exprs = Vec::new(); exprs.push(expr(s)?); diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 9ce6c2fd3a..6e7014c417 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -919,7 +919,7 @@ fn parse_block_or_stmt(s: &mut ParserContext) -> Result> { } } -/// Grammar ` LPAREN expression RPAREN if_body=statementOrScope (ELSE else_body=statementOrScope)?`. +/// 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; @@ -958,12 +958,9 @@ fn for_loop_range_expr(s: &mut ParserContext) -> Result { // If we find a third expr, then the second expr was the `step`. // and this third expr is the actual `end`. - if let Some(expr) = opt(s, |s| { - token(s, TokenKind::Colon)?; - expr::expr(s) - })? { + if token(s, TokenKind::Colon).is_ok() { step = end; - end = Some(expr); + end = Some(expr::expr(s)?); } recovering_token(s, TokenKind::Close(Delim::Bracket)); @@ -1050,7 +1047,7 @@ fn parse_end_stmt(s: &mut ParserContext) -> Result { Ok(EndStmt { span: s.span(lo) }) } -/// GRAMMAR: `expression SEMICOLON`. +/// Grammar: `expression SEMICOLON`. fn parse_expression_stmt(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; let expr = expr::expr(s)?; @@ -1061,12 +1058,14 @@ fn parse_expression_stmt(s: &mut ParserContext) -> Result { }) } +/// 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, diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs index a7fa360090..65d53fc6c7 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs @@ -9,20 +9,20 @@ use expect_test::expect; fn alias_decl_stmt() { check( parse, - "let x = a[1:2] ++ b ++ c[1:2:3]", + "let x = a[1:2] ++ b ++ c[1:2:3];", &expect![[r#" - Stmt [0-31] - StmtKind: Alias [0-31]: Ident [4-5] "x" - Expr [8-14]: IndexExpr [9-14]: Expr [8-9]: Ident [8-9] "a", IndexElement: - Range: [10-13] - start: Expr [10-11]: Lit: Int(1) - - end: Expr [12-13]: Lit: Int(2) - Expr [18-19]: Ident [18-19] "b" - Expr [23-31]: IndexExpr [24-31]: Expr [23-24]: Ident [23-24] "c", IndexElement: - Range: [25-30] - start: Expr [25-26]: Lit: Int(1) - step: Expr [27-28]: Lit: Int(2) - end: Expr [29-30]: Lit: Int(3)"#]], + Stmt [0-32] + StmtKind: Alias [0-32]: Ident [4-5] "x" + Expr [8-14]: IndexExpr [9-14]: Expr [8-9]: Ident [8-9] "a", IndexElement: + Range: [10-13] + start: Expr [10-11]: Lit: Int(1) + + end: Expr [12-13]: Lit: Int(2) + Expr [18-19]: Ident [18-19] "b" + Expr [23-31]: IndexExpr [24-31]: Expr [23-24]: Ident [23-24] "c", IndexElement: + Range: [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/if_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs index 1e265ba04b..fe1ae6dfdc 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs @@ -7,22 +7,26 @@ fn simple_if_stmt() { parse, " if (x == y) { - int a = 0; + a = 0; } else { - int a = 1; + a = 1; } ", &expect![[r#" - Stmt [5-75] - StmtKind: IfStmt [5-75]: Expr [8-16]: Paren: + Stmt [5-67] + StmtKind: IfStmt [5-67]: Expr [8-16]: Paren: Expr [9-15]: BinOp (Eq): Expr [9-10]: Ident [9-10] "x" Expr [14-15]: Ident [14-15] "y" - Stmt [27-37] - StmtKind: ClassicalDeclarationStmt [27-37]: ClassicalType [27-30]: IntType [27-30], Ident [31-32] "a", ValueExpression ExprStmt [35-36]: Expr [35-36]: Lit: Int(0) + Stmt [27-33] + StmtKind: ExprStmt [27-33]: Expr [27-32]: Assign: + Expr [27-28]: Ident [27-28] "a" + Expr [31-32]: Lit: Int(0) Else: - Stmt [59-69] - StmtKind: ClassicalDeclarationStmt [59-69]: ClassicalType [59-62]: IntType [59-62], Ident [63-64] "a", ValueExpression ExprStmt [67-68]: Expr [67-68]: Lit: Int(1)"#]], + Stmt [55-61] + StmtKind: ExprStmt [55-61]: Expr [55-60]: Assign: + Expr [55-56]: Ident [55-56] "a" + Expr [59-60]: Lit: Int(1)"#]], ); } @@ -32,17 +36,19 @@ fn if_stmt_missing_else() { parse, " if (x == y) { - int a = 0; + a = 0; } ", &expect![[r#" - Stmt [5-43] - StmtKind: IfStmt [5-43]: Expr [8-16]: Paren: + Stmt [5-39] + StmtKind: IfStmt [5-39]: Expr [8-16]: Paren: Expr [9-15]: BinOp (Eq): Expr [9-10]: Ident [9-10] "x" Expr [14-15]: Ident [14-15] "y" - Stmt [27-37] - StmtKind: ClassicalDeclarationStmt [27-37]: ClassicalType [27-30]: IntType [27-30], Ident [31-32] "a", ValueExpression ExprStmt [35-36]: Expr [35-36]: Lit: Int(0)"#]], + Stmt [27-33] + StmtKind: ExprStmt [27-33]: Expr [27-32]: Assign: + Expr [27-28]: Ident [27-28] "a" + Expr [31-32]: Lit: Int(0)"#]], ); } @@ -53,44 +59,52 @@ fn nested_if_stmts() { " if (x == y) { if (x1 == y1) { - int a = 0; + a = 0; } else { - int a = 1; + a = 1; } } else { if (x2 == y2) { - int a = 2; + a = 2; } else { - int a = 3; + a = 3; } } ", &expect![[r#" - Stmt [5-231] - StmtKind: IfStmt [5-231]: Expr [8-16]: Paren: + Stmt [5-215] + StmtKind: IfStmt [5-215]: Expr [8-16]: Paren: Expr [9-15]: BinOp (Eq): Expr [9-10]: Ident [9-10] "x" Expr [14-15]: Ident [14-15] "y" - Stmt [27-115] - StmtKind: IfStmt [27-115]: Expr [30-40]: Paren: + Stmt [27-107] + StmtKind: IfStmt [27-107]: Expr [30-40]: Paren: Expr [31-39]: BinOp (Eq): Expr [31-33]: Ident [31-33] "x1" Expr [37-39]: Ident [37-39] "y1" - Stmt [55-65] - StmtKind: ClassicalDeclarationStmt [55-65]: ClassicalType [55-58]: IntType [55-58], Ident [59-60] "a", ValueExpression ExprStmt [63-64]: Expr [63-64]: Lit: Int(0) + Stmt [55-61] + StmtKind: ExprStmt [55-61]: Expr [55-60]: Assign: + Expr [55-56]: Ident [55-56] "a" + Expr [59-60]: Lit: Int(0) Else: - Stmt [95-105] - StmtKind: ClassicalDeclarationStmt [95-105]: ClassicalType [95-98]: IntType [95-98], Ident [99-100] "a", ValueExpression ExprStmt [103-104]: Expr [103-104]: Lit: Int(1) + Stmt [91-97] + StmtKind: ExprStmt [91-97]: Expr [91-96]: Assign: + Expr [91-92]: Ident [91-92] "a" + Expr [95-96]: Lit: Int(1) Else: - Stmt [137-225] - StmtKind: IfStmt [137-225]: Expr [140-150]: Paren: - Expr [141-149]: BinOp (Eq): - Expr [141-143]: Ident [141-143] "x2" - Expr [147-149]: Ident [147-149] "y2" - Stmt [165-175] - StmtKind: ClassicalDeclarationStmt [165-175]: ClassicalType [165-168]: IntType [165-168], Ident [169-170] "a", ValueExpression ExprStmt [173-174]: Expr [173-174]: Lit: Int(2) + Stmt [129-209] + StmtKind: IfStmt [129-209]: Expr [132-142]: Paren: + Expr [133-141]: BinOp (Eq): + Expr [133-135]: Ident [133-135] "x2" + Expr [139-141]: Ident [139-141] "y2" + Stmt [157-163] + StmtKind: ExprStmt [157-163]: Expr [157-162]: Assign: + Expr [157-158]: Ident [157-158] "a" + Expr [161-162]: Lit: Int(2) Else: - Stmt [205-215] - StmtKind: ClassicalDeclarationStmt [205-215]: ClassicalType [205-208]: IntType [205-208], Ident [209-210] "a", ValueExpression ExprStmt [213-214]: Expr [213-214]: Lit: Int(3)"#]], + Stmt [193-199] + StmtKind: ExprStmt [193-199]: Expr [193-198]: Assign: + Expr [193-194]: Ident [193-194] "a" + Expr [197-198]: Lit: Int(3)"#]], ); } From 9642239cd6f1144a4aa1d8511bad427ed72bac4d Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:57:42 -0800 Subject: [PATCH 22/98] gate calls --- compiler/qsc_qasm3/src/ast.rs | 99 ++++++------- compiler/qsc_qasm3/src/lex/cooked.rs | 58 ++++++-- compiler/qsc_qasm3/src/lex/cooked/tests.rs | 1 + compiler/qsc_qasm3/src/parser/expr.rs | 28 ++-- compiler/qsc_qasm3/src/parser/stmt.rs | 133 ++++++++++++++++-- compiler/qsc_qasm3/src/parser/stmt/tests.rs | 2 + .../src/parser/stmt/tests/classical_decl.rs | 82 +++++------ .../qsc_qasm3/src/parser/stmt/tests/def.rs | 16 +-- .../src/parser/stmt/tests/expr_stmt.rs | 42 ++++++ .../src/parser/stmt/tests/extern_decl.rs | 10 +- .../src/parser/stmt/tests/for_loops.rs | 3 + .../src/parser/stmt/tests/gate_call.rs | 102 ++++++++++++++ .../src/parser/stmt/tests/gate_def.rs | 2 +- .../src/parser/stmt/tests/if_stmt.rs | 3 + .../src/parser/stmt/tests/io_decl.rs | 24 ++-- .../src/parser/stmt/tests/old_style_decl.rs | 4 +- .../src/parser/stmt/tests/quantum_decl.rs | 2 +- .../src/parser/stmt/tests/switch_stmt.rs | 7 +- .../src/parser/stmt/tests/while_loops.rs | 3 + 19 files changed, 448 insertions(+), 173 deletions(-) create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index 5b5e8405fc..dcbf18d2f6 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -506,7 +506,7 @@ impl Display for IfStmt { #[derive(Clone, Debug)] pub struct DelayStmt { pub span: Span, - pub duration: ExprStmt, + pub duration: Expr, } impl Display for DelayStmt { @@ -688,12 +688,31 @@ pub struct RangeDefinition { #[derive(Clone, Debug)] pub struct QuantumGateModifier { pub span: Span, - pub qubit: Box, + pub kind: GateModifierKind, } impl Display for QuantumGateModifier { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "QuantumGateModifier {}: {}", self.span, self.qubit) + 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:?}"), + } } } @@ -738,7 +757,7 @@ impl Display for ClassicalArgument { #[derive(Clone, Debug)] pub enum ExternParameter { Scalar(ScalarType, Span), - Quantum(Option, Span), + Quantum(Option, Span), ArrayReference(ArrayReferenceType, Span), } @@ -845,7 +864,7 @@ impl Display for ArrayBaseTypeKind { #[derive(Clone, Debug)] pub struct IntType { - pub size: Option, + pub size: Option, pub span: Span, } @@ -861,7 +880,7 @@ impl Display for IntType { #[derive(Clone, Debug)] pub struct UIntType { - pub size: Option, + pub size: Option, pub span: Span, } @@ -877,7 +896,7 @@ impl Display for UIntType { #[derive(Clone, Debug)] pub struct FloatType { - pub size: Option, + pub size: Option, pub span: Span, } @@ -909,7 +928,7 @@ impl Display for ComplexType { #[derive(Clone, Debug)] pub struct AngleType { - pub size: Option, + pub size: Option, pub span: Span, } @@ -925,7 +944,7 @@ impl Display for AngleType { #[derive(Clone, Debug)] pub struct BitType { - pub size: Option, + pub size: Option, pub span: Span, } @@ -1015,7 +1034,7 @@ impl Display for AccessControl { #[derive(Clone, Debug)] pub struct QuantumArgument { pub span: Span, - pub expr: Option, + pub expr: Option, } impl Display for QuantumArgument { @@ -1074,7 +1093,7 @@ impl Display for IncludeStmt { pub struct QubitDeclaration { pub span: Span, pub qubit: Box, - pub size: Option, + pub size: Option, } impl Display for QubitDeclaration { @@ -1196,9 +1215,9 @@ pub struct GateCall { pub span: Span, pub modifiers: List, pub name: Identifier, - pub args: List, - pub qubits: List>, - pub duration: Option, + pub args: List, + pub qubits: List, + pub duration: Option, } impl Display for GateCall { @@ -1222,7 +1241,7 @@ impl Display for GateCall { pub struct QuantumPhase { pub span: Span, pub modifiers: List, - pub arg: ExprStmt, + pub arg: Expr, pub qubits: List>, } @@ -1240,7 +1259,7 @@ impl Display for QuantumPhase { #[derive(Clone, Debug)] pub struct DelayInstruction { span: Span, - duration: ExprStmt, + duration: Expr, qubits: List>, } @@ -1258,7 +1277,7 @@ impl Display for DelayInstruction { #[derive(Clone, Debug)] pub struct BoxStmt { pub span: Span, - pub duration: Option, + pub duration: Option, pub body: List, } @@ -1322,7 +1341,7 @@ impl Display for ClassicalDeclarationStmt { #[derive(Clone, Debug)] pub enum ValueExpression { - Expr(ExprStmt), + Expr(Expr), Measurement(MeasureExpr), } @@ -1358,7 +1377,7 @@ pub struct ConstantDeclaration { pub span: Span, pub r#type: TypeDef, pub identifier: Box, - pub init_expr: Box, + pub init_expr: Expr, } impl Display for ConstantDeclaration { @@ -1429,7 +1448,7 @@ impl Display for CalibrationDefinition { #[derive(Clone, Debug)] pub enum CalibrationArgument { Classical(ClassicalArgument), - Expr(ExprStmt), + Expr(Expr), } impl Display for CalibrationArgument { @@ -1444,7 +1463,7 @@ impl Display for CalibrationArgument { #[derive(Clone, Debug)] pub enum TypedParameter { Scalar(ScalarType, Box, Span), - Quantum(Option, Box, Span), + Quantum(Option, Box, Span), ArrayReference(ArrayReferenceType, Box, Span), } @@ -1702,7 +1721,6 @@ pub enum ExprKind { Lit(Lit), FunctionCall(FunctionCall), Cast(Cast), - Concatenation(Concatenation), IndexExpr(IndexExpr), Paren(Expr), } @@ -1718,7 +1736,6 @@ impl Display for ExprKind { ExprKind::Lit(lit) => write!(f, "{lit}"), ExprKind::FunctionCall(call) => write!(f, "{call}"), ExprKind::Cast(cast) => display_cast(indent, cast), - ExprKind::Concatenation(concat) => write!(f, "{concat}"), ExprKind::IndexExpr(index) => write!(f, "{index}"), ExprKind::Assign(expr) => write!(f, "{expr}"), ExprKind::AssignOp(expr) => write!(f, "{expr}"), @@ -1899,23 +1916,6 @@ impl fmt::Display for Version { } } -#[derive(Clone, Debug)] -pub struct Concatenation { - lhs: ExprStmt, - rhs: ExprStmt, -} - -impl Display for Concatenation { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "Concatenation:")?; - indent = set_indentation(indent, 1); - write!(indent, "\n{}", self.lhs)?; - write!(indent, "\n{}", self.rhs)?; - Ok(()) - } -} - #[derive(Clone, Debug)] pub enum IndexElement { DiscreteSet(DiscreteSet), @@ -1984,25 +1984,6 @@ impl Display for AssignmentOp { } } -#[derive(Clone, Debug)] -pub enum GateModifierName { - Inv, - Pow, - Ctrl, - NegCtrl, -} - -impl Display for GateModifierName { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - GateModifierName::Inv => write!(f, "inv"), - GateModifierName::Pow => write!(f, "pow"), - GateModifierName::Ctrl => write!(f, "ctrl"), - GateModifierName::NegCtrl => write!(f, "negctrl"), - } - } -} - #[derive(Clone, Debug)] pub enum IOKeyword { Input, diff --git a/compiler/qsc_qasm3/src/lex/cooked.rs b/compiler/qsc_qasm3/src/lex/cooked.rs index 8838cda2b7..e15544f0e5 100644 --- a/compiler/qsc_qasm3/src/lex/cooked.rs +++ b/compiler/qsc_qasm3/src/lex/cooked.rs @@ -125,6 +125,8 @@ pub enum TokenKind { PlusPlus, /// `->` Arrow, + /// `@` + At, // Operators, ClosedBinOp(ClosedBinOp), @@ -173,6 +175,7 @@ impl Display for TokenKind { 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}`"), @@ -404,6 +407,12 @@ pub(crate) struct Lexer<'a> { // 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> { @@ -415,6 +424,7 @@ impl<'a> Lexer<'a> { .try_into() .expect("input length should fit into u32"), tokens: raw::Lexer::new(input).peekable(), + beginning_of_file: true, } } @@ -503,8 +513,27 @@ impl<'a> Lexer<'a> { hi: token.offset, })) } - raw::TokenKind::Comment(_) | raw::TokenKind::Newline | raw::TokenKind::Whitespace => { - Ok(None) + 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)]; @@ -620,11 +649,14 @@ impl<'a> Lexer<'a> { } } Single::At => { - // AnnotationKeyword: '@' Identifier ('.' Identifier)* -> pushMode(EAT_TO_LINE_END); - let complete = TokenKind::Annotation; - self.expect(raw::TokenKind::Ident, complete); - self.eat_to_end_of_line(); - Ok(complete) + 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) { @@ -739,9 +771,15 @@ impl Iterator for Lexer<'_> { fn next(&mut self) -> Option { while let Some(token) = self.tokens.next() { match self.cook(&token) { - Ok(None) => {} - Ok(Some(token)) => return Some(Ok(token)), - Err(err) => return Some(Err(err)), + 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)); + } } } diff --git a/compiler/qsc_qasm3/src/lex/cooked/tests.rs b/compiler/qsc_qasm3/src/lex/cooked/tests.rs index b025519177..03d169d82f 100644 --- a/compiler/qsc_qasm3/src/lex/cooked/tests.rs +++ b/compiler/qsc_qasm3/src/lex/cooked/tests.rs @@ -40,6 +40,7 @@ fn op_string(kind: TokenKind) -> Option { 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(_) diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index f3772a7f36..c2f4d085fa 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -17,9 +17,9 @@ use qsc_data_structures::span::Span; use crate::{ ast::{ self, list_from_iter, AssignExpr, AssignOpExpr, BinOp, BinaryOpExpr, Cast, DiscreteSet, - Expr, ExprKind, ExprStmt, FunctionCall, GateOperand, HardwareQubit, Ident, IndexElement, - IndexExpr, IndexSetItem, IndexedIdent, List, Lit, LiteralKind, MeasureExpr, - RangeDefinition, TypeDef, UnaryOp, ValueExpression, Version, + Expr, ExprKind, FunctionCall, GateOperand, HardwareQubit, Ident, IndexElement, IndexExpr, + IndexSetItem, IndexedIdent, List, Lit, LiteralKind, MeasureExpr, RangeDefinition, TypeDef, + UnaryOp, ValueExpression, Version, }, keyword::Keyword, lex::{ @@ -673,15 +673,11 @@ fn unescape(s: &str) -> std::result::Result { Ok(buf) } -pub(super) fn designator(s: &mut ParserContext) -> Result { - let lo = s.peek().span.lo; +pub(super) fn designator(s: &mut ParserContext) -> Result { token(s, TokenKind::Open(Delim::Bracket))?; let expr = expr(s)?; - token(s, TokenKind::Close(Delim::Bracket))?; - Ok(ExprStmt { - span: s.span(lo), - expr, - }) + recovering_token(s, TokenKind::Close(Delim::Bracket)); + Ok(expr) } /// A literal array is a list of literal array elements. @@ -708,23 +704,17 @@ fn lit_array_element(s: &mut ParserContext) -> Result { } pub(super) fn value_expr(s: &mut ParserContext) -> Result> { - let lo = s.peek().span.lo; if let Some(measurement) = opt(s, measure_expr)? { return Ok(Box::new(ValueExpression::Measurement(measurement))); } - let expr = if let Some(expr) = opt(s, expr_stmt)? { + let expr = if let Some(expr) = opt(s, expr)? { expr } else { lit_array(s)? }; - let stmt = ExprStmt { - span: s.span(lo), - expr, - }; - - Ok(Box::new(ValueExpression::Expr(stmt))) + Ok(Box::new(ValueExpression::Expr(expr))) } pub(crate) fn expr_list(s: &mut ParserContext) -> Result> { @@ -741,7 +731,7 @@ fn measure_expr(s: &mut ParserContext) -> Result { }) } -fn gate_operand(s: &mut ParserContext) -> Result { +pub(crate) fn gate_operand(s: &mut ParserContext) -> Result { if let Some(indexed_ident) = opt(s, indexed_identifier)? { return Ok(GateOperand::IndexedIdent(Box::new(indexed_ident))); } diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 6e7014c417..7bdaadd8f9 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -10,7 +10,7 @@ use std::rc::Rc; use super::{ completion::WordKinds, error::{Error, ErrorKind}, - expr::{self, designator}, + expr::{self, designator, gate_operand}, prim::{self, barrier, many, opt, recovering, recovering_semi, recovering_token, seq, shorten}, Result, }; @@ -19,10 +19,12 @@ use crate::{ list_from_iter, AccessControl, AliasDeclStmt, AngleType, Annotation, ArrayBaseTypeKind, ArrayReferenceType, ArrayType, BitType, Block, BreakStmt, ClassicalDeclarationStmt, ComplexType, ConstantDeclaration, ContinueStmt, DefStmt, EndStmt, EnumerableSet, Expr, - ExprStmt, ExternDecl, ExternParameter, FloatType, ForStmt, IODeclaration, IOKeyword, Ident, - Identifier, IfStmt, IncludeStmt, IntType, List, LiteralKind, Pragma, QuantumGateDefinition, - QubitDeclaration, RangeDefinition, ReturnStmt, ScalarType, ScalarTypeKind, Stmt, StmtKind, - SwitchStmt, TypeDef, TypedParameter, UIntType, WhileLoop, + ExprKind, ExprStmt, ExternDecl, ExternParameter, FloatType, ForStmt, FunctionCall, + GateCall, GateModifierKind, IODeclaration, IOKeyword, Ident, Identifier, IfStmt, + IncludeStmt, IntType, List, LiteralKind, Pragma, QuantumGateDefinition, + QuantumGateModifier, QuantumStmt, QubitDeclaration, RangeDefinition, ReturnStmt, + ScalarType, ScalarTypeKind, Stmt, StmtKind, SwitchStmt, TypeDef, TypedParameter, UIntType, + WhileLoop, }, keyword::Keyword, lex::{ @@ -77,6 +79,8 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { Box::new(StmtKind::Break(stmt)) } else if let Some(stmt) = opt(s, parse_end_stmt)? { Box::new(StmtKind::End(stmt)) + } else if let Some(stmt_kind) = opt(s, parse_gate_call_stmt)? { + Box::new(stmt_kind) } else if let Some(stmt) = opt(s, parse_expression_stmt)? { Box::new(StmtKind::ExprStmt(stmt)) } else if let Some(alias) = opt(s, parse_alias_stmt)? { @@ -452,7 +456,7 @@ fn parse_quantum_decl(s: &mut ParserContext) -> Result { })) } -fn qubit_type(s: &mut ParserContext<'_>) -> Result> { +fn qubit_type(s: &mut ParserContext<'_>) -> Result> { token(s, TokenKind::Keyword(crate::keyword::Keyword::Qubit))?; let size = opt(s, designator)?; Ok(size) @@ -521,10 +525,7 @@ fn parse_classical_decl(s: &mut ParserContext) -> Result { span: s.span(lo), r#type: ty, identifier, - init_expr: Box::new(ExprStmt { - span: init_expr.span, - expr: init_expr, - }), + init_expr, }; StmtKind::ConstDecl(decl) } else { @@ -657,20 +658,20 @@ fn qreg_decl(s: &mut ParserContext) -> Result { })) } -fn extern_creg_type(s: &mut ParserContext) -> Result> { +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<(Box, Option)> { +fn creg_type(s: &mut ParserContext) -> Result<(Box, Option)> { token(s, TokenKind::Keyword(crate::keyword::Keyword::CReg))?; let name = Box::new(prim::ident(s)?); let size = opt(s, designator)?; Ok((name, size)) } -fn qreg_type(s: &mut ParserContext) -> Result<(Box, Option)> { +fn qreg_type(s: &mut ParserContext) -> Result<(Box, Option)> { token(s, TokenKind::Keyword(crate::keyword::Keyword::QReg))?; let name = Box::new(prim::ident(s)?); let size = opt(s, designator)?; @@ -1073,3 +1074,109 @@ fn parse_alias_stmt(s: &mut ParserContext) -> Result { span: s.span(lo), }) } + +/// In QASM3, it is hard to disambiguate between a quantum-gate-call missing modifiers +/// and expression statements. Consider the following expressions: +/// 1. `Rx(2, 3) q0;` +/// 2. `Rx(2, 3) + q0;` +/// 3. `Rx(2, 3);` +/// 4. `Rx;` +/// +/// (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)?); + + // As explained in the docstring, we parse the gate using the expr parser. + let gate_or_expr = expr::expr(s)?; + + let duration = opt(s, designator)?; + let qubits = list_from_iter(many(s, gate_operand)?); + recovering_semi(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(ExprStmt { + span: s.span(lo), + expr: gate_or_expr, + })); + } + + let (name, args) = match *gate_or_expr.kind { + ExprKind::FunctionCall(FunctionCall { name, args, .. }) => (name, args), + ExprKind::Ident(ident) => (Identifier::Ident(Box::new(ident)), Default::default()), + _ => { + return Err(Error::new(ErrorKind::ExpectedItem( + TokenKind::Identifier, + gate_or_expr.span, + ))) + } + }; + + let quantum_stmt = QuantumStmt { + span: s.span(lo), + kind: crate::ast::QuantumStmtKind::Gate(GateCall { + span: s.span(lo), + modifiers, + name, + args, + qubits, + duration, + }), + }; + + Ok(StmtKind::Quantum(quantum_stmt)) +} + +/// 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, + }) +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests.rs b/compiler/qsc_qasm3/src/parser/stmt/tests.rs index e729691c4f..14fcea83ad 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests.rs @@ -5,8 +5,10 @@ mod alias; mod annotation; mod classical_decl; mod def; +mod expr_stmt; mod extern_decl; mod for_loops; +mod gate_call; mod gate_def; mod if_stmt; mod io_decl; diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs index 6159810966..4642879f27 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs @@ -25,7 +25,7 @@ fn bit_decl_bit_lit() { "bit b = 1;", &expect![[r#" Stmt [0-10] - StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-3]: BitType, Ident [4-5] "b", ValueExpression ExprStmt [8-9]: Expr [8-9]: Lit: Int(1)"#]], + StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-3]: BitType, Ident [4-5] "b", ValueExpression Expr [8-9]: Lit: Int(1)"#]], ); } @@ -36,7 +36,7 @@ fn const_bit_decl_bit_lit() { "const bit b = 1;", &expect![[r#" Stmt [0-16] - StmtKind: ConstantDeclaration [0-16]: ClassicalType [6-9]: BitType, Ident [10-11] "b", ExprStmt [14-15]: Expr [14-15]: Lit: Int(1)"#]], + StmtKind: ConstantDeclaration [0-16]: ClassicalType [6-9]: BitType, Ident [10-11] "b", Expr [14-15]: Lit: Int(1)"#]], ); } @@ -47,7 +47,7 @@ fn bit_array_decl() { "bit[2] b;", &expect![[r#" Stmt [0-9] - StmtKind: ClassicalDeclarationStmt [0-9]: ClassicalType [0-6]: BitType [0-6]: ExprStmt [3-6]: Expr [4-5]: Lit: Int(2), Ident [7-8] "b""#]], + StmtKind: ClassicalDeclarationStmt [0-9]: ClassicalType [0-6]: BitType [0-6]: Expr [4-5]: Lit: Int(2), Ident [7-8] "b""#]], ); } @@ -58,7 +58,7 @@ fn bit_array_decl_bit_lit() { "bit[2] b = \"11\";", &expect![[r#" Stmt [0-16] - StmtKind: ClassicalDeclarationStmt [0-16]: ClassicalType [0-6]: BitType [0-6]: ExprStmt [3-6]: Expr [4-5]: Lit: Int(2), Ident [7-8] "b", ValueExpression ExprStmt [11-15]: Expr [11-15]: Lit: Bitstring("11")"#]], + StmtKind: ClassicalDeclarationStmt [0-16]: ClassicalType [0-6]: BitType [0-6]: Expr [4-5]: Lit: Int(2), Ident [7-8] "b", ValueExpression Expr [11-15]: Lit: Bitstring("11")"#]], ); } @@ -69,7 +69,7 @@ fn const_bit_array_decl_bit_lit() { "const bit[2] b = \"11\";", &expect![[r#" Stmt [0-22] - StmtKind: ConstantDeclaration [0-22]: ClassicalType [6-12]: BitType [6-12]: ExprStmt [9-12]: Expr [10-11]: Lit: Int(2), Ident [13-14] "b", ExprStmt [17-21]: Expr [17-21]: Lit: Bitstring("11")"#]], + StmtKind: ConstantDeclaration [0-22]: ClassicalType [6-12]: BitType [6-12]: Expr [10-11]: Lit: Int(2), Ident [13-14] "b", Expr [17-21]: Lit: Bitstring("11")"#]], ); } @@ -91,7 +91,7 @@ fn bool_decl_int_lit() { "bool b = 1;", &expect![[r#" Stmt [0-11] - StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-4]: BoolType, Ident [5-6] "b", ValueExpression ExprStmt [9-10]: Expr [9-10]: Lit: Int(1)"#]], + StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-4]: BoolType, Ident [5-6] "b", ValueExpression Expr [9-10]: Lit: Int(1)"#]], ); } @@ -102,7 +102,7 @@ fn const_bool_decl_bool_lit() { "const bool b = true;", &expect![[r#" Stmt [0-20] - StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-10]: BoolType, Ident [11-12] "b", ExprStmt [15-19]: Expr [15-19]: Lit: Bool(true)"#]], + StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-10]: BoolType, Ident [11-12] "b", Expr [15-19]: Lit: Bool(true)"#]], ); } @@ -124,7 +124,7 @@ fn complex_decl_complex_lit() { "complex c = 1im;", &expect![[r#" Stmt [0-16] - StmtKind: ClassicalDeclarationStmt [0-16]: ClassicalType [0-7]: ComplexType [0-7], Ident [8-9] "c", ValueExpression ExprStmt [12-15]: Expr [12-15]: Lit: Imaginary(1.0)"#]], + StmtKind: ClassicalDeclarationStmt [0-16]: ClassicalType [0-7]: ComplexType [0-7], Ident [8-9] "c", ValueExpression Expr [12-15]: Lit: Imaginary(1.0)"#]], ); } @@ -135,7 +135,7 @@ fn const_complex_decl_complex_lit() { "const complex c = 1im;", &expect![[r#" Stmt [0-22] - StmtKind: ConstantDeclaration [0-22]: ClassicalType [6-13]: ComplexType [6-13], Ident [14-15] "c", ExprStmt [18-21]: Expr [18-21]: Lit: Imaginary(1.0)"#]], + StmtKind: ConstantDeclaration [0-22]: ClassicalType [6-13]: ComplexType [6-13], Ident [14-15] "c", Expr [18-21]: Lit: Imaginary(1.0)"#]], ); } @@ -173,7 +173,7 @@ fn complex_sized_decl() { "complex[float[32]] c;", &expect![[r#" Stmt [0-21] - StmtKind: ClassicalDeclarationStmt [0-21]: ClassicalType [0-18]: ComplexType[float[FloatType[ExprStmt [13-17]: Expr [14-16]: Lit: Int(32)]: [8-17]]]: [0-18], Ident [19-20] "c""#]], + StmtKind: ClassicalDeclarationStmt [0-21]: ClassicalType [0-18]: ComplexType[float[FloatType[Expr [14-16]: Lit: Int(32)]: [8-17]]]: [0-18], Ident [19-20] "c""#]], ); } @@ -206,7 +206,7 @@ fn complex_sized_decl_complex_lit() { "complex[float[32]] c = 1im;", &expect![[r#" Stmt [0-27] - StmtKind: ClassicalDeclarationStmt [0-27]: ClassicalType [0-18]: ComplexType[float[FloatType[ExprStmt [13-17]: Expr [14-16]: Lit: Int(32)]: [8-17]]]: [0-18], Ident [19-20] "c", ValueExpression ExprStmt [23-26]: Expr [23-26]: Lit: Imaginary(1.0)"#]], + StmtKind: ClassicalDeclarationStmt [0-27]: ClassicalType [0-18]: ComplexType[float[FloatType[Expr [14-16]: Lit: Int(32)]: [8-17]]]: [0-18], Ident [19-20] "c", ValueExpression Expr [23-26]: Lit: Imaginary(1.0)"#]], ); } @@ -217,7 +217,7 @@ fn const_complex_sized_decl_complex_lit() { "const complex[float[32]] c = 1im;", &expect![[r#" Stmt [0-33] - StmtKind: ConstantDeclaration [0-33]: ClassicalType [6-24]: ComplexType[float[FloatType[ExprStmt [19-23]: Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24], Ident [25-26] "c", ExprStmt [29-32]: Expr [29-32]: Lit: Imaginary(1.0)"#]], + StmtKind: ConstantDeclaration [0-33]: ClassicalType [6-24]: ComplexType[float[FloatType[Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24], Ident [25-26] "c", Expr [29-32]: Lit: Imaginary(1.0)"#]], ); } @@ -239,7 +239,7 @@ fn int_decl_int_lit() { "int i = 1;", &expect![[r#" Stmt [0-10] - StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-3]: IntType [0-3], Ident [4-5] "i", ValueExpression ExprStmt [8-9]: Expr [8-9]: Lit: Int(1)"#]], + StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-3]: IntType [0-3], Ident [4-5] "i", ValueExpression Expr [8-9]: Lit: Int(1)"#]], ); } @@ -250,7 +250,7 @@ fn const_int_decl_int_lit() { "const int i = 1;", &expect![[r#" Stmt [0-16] - StmtKind: ConstantDeclaration [0-16]: ClassicalType [6-9]: IntType [6-9], Ident [10-11] "i", ExprStmt [14-15]: Expr [14-15]: Lit: Int(1)"#]], + StmtKind: ConstantDeclaration [0-16]: ClassicalType [6-9]: IntType [6-9], Ident [10-11] "i", Expr [14-15]: Lit: Int(1)"#]], ); } @@ -261,7 +261,7 @@ fn int_sized_decl() { "int[32] i;", &expect![[r#" Stmt [0-10] - StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-7]: IntType[ExprStmt [3-7]: Expr [4-6]: Lit: Int(32)]: [0-7], Ident [8-9] "i""#]], + StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-7]: IntType[Expr [4-6]: Lit: Int(32)]: [0-7], Ident [8-9] "i""#]], ); } @@ -272,7 +272,7 @@ fn int_sized_decl_int_lit() { "int[32] i = 1;", &expect![[r#" Stmt [0-14] - StmtKind: ClassicalDeclarationStmt [0-14]: ClassicalType [0-7]: IntType[ExprStmt [3-7]: Expr [4-6]: Lit: Int(32)]: [0-7], Ident [8-9] "i", ValueExpression ExprStmt [12-13]: Expr [12-13]: Lit: Int(1)"#]], + StmtKind: ClassicalDeclarationStmt [0-14]: ClassicalType [0-7]: IntType[Expr [4-6]: Lit: Int(32)]: [0-7], Ident [8-9] "i", ValueExpression Expr [12-13]: Lit: Int(1)"#]], ); } @@ -283,7 +283,7 @@ fn const_int_sized_decl_int_lit() { "const int[32] i = 1;", &expect![[r#" Stmt [0-20] - StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-13]: IntType[ExprStmt [9-13]: Expr [10-12]: Lit: Int(32)]: [6-13], Ident [14-15] "i", ExprStmt [18-19]: Expr [18-19]: Lit: Int(1)"#]], + StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-13]: IntType[Expr [10-12]: Lit: Int(32)]: [6-13], Ident [14-15] "i", Expr [18-19]: Lit: Int(1)"#]], ); } @@ -305,7 +305,7 @@ fn uint_decl_uint_lit() { "uint i = 1;", &expect![[r#" Stmt [0-11] - StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-4]: UIntType [0-4], Ident [5-6] "i", ValueExpression ExprStmt [9-10]: Expr [9-10]: Lit: Int(1)"#]], + StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-4]: UIntType [0-4], Ident [5-6] "i", ValueExpression Expr [9-10]: Lit: Int(1)"#]], ); } @@ -316,7 +316,7 @@ fn const_uint_decl_uint_lit() { "const uint i = 1;", &expect![[r#" Stmt [0-17] - StmtKind: ConstantDeclaration [0-17]: ClassicalType [6-10]: UIntType [6-10], Ident [11-12] "i", ExprStmt [15-16]: Expr [15-16]: Lit: Int(1)"#]], + StmtKind: ConstantDeclaration [0-17]: ClassicalType [6-10]: UIntType [6-10], Ident [11-12] "i", Expr [15-16]: Lit: Int(1)"#]], ); } @@ -327,7 +327,7 @@ fn uint_sized_decl() { "uint[32] i;", &expect![[r#" Stmt [0-11] - StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-8]: UIntType[ExprStmt [4-8]: Expr [5-7]: Lit: Int(32)]: [0-8], Ident [9-10] "i""#]], + StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-8]: UIntType[Expr [5-7]: Lit: Int(32)]: [0-8], Ident [9-10] "i""#]], ); } @@ -338,7 +338,7 @@ fn uint_sized_decl_uint_lit() { "uint[32] i = 1;", &expect![[r#" Stmt [0-15] - StmtKind: ClassicalDeclarationStmt [0-15]: ClassicalType [0-8]: UIntType[ExprStmt [4-8]: Expr [5-7]: Lit: Int(32)]: [0-8], Ident [9-10] "i", ValueExpression ExprStmt [13-14]: Expr [13-14]: Lit: Int(1)"#]], + StmtKind: ClassicalDeclarationStmt [0-15]: ClassicalType [0-8]: UIntType[Expr [5-7]: Lit: Int(32)]: [0-8], Ident [9-10] "i", ValueExpression Expr [13-14]: Lit: Int(1)"#]], ); } @@ -349,7 +349,7 @@ fn const_uint_sized_decl_uint_lit() { "const uint[32] i = 1;", &expect![[r#" Stmt [0-21] - StmtKind: ConstantDeclaration [0-21]: ClassicalType [6-14]: UIntType[ExprStmt [10-14]: Expr [11-13]: Lit: Int(32)]: [6-14], Ident [15-16] "i", ExprStmt [19-20]: Expr [19-20]: Lit: Int(1)"#]], + StmtKind: ConstantDeclaration [0-21]: ClassicalType [6-14]: UIntType[Expr [11-13]: Lit: Int(32)]: [6-14], Ident [15-16] "i", Expr [19-20]: Lit: Int(1)"#]], ); } @@ -371,7 +371,7 @@ fn float_decl_float_lit() { "float f = 1;", &expect![[r#" Stmt [0-12] - StmtKind: ClassicalDeclarationStmt [0-12]: ClassicalType [0-5]: FloatType [0-5], Ident [6-7] "f", ValueExpression ExprStmt [10-11]: Expr [10-11]: Lit: Int(1)"#]], + StmtKind: ClassicalDeclarationStmt [0-12]: ClassicalType [0-5]: FloatType [0-5], Ident [6-7] "f", ValueExpression Expr [10-11]: Lit: Int(1)"#]], ); } @@ -382,7 +382,7 @@ fn const_float_decl_float_lit() { "const float f = 1.0;", &expect![[r#" Stmt [0-20] - StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-11]: FloatType [6-11], Ident [12-13] "f", ExprStmt [16-19]: Expr [16-19]: Lit: Float(1.0)"#]], + StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-11]: FloatType [6-11], Ident [12-13] "f", Expr [16-19]: Lit: Float(1.0)"#]], ); } @@ -393,7 +393,7 @@ fn float_sized_decl() { "float[32] f;", &expect![[r#" Stmt [0-12] - StmtKind: ClassicalDeclarationStmt [0-12]: ClassicalType [0-9]: FloatType[ExprStmt [5-9]: Expr [6-8]: Lit: Int(32)]: [0-9], Ident [10-11] "f""#]], + StmtKind: ClassicalDeclarationStmt [0-12]: ClassicalType [0-9]: FloatType[Expr [6-8]: Lit: Int(32)]: [0-9], Ident [10-11] "f""#]], ); } @@ -404,7 +404,7 @@ fn float_sized_decl_float_lit() { "float[32] f = 1.0;", &expect![[r#" Stmt [0-18] - StmtKind: ClassicalDeclarationStmt [0-18]: ClassicalType [0-9]: FloatType[ExprStmt [5-9]: Expr [6-8]: Lit: Int(32)]: [0-9], Ident [10-11] "f", ValueExpression ExprStmt [14-17]: Expr [14-17]: Lit: Float(1.0)"#]], + StmtKind: ClassicalDeclarationStmt [0-18]: ClassicalType [0-9]: FloatType[Expr [6-8]: Lit: Int(32)]: [0-9], Ident [10-11] "f", ValueExpression Expr [14-17]: Lit: Float(1.0)"#]], ); } @@ -415,7 +415,7 @@ fn const_float_sized_decl_float_lit() { "const float[32] f = 1;", &expect![[r#" Stmt [0-22] - StmtKind: ConstantDeclaration [0-22]: ClassicalType [6-15]: FloatType[ExprStmt [11-15]: Expr [12-14]: Lit: Int(32)]: [6-15], Ident [16-17] "f", ExprStmt [20-21]: Expr [20-21]: Lit: Int(1)"#]], + StmtKind: ConstantDeclaration [0-22]: ClassicalType [6-15]: FloatType[Expr [12-14]: Lit: Int(32)]: [6-15], Ident [16-17] "f", Expr [20-21]: Lit: Int(1)"#]], ); } @@ -437,7 +437,7 @@ fn angle_decl_angle_lit() { "angle a = 1.0;", &expect![[r#" Stmt [0-14] - StmtKind: ClassicalDeclarationStmt [0-14]: ClassicalType [0-5]: AngleType [0-5], Ident [6-7] "a", ValueExpression ExprStmt [10-13]: Expr [10-13]: Lit: Float(1.0)"#]], + StmtKind: ClassicalDeclarationStmt [0-14]: ClassicalType [0-5]: AngleType [0-5], Ident [6-7] "a", ValueExpression Expr [10-13]: Lit: Float(1.0)"#]], ); } @@ -468,7 +468,7 @@ fn const_angle_decl_angle_lit() { "const angle a = 1.0;", &expect![[r#" Stmt [0-20] - StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-11]: AngleType [6-11], Ident [12-13] "a", ExprStmt [16-19]: Expr [16-19]: Lit: Float(1.0)"#]], + StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-11]: AngleType [6-11], Ident [12-13] "a", Expr [16-19]: Lit: Float(1.0)"#]], ); } @@ -479,7 +479,7 @@ fn angle_sized_decl() { "angle[32] a;", &expect![[r#" Stmt [0-12] - StmtKind: ClassicalDeclarationStmt [0-12]: ClassicalType [0-9]: AngleType [0-9]: ExprStmt [5-9]: Expr [6-8]: Lit: Int(32), Ident [10-11] "a""#]], + StmtKind: ClassicalDeclarationStmt [0-12]: ClassicalType [0-9]: AngleType [0-9]: Expr [6-8]: Lit: Int(32), Ident [10-11] "a""#]], ); } @@ -490,7 +490,7 @@ fn angle_sized_decl_angle_lit() { "angle[32] a = 1.0;", &expect![[r#" Stmt [0-18] - StmtKind: ClassicalDeclarationStmt [0-18]: ClassicalType [0-9]: AngleType [0-9]: ExprStmt [5-9]: Expr [6-8]: Lit: Int(32), Ident [10-11] "a", ValueExpression ExprStmt [14-17]: Expr [14-17]: Lit: Float(1.0)"#]], + StmtKind: ClassicalDeclarationStmt [0-18]: ClassicalType [0-9]: AngleType [0-9]: Expr [6-8]: Lit: Int(32), Ident [10-11] "a", ValueExpression Expr [14-17]: Lit: Float(1.0)"#]], ); } @@ -501,7 +501,7 @@ fn const_angle_sized_decl_angle_lit() { "const angle[32] a = 1.0;", &expect![[r#" Stmt [0-24] - StmtKind: ConstantDeclaration [0-24]: ClassicalType [6-15]: AngleType [6-15]: ExprStmt [11-15]: Expr [12-14]: Lit: Int(32), Ident [16-17] "a", ExprStmt [20-23]: Expr [20-23]: Lit: Float(1.0)"#]], + StmtKind: ConstantDeclaration [0-24]: ClassicalType [6-15]: AngleType [6-15]: Expr [12-14]: Lit: Int(32), Ident [16-17] "a", Expr [20-23]: Lit: Float(1.0)"#]], ); } @@ -677,7 +677,7 @@ fn empty_array_decl() { &expect![[r#" Stmt [0-23] StmtKind: ClassicalDeclarationStmt [0-23]: ArrayType [0-13]: ArrayBaseTypeKind IntType [6-9] - Expr [11-12]: Lit: Int(0), Ident [14-17] "arr", ValueExpression ExprStmt [20-22]: Expr [20-22]: Lit: Array:"#]], + Expr [11-12]: Lit: Int(0), Ident [14-17] "arr", ValueExpression Expr [20-22]: Lit: Array:"#]], ); } @@ -687,12 +687,12 @@ fn simple_array_decl() { parse, "array[int[32], 3] arr = {1, 2, 3};", &expect![[r#" - Stmt [0-34] - StmtKind: ClassicalDeclarationStmt [0-34]: ArrayType [0-17]: ArrayBaseTypeKind IntType[ExprStmt [9-13]: Expr [10-12]: Lit: Int(32)]: [6-13] - Expr [15-16]: Lit: Int(3), Ident [18-21] "arr", ValueExpression ExprStmt [24-33]: Expr [24-33]: Lit: Array: - Expr { span: Span { lo: 25, hi: 26 }, kind: Lit(Lit { span: Span { lo: 25, hi: 26 }, kind: Int(1) }) } - Expr { span: Span { lo: 28, hi: 29 }, kind: Lit(Lit { span: Span { lo: 28, hi: 29 }, kind: Int(2) }) } - Expr { span: Span { lo: 31, hi: 32 }, kind: Lit(Lit { span: Span { lo: 31, hi: 32 }, kind: Int(3) }) }"#]], + Stmt [0-34] + StmtKind: ClassicalDeclarationStmt [0-34]: ArrayType [0-17]: ArrayBaseTypeKind IntType[Expr [10-12]: Lit: Int(32)]: [6-13] + Expr [15-16]: Lit: Int(3), Ident [18-21] "arr", ValueExpression Expr [24-33]: Lit: Array: + Expr { span: Span { lo: 25, hi: 26 }, kind: Lit(Lit { span: Span { lo: 25, hi: 26 }, kind: Int(1) }) } + Expr { span: Span { lo: 28, hi: 29 }, kind: Lit(Lit { span: Span { lo: 28, hi: 29 }, kind: Int(2) }) } + Expr { span: Span { lo: 31, hi: 32 }, kind: Lit(Lit { span: Span { lo: 31, hi: 32 }, kind: Int(3) }) }"#]], ); } @@ -703,9 +703,9 @@ fn nested_array_decl() { "array[int[32], 3, 2] arr = {{1, 2}, {3, 4}, {5, 6}};", &expect![[r#" Stmt [0-52] - StmtKind: ClassicalDeclarationStmt [0-52]: ArrayType [0-20]: ArrayBaseTypeKind IntType[ExprStmt [9-13]: Expr [10-12]: Lit: Int(32)]: [6-13] + StmtKind: ClassicalDeclarationStmt [0-52]: ArrayType [0-20]: ArrayBaseTypeKind IntType[Expr [10-12]: Lit: Int(32)]: [6-13] Expr [15-16]: Lit: Int(3) - Expr [18-19]: Lit: Int(2), Ident [21-24] "arr", ValueExpression ExprStmt [27-51]: Expr [27-51]: Lit: Array: + Expr [18-19]: Lit: Int(2), Ident [21-24] "arr", ValueExpression Expr [27-51]: Lit: Array: Expr { span: Span { lo: 28, hi: 34 }, kind: Lit(Lit { span: Span { lo: 28, hi: 34 }, kind: Array([Expr { span: Span { lo: 29, hi: 30 }, kind: Lit(Lit { span: Span { lo: 29, hi: 30 }, kind: Int(1) }) }, Expr { span: Span { lo: 32, hi: 33 }, kind: Lit(Lit { span: Span { lo: 32, hi: 33 }, kind: Int(2) }) }]) }) } Expr { span: Span { lo: 36, hi: 42 }, kind: Lit(Lit { span: Span { lo: 36, hi: 42 }, kind: Array([Expr { span: Span { lo: 37, hi: 38 }, kind: Lit(Lit { span: Span { lo: 37, hi: 38 }, kind: Int(3) }) }, Expr { span: Span { lo: 40, hi: 41 }, kind: Lit(Lit { span: Span { lo: 40, hi: 41 }, kind: Int(4) }) }]) }) } Expr { span: Span { lo: 44, hi: 50 }, kind: Lit(Lit { span: Span { lo: 44, hi: 50 }, kind: Array([Expr { span: Span { lo: 45, hi: 46 }, kind: Lit(Lit { span: Span { lo: 45, hi: 46 }, kind: Int(5) }) }, Expr { span: Span { lo: 48, hi: 49 }, kind: Lit(Lit { span: Span { lo: 48, hi: 49 }, kind: Int(6) }) }]) }) }"#]], diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs index a67bd8b88d..d9f82d4b77 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs @@ -91,9 +91,9 @@ fn classical_subroutine() { "def square(int[32] x) -> int { return x ** 2; }", &expect![[r#" Stmt [0-47] - StmtKind: DefStmt [0-47]: Ident [4-10] "square"([11-20] Ident [19-20] "x": ClassicalType [11-18]: IntType[ExprStmt [14-18]: Expr [15-17]: Lit: Int(32)]: [11-18]) + StmtKind: DefStmt [0-47]: Ident [4-10] "square"([11-20] Ident [19-20] "x": ClassicalType [11-18]: IntType[Expr [15-17]: Lit: Int(32)]: [11-18]) Stmt [31-45] - StmtKind: ReturnStmt [31-45]: ValueExpression ExprStmt [38-44]: Expr [38-44]: BinOp (Exp): + StmtKind: ReturnStmt [31-45]: ValueExpression Expr [38-44]: BinOp (Exp): Expr [38-39]: Ident [38-39] "x" Expr [43-44]: Lit: Int(2) ClassicalType [25-28]: IntType [25-28]"#]], @@ -107,7 +107,7 @@ fn quantum_args() { "def x(qubit q, qubit[n] qubits) { }", &expect![[r#" Stmt [0-35] - StmtKind: DefStmt [0-35]: Ident [4-5] "x"([6-13] Ident [12-13] "q": qubit, [15-30] Ident [24-30] "qubits": qubit[ExprStmt [20-23]: Expr [21-22]: Ident [21-22] "n"]) "#]], + StmtKind: DefStmt [0-35]: Ident [4-5] "x"([6-13] Ident [12-13] "q": qubit, [15-30] Ident [24-30] "qubits": qubit[Expr [21-22]: Ident [21-22] "n"]) "#]], ); } @@ -118,9 +118,9 @@ fn old_style_args() { "def test(creg c, qreg q, creg c2[2], qreg q4[4]) -> int { return x ** 2; }", &expect![[r#" Stmt [0-74] - StmtKind: DefStmt [0-74]: Ident [4-8] "test"([9-15] Ident [14-15] "c": ClassicalType [9-15]: BitType, [17-23] Ident [22-23] "q": qubit, [25-35] Ident [30-32] "c2": ClassicalType [25-35]: BitType [25-35]: ExprStmt [32-35]: Expr [33-34]: Lit: Int(2), [37-47] Ident [42-44] "q4": qubit[ExprStmt [44-47]: Expr [45-46]: Lit: Int(4)]) + StmtKind: DefStmt [0-74]: Ident [4-8] "test"([9-15] Ident [14-15] "c": ClassicalType [9-15]: BitType, [17-23] Ident [22-23] "q": qubit, [25-35] Ident [30-32] "c2": ClassicalType [25-35]: BitType [25-35]: Expr [33-34]: Lit: Int(2), [37-47] Ident [42-44] "q4": qubit[Expr [45-46]: Lit: Int(4)]) Stmt [58-72] - StmtKind: ReturnStmt [58-72]: ValueExpression ExprStmt [65-71]: Expr [65-71]: BinOp (Exp): + StmtKind: ReturnStmt [58-72]: ValueExpression Expr [65-71]: BinOp (Exp): Expr [65-66]: Ident [65-66] "x" Expr [70-71]: Lit: Int(2) ClassicalType [52-55]: IntType [52-55]"#]], @@ -134,7 +134,7 @@ fn readonly_array_arg_with_int_dims() { "def specified_sub(readonly array[int[8], 2, 10] arr_arg) {}", &expect![[r#" Stmt [0-59] - StmtKind: DefStmt [0-59]: Ident [4-17] "specified_sub"([18-55] Ident [48-55] "arr_arg": ArrayReferenceType [18-47]: ArrayBaseTypeKind IntType[ExprStmt [36-39]: Expr [37-38]: Lit: Int(8)]: [33-39] + StmtKind: DefStmt [0-59]: Ident [4-17] "specified_sub"([18-55] Ident [48-55] "arr_arg": ArrayReferenceType [18-47]: ArrayBaseTypeKind IntType[Expr [37-38]: Lit: Int(8)]: [33-39] Expr [41-42]: Lit: Int(2) Expr [44-46]: Lit: Int(10)) "#]], ); @@ -147,7 +147,7 @@ fn readonly_array_arg_with_dim() { "def arr_subroutine(readonly array[int[8], #dim = 1] arr_arg) {}", &expect![[r#" Stmt [0-63] - StmtKind: DefStmt [0-63]: Ident [4-18] "arr_subroutine"([19-59] Ident [52-59] "arr_arg": ArrayReferenceType [19-51]: ArrayBaseTypeKind IntType[ExprStmt [37-40]: Expr [38-39]: Lit: Int(8)]: [34-40] + StmtKind: DefStmt [0-63]: Ident [4-18] "arr_subroutine"([19-59] Ident [52-59] "arr_arg": ArrayReferenceType [19-51]: ArrayBaseTypeKind IntType[Expr [38-39]: Lit: Int(8)]: [34-40] Expr [49-50]: Lit: Int(1)) "#]], ); } @@ -159,7 +159,7 @@ fn mutable_array_arg() { "def mut_subroutine(mutable array[int[8], #dim = 1] arr_arg) {}", &expect![[r#" Stmt [0-62] - StmtKind: DefStmt [0-62]: Ident [4-18] "mut_subroutine"([19-58] Ident [51-58] "arr_arg": ArrayReferenceType [19-50]: ArrayBaseTypeKind IntType[ExprStmt [36-39]: Expr [37-38]: Lit: Int(8)]: [33-39] + StmtKind: DefStmt [0-62]: Ident [4-18] "mut_subroutine"([19-58] Ident [51-58] "arr_arg": ArrayReferenceType [19-50]: ArrayBaseTypeKind IntType[Expr [37-38]: Lit: Int(8)]: [33-39] Expr [48-49]: 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..964a6f3e76 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs @@ -0,0 +1,42 @@ +// 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] + StmtKind: ExprStmt [0-2]: Expr [0-1]: Ident [0-1] "H""#]], + ); +} + +#[test] +fn identifier_plus_number() { + check( + parse, + "H + 2;", + &expect![[r#" + Stmt [0-6] + StmtKind: ExprStmt [0-6]: Expr [0-5]: BinOp (Add): + Expr [0-1]: Ident [0-1] "H" + Expr [4-5]: Lit: Int(2)"#]], + ); +} + +#[test] +fn function_call() { + check( + parse, + "f(2);", + &expect![[r#" + Stmt [0-5] + StmtKind: ExprStmt [0-5]: Expr [0-4]: FunctionCall [0-4]: Ident [0-1] "f" + Expr [2-3]: Lit: Int(2)"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/extern_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/extern_decl.rs index 8f5eddc341..2771da1428 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/extern_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/extern_decl.rs @@ -52,7 +52,7 @@ fn sized_bit_param_bit_ret_decl() { &expect![[r#" Stmt [0-24] StmtKind: ExternDecl [0-24]: Ident [7-8] "x" - [9-15]: ClassicalType [9-15]: BitType [9-15]: ExprStmt [12-15]: Expr [13-14]: Ident [13-14] "n" + [9-15]: ClassicalType [9-15]: BitType [9-15]: Expr [13-14]: Ident [13-14] "n" ClassicalType [20-23]: BitType"#]], ); } @@ -65,7 +65,7 @@ fn sized_creg_param_bit_ret_decl() { &expect![[r#" Stmt [0-25] StmtKind: ExternDecl [0-25]: Ident [7-8] "x" - [9-16]: ClassicalType [9-16]: BitType [9-16]: ExprStmt [13-16]: Expr [14-15]: Ident [14-15] "n" + [9-16]: ClassicalType [9-16]: BitType [9-16]: Expr [14-15]: Ident [14-15] "n" ClassicalType [21-24]: BitType"#]], ); } @@ -91,7 +91,7 @@ fn readonly_array_arg_with_int_dims() { &expect![[r#" Stmt [0-40] StmtKind: ExternDecl [0-40]: Ident [7-8] "x" - [9-38]: ArrayReferenceType [9-38]: ArrayBaseTypeKind IntType[ExprStmt [27-30]: Expr [28-29]: Lit: Int(8)]: [24-30] + [9-38]: ArrayReferenceType [9-38]: ArrayBaseTypeKind IntType[Expr [28-29]: Lit: Int(8)]: [24-30] Expr [32-33]: Lit: Int(2) Expr [35-37]: Lit: Int(10)"#]], ); @@ -105,7 +105,7 @@ fn readonly_array_arg_with_dim() { &expect![[r#" Stmt [0-43] StmtKind: ExternDecl [0-43]: Ident [7-8] "x" - [9-41]: ArrayReferenceType [9-41]: ArrayBaseTypeKind IntType[ExprStmt [27-30]: Expr [28-29]: Lit: Int(8)]: [24-30] + [9-41]: ArrayReferenceType [9-41]: ArrayBaseTypeKind IntType[Expr [28-29]: Lit: Int(8)]: [24-30] Expr [39-40]: Lit: Int(1)"#]], ); } @@ -118,7 +118,7 @@ fn mutable_array_arg() { &expect![[r#" Stmt [0-42] StmtKind: ExternDecl [0-42]: Ident [7-8] "x" - [9-40]: ArrayReferenceType [9-40]: ArrayBaseTypeKind IntType[ExprStmt [26-29]: Expr [27-28]: Lit: Int(8)]: [23-29] + [9-40]: ArrayReferenceType [9-40]: ArrayBaseTypeKind IntType[Expr [27-28]: Lit: Int(8)]: [23-29] Expr [38-39]: Lit: Int(1)"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs index 1d6aa1f1e9..8ce396ef6b 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + use crate::parser::{stmt::parse, tests::check}; use expect_test::expect; 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..ef2ff116a7 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs @@ -0,0 +1,102 @@ +// 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] + StmtKind: QuantumStmt [0-5]: GateCall [0-5]: Ident [0-1] "H" + GateOperand IndexedIdent [2-4]: Ident [2-4] "q0"[]"#]], + ); +} + +#[test] +fn gate_call_qubit_register() { + check( + parse, + "H q[2];", + &expect![[r#" + Stmt [0-7] + StmtKind: QuantumStmt [0-7]: GateCall [0-7]: Ident [0-1] "H" + GateOperand IndexedIdent [2-6]: Ident [2-3] "q"[ + IndexElement: + IndexSetItem Expr [4-5]: Lit: Int(2)]"#]], + ); +} + +#[test] +fn gate_multiple_qubits() { + check( + parse, + "CNOT q0 q[4];", + &expect![[r#" + Stmt [0-13] + StmtKind: QuantumStmt [0-13]: GateCall [0-13]: Ident [0-4] "CNOT" + GateOperand IndexedIdent [5-7]: Ident [5-7] "q0"[] + GateOperand IndexedIdent [8-12]: Ident [8-9] "q"[ + IndexElement: + IndexSetItem Expr [10-11]: Lit: Int(4)]"#]], + ); +} + +#[test] +fn gate_no_qubits() { + check( + parse, + "inv @ H;", + &expect![[r#" + Stmt [0-8] + StmtKind: QuantumStmt [0-8]: GateCall [0-8]: Ident [6-7] "H""#]], + ); +} + +#[test] +fn gate_call_with_parameters() { + check( + parse, + "Rx(pi / 2) q0;", + &expect![[r#" + Stmt [0-14] + StmtKind: QuantumStmt [0-14]: GateCall [0-14]: Ident [0-2] "Rx" + Expr [3-9]: BinOp (Div): + Expr [3-5]: Ident [3-5] "pi" + Expr [8-9]: Lit: Int(2) + GateOperand IndexedIdent [11-13]: Ident [11-13] "q0"[]"#]], + ); +} + +#[test] +fn gate_call_inv_modifier() { + check( + parse, + "inv @ H q0;", + &expect![[r#" + Stmt [0-11] + StmtKind: QuantumStmt [0-11]: GateCall [0-11]: Ident [6-7] "H" + GateOperand IndexedIdent [8-10]: Ident [8-10] "q0"[]"#]], + ); +} + +#[test] +fn gate_call_ctrl_inv_modifiers() { + check( + parse, + "ctrl(2) @ inv @ Rx(pi / 2) c1 c2 q0;", + &expect![[r#" + Stmt [0-36] + StmtKind: QuantumStmt [0-36]: GateCall [0-36]: Ident [16-18] "Rx" + Expr [19-25]: BinOp (Div): + Expr [19-21]: Ident [19-21] "pi" + Expr [24-25]: Lit: Int(2) + GateOperand IndexedIdent [27-29]: Ident [27-29] "c1"[] + GateOperand IndexedIdent [30-32]: Ident [30-32] "c2"[] + GateOperand IndexedIdent [33-35]: Ident [33-35] "q0"[]"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_def.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_def.rs index 24b8fb4ce2..a45c07effa 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_def.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_def.rs @@ -137,7 +137,7 @@ fn two_qubits_two_classical_with_body() { StmtKind: Gate [0-45]: Ident [5-9] "c2q2"(Ident [10-11] "a", Ident [13-14] "b") Ident [16-17] "c", Ident [19-20] "d" Stmt [23-43] - StmtKind: ClassicalDeclarationStmt [23-43]: ClassicalType [23-32]: FloatType[ExprStmt [28-32]: Expr [29-31]: Lit: Int(32)]: [23-32], Ident [33-34] "x", ValueExpression ExprStmt [37-42]: Expr [37-42]: BinOp (Sub): + StmtKind: ClassicalDeclarationStmt [23-43]: ClassicalType [23-32]: FloatType[Expr [29-31]: Lit: Int(32)]: [23-32], Ident [33-34] "x", ValueExpression Expr [37-42]: BinOp (Sub): Expr [37-38]: Ident [37-38] "a" Expr [41-42]: Ident [41-42] "b""#]], ); diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs index fe1ae6dfdc..c4239cade0 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + use crate::parser::{stmt::parse, tests::check}; use expect_test::expect; diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/io_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/io_decl.rs index b291ee1c6e..cfcae5cb3e 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/io_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/io_decl.rs @@ -36,7 +36,7 @@ fn input_bit_array_decl() { "input bit[2] b;", &expect![[r#" Stmt [0-15] - StmtKind: IODeclaration [0-15]: input, ClassicalType [6-12]: BitType [6-12]: ExprStmt [9-12]: Expr [10-11]: Lit: Int(2), Ident [13-14] "b""#]], + StmtKind: IODeclaration [0-15]: input, ClassicalType [6-12]: BitType [6-12]: Expr [10-11]: Lit: Int(2), Ident [13-14] "b""#]], ); } @@ -47,7 +47,7 @@ fn output_bit_array_decl() { "output bit[2] b;", &expect![[r#" Stmt [0-16] - StmtKind: IODeclaration [0-16]: output, ClassicalType [7-13]: BitType [7-13]: ExprStmt [10-13]: Expr [11-12]: Lit: Int(2), Ident [14-15] "b""#]], + StmtKind: IODeclaration [0-16]: output, ClassicalType [7-13]: BitType [7-13]: Expr [11-12]: Lit: Int(2), Ident [14-15] "b""#]], ); } @@ -102,7 +102,7 @@ fn input_complex_sized_decl() { "input complex[float[32]] c;", &expect![[r#" Stmt [0-27] - StmtKind: IODeclaration [0-27]: input, ClassicalType [6-24]: ComplexType[float[FloatType[ExprStmt [19-23]: Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24], Ident [25-26] "c""#]], + StmtKind: IODeclaration [0-27]: input, ClassicalType [6-24]: ComplexType[float[FloatType[Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24], Ident [25-26] "c""#]], ); } @@ -113,7 +113,7 @@ fn output_complex_sized_decl() { "output complex[float[32]] c;", &expect![[r#" Stmt [0-28] - StmtKind: IODeclaration [0-28]: output, ClassicalType [7-25]: ComplexType[float[FloatType[ExprStmt [20-24]: Expr [21-23]: Lit: Int(32)]: [15-24]]]: [7-25], Ident [26-27] "c""#]], + StmtKind: IODeclaration [0-28]: output, ClassicalType [7-25]: ComplexType[float[FloatType[Expr [21-23]: Lit: Int(32)]: [15-24]]]: [7-25], Ident [26-27] "c""#]], ); } @@ -146,7 +146,7 @@ fn input_int_sized_decl() { "input int[32] i;", &expect![[r#" Stmt [0-16] - StmtKind: IODeclaration [0-16]: input, ClassicalType [6-13]: IntType[ExprStmt [9-13]: Expr [10-12]: Lit: Int(32)]: [6-13], Ident [14-15] "i""#]], + StmtKind: IODeclaration [0-16]: input, ClassicalType [6-13]: IntType[Expr [10-12]: Lit: Int(32)]: [6-13], Ident [14-15] "i""#]], ); } @@ -157,7 +157,7 @@ fn output_int_sized_decl() { "output int[32] i;", &expect![[r#" Stmt [0-17] - StmtKind: IODeclaration [0-17]: output, ClassicalType [7-14]: IntType[ExprStmt [10-14]: Expr [11-13]: Lit: Int(32)]: [7-14], Ident [15-16] "i""#]], + StmtKind: IODeclaration [0-17]: output, ClassicalType [7-14]: IntType[Expr [11-13]: Lit: Int(32)]: [7-14], Ident [15-16] "i""#]], ); } @@ -190,7 +190,7 @@ fn input_uint_sized_decl() { "input uint[32] i;", &expect![[r#" Stmt [0-17] - StmtKind: IODeclaration [0-17]: input, ClassicalType [6-14]: UIntType[ExprStmt [10-14]: Expr [11-13]: Lit: Int(32)]: [6-14], Ident [15-16] "i""#]], + StmtKind: IODeclaration [0-17]: input, ClassicalType [6-14]: UIntType[Expr [11-13]: Lit: Int(32)]: [6-14], Ident [15-16] "i""#]], ); } @@ -201,7 +201,7 @@ fn output_uint_sized_decl() { "output uint[32] i;", &expect![[r#" Stmt [0-18] - StmtKind: IODeclaration [0-18]: output, ClassicalType [7-15]: UIntType[ExprStmt [11-15]: Expr [12-14]: Lit: Int(32)]: [7-15], Ident [16-17] "i""#]], + StmtKind: IODeclaration [0-18]: output, ClassicalType [7-15]: UIntType[Expr [12-14]: Lit: Int(32)]: [7-15], Ident [16-17] "i""#]], ); } @@ -234,7 +234,7 @@ fn input_float_sized_decl() { "input float[32] f;", &expect![[r#" Stmt [0-18] - StmtKind: IODeclaration [0-18]: input, ClassicalType [6-15]: FloatType[ExprStmt [11-15]: Expr [12-14]: Lit: Int(32)]: [6-15], Ident [16-17] "f""#]], + StmtKind: IODeclaration [0-18]: input, ClassicalType [6-15]: FloatType[Expr [12-14]: Lit: Int(32)]: [6-15], Ident [16-17] "f""#]], ); } @@ -245,7 +245,7 @@ fn output_float_sized_decl() { "output float[32] f;", &expect![[r#" Stmt [0-19] - StmtKind: IODeclaration [0-19]: output, ClassicalType [7-16]: FloatType[ExprStmt [12-16]: Expr [13-15]: Lit: Int(32)]: [7-16], Ident [17-18] "f""#]], + StmtKind: IODeclaration [0-19]: output, ClassicalType [7-16]: FloatType[Expr [13-15]: Lit: Int(32)]: [7-16], Ident [17-18] "f""#]], ); } @@ -278,7 +278,7 @@ fn input_angle_sized_decl() { "input angle[32] a;", &expect![[r#" Stmt [0-18] - StmtKind: IODeclaration [0-18]: input, ClassicalType [6-15]: AngleType [6-15]: ExprStmt [11-15]: Expr [12-14]: Lit: Int(32), Ident [16-17] "a""#]], + StmtKind: IODeclaration [0-18]: input, ClassicalType [6-15]: AngleType [6-15]: Expr [12-14]: Lit: Int(32), Ident [16-17] "a""#]], ); } @@ -289,7 +289,7 @@ fn output_angle_sized_decl() { "output angle[32] a;", &expect![[r#" Stmt [0-19] - StmtKind: IODeclaration [0-19]: output, ClassicalType [7-16]: AngleType [7-16]: ExprStmt [12-16]: Expr [13-15]: Lit: Int(32), Ident [17-18] "a""#]], + StmtKind: IODeclaration [0-19]: output, ClassicalType [7-16]: AngleType [7-16]: Expr [13-15]: Lit: Int(32), Ident [17-18] "a""#]], ); } 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 index 87db062e4c..69152c07ff 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/old_style_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/old_style_decl.rs @@ -25,7 +25,7 @@ fn creg_array_decl() { "creg c[n];", &expect![[r#" Stmt [0-10] - StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-10]: BitType [0-10]: ExprStmt [6-9]: Expr [7-8]: Ident [7-8] "n", Ident [5-6] "c""#]], + StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-10]: BitType [0-10]: Expr [7-8]: Ident [7-8] "n", Ident [5-6] "c""#]], ); } @@ -47,6 +47,6 @@ fn qreg_array_decl() { "qreg q[n];", &expect![[r#" Stmt [0-10] - StmtKind: QubitDeclaration [0-10]: Ident [5-6] "q", ExprStmt [6-9]: Expr [7-8]: Ident [7-8] "n""#]], + StmtKind: QubitDeclaration [0-10]: Ident [5-6] "q", Expr [7-8]: Ident [7-8] "n""#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/quantum_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/quantum_decl.rs index c2d5e4337c..93d18cec15 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/quantum_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/quantum_decl.rs @@ -77,7 +77,7 @@ fn quantum_decl_with_designator() { "qubit[5] qubits;", &expect![[r#" Stmt [0-16] - StmtKind: QubitDeclaration [0-16]: Ident [9-15] "qubits", ExprStmt [5-8]: Expr [6-7]: Lit: Int(5)"#]], + StmtKind: QubitDeclaration [0-16]: Ident [9-15] "qubits", Expr [6-7]: Lit: Int(5)"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs index 6077fa366d..d32cce4a7f 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + use crate::parser::{stmt::parse_switch_stmt, tests::check}; use expect_test::expect; @@ -153,12 +156,12 @@ fn multiple_cases() { Expr [37-38]: Lit: Int(0) Block [39-53]: Stmt [41-51] - StmtKind: ClassicalDeclarationStmt [41-51]: ClassicalType [41-44]: IntType [41-44], Ident [45-46] "x", ValueExpression ExprStmt [49-50]: Expr [49-50]: Lit: Int(0) + StmtKind: ClassicalDeclarationStmt [41-51]: ClassicalType [41-44]: IntType [41-44], Ident [45-46] "x", ValueExpression Expr [49-50]: Lit: Int(0) Labels: Expr [69-70]: Lit: Int(1) Block [71-85]: Stmt [73-83] - StmtKind: ClassicalDeclarationStmt [73-83]: ClassicalType [73-76]: IntType [73-76], Ident [77-78] "y", ValueExpression ExprStmt [81-82]: Expr [81-82]: Lit: Int(1) + StmtKind: ClassicalDeclarationStmt [73-83]: ClassicalType [73-76]: IntType [73-76], Ident [77-78] "y", ValueExpression Expr [81-82]: Lit: Int(1) "#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs index 9e5bde86a8..f444682c90 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + use crate::parser::{stmt::parse, tests::check}; use expect_test::expect; From 4aaf8fddf9223fef8d5c2871c7c6375bdb649ca3 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Mon, 24 Feb 2025 17:01:42 -0800 Subject: [PATCH 23/98] update expect tests --- compiler/qsc_qasm3/src/parser/expr/tests.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index 808c3f6781..655084d9d5 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -765,7 +765,7 @@ fn cast_to_bit_with_designator() { "bit[4](0)", &expect![[r#" Expr [0-9]: Cast [0-9]: - ClassicalType [0-6]: BitType [0-6]: ExprStmt [3-6]: Expr [4-5]: Lit: Int(4) + ClassicalType [0-6]: BitType [0-6]: Expr [4-5]: Lit: Int(4) Expr [0-9]: Paren: Expr [7-8]: Lit: Int(0)"#]], ); @@ -791,7 +791,7 @@ fn cast_to_int_with_designator() { "int[64](0)", &expect![[r#" Expr [0-10]: Cast [0-10]: - ClassicalType [0-7]: IntType[ExprStmt [3-7]: Expr [4-6]: Lit: Int(64)]: [0-7] + ClassicalType [0-7]: IntType[Expr [4-6]: Lit: Int(64)]: [0-7] Expr [0-10]: Paren: Expr [8-9]: Lit: Int(0)"#]], ); @@ -817,7 +817,7 @@ fn cast_to_uint_with_designator() { "uint[64](0)", &expect![[r#" Expr [0-11]: Cast [0-11]: - ClassicalType [0-8]: UIntType[ExprStmt [4-8]: Expr [5-7]: Lit: Int(64)]: [0-8] + ClassicalType [0-8]: UIntType[Expr [5-7]: Lit: Int(64)]: [0-8] Expr [0-11]: Paren: Expr [9-10]: Lit: Int(0)"#]], ); @@ -843,7 +843,7 @@ fn cast_to_float_with_designator() { "float[64](0)", &expect![[r#" Expr [0-12]: Cast [0-12]: - ClassicalType [0-9]: FloatType[ExprStmt [5-9]: Expr [6-8]: Lit: Int(64)]: [0-9] + ClassicalType [0-9]: FloatType[Expr [6-8]: Lit: Int(64)]: [0-9] Expr [0-12]: Paren: Expr [10-11]: Lit: Int(0)"#]], ); @@ -869,7 +869,7 @@ fn cast_to_complex_with_designator() { "complex[float[64]](0)", &expect![[r#" Expr [0-21]: Cast [0-21]: - ClassicalType [0-18]: ComplexType[float[FloatType[ExprStmt [13-17]: Expr [14-16]: Lit: Int(64)]: [8-17]]]: [0-18] + ClassicalType [0-18]: ComplexType[float[FloatType[Expr [14-16]: Lit: Int(64)]: [8-17]]]: [0-18] Expr [0-21]: Paren: Expr [19-20]: Lit: Int(0)"#]], ); @@ -921,7 +921,7 @@ fn cast_to_int_array() { "array[int[64], 4](0)", &expect![[r#" Expr [0-20]: Cast [0-20]: - ArrayType [0-17]: ArrayBaseTypeKind IntType[ExprStmt [9-13]: Expr [10-12]: Lit: Int(64)]: [6-13] + ArrayType [0-17]: ArrayBaseTypeKind IntType[Expr [10-12]: Lit: Int(64)]: [6-13] Expr [15-16]: Lit: Int(4) Expr [0-20]: Paren: Expr [18-19]: Lit: Int(0)"#]], @@ -935,7 +935,7 @@ fn cast_to_uint_array() { "array[uint[64], 4](0)", &expect![[r#" Expr [0-21]: Cast [0-21]: - ArrayType [0-18]: ArrayBaseTypeKind UIntType[ExprStmt [10-14]: Expr [11-13]: Lit: Int(64)]: [6-14] + ArrayType [0-18]: ArrayBaseTypeKind UIntType[Expr [11-13]: Lit: Int(64)]: [6-14] Expr [16-17]: Lit: Int(4) Expr [0-21]: Paren: Expr [19-20]: Lit: Int(0)"#]], @@ -949,7 +949,7 @@ fn cast_to_float_array() { "array[float[64], 4](0)", &expect![[r#" Expr [0-22]: Cast [0-22]: - ArrayType [0-19]: ArrayBaseTypeKind FloatType[ExprStmt [11-15]: Expr [12-14]: Lit: Int(64)]: [6-15] + ArrayType [0-19]: ArrayBaseTypeKind FloatType[Expr [12-14]: Lit: Int(64)]: [6-15] Expr [17-18]: Lit: Int(4) Expr [0-22]: Paren: Expr [20-21]: Lit: Int(0)"#]], @@ -963,7 +963,7 @@ fn cast_to_angle_array() { "array[angle[64], 4](0)", &expect![[r#" Expr [0-22]: Cast [0-22]: - ArrayType [0-19]: ArrayBaseTypeKind AngleType [6-15]: ExprStmt [11-15]: Expr [12-14]: Lit: Int(64) + ArrayType [0-19]: ArrayBaseTypeKind AngleType [6-15]: Expr [12-14]: Lit: Int(64) Expr [17-18]: Lit: Int(4) Expr [0-22]: Paren: Expr [20-21]: Lit: Int(0)"#]], @@ -1005,7 +1005,7 @@ fn cast_to_complex_array() { "array[complex[float[32]], 4](0)", &expect![[r#" Expr [0-31]: Cast [0-31]: - ArrayType [0-28]: ArrayBaseTypeKind ComplexType[float[FloatType[ExprStmt [19-23]: Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24] + ArrayType [0-28]: ArrayBaseTypeKind ComplexType[float[FloatType[Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24] Expr [26-27]: Lit: Int(4) Expr [0-31]: Paren: Expr [29-30]: Lit: Int(0)"#]], From 0c39f4973ee0dc7623d77fd56698322ce204d07d Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Tue, 25 Feb 2025 09:24:08 -0800 Subject: [PATCH 24/98] gate-call qubit args should be comma separated --- compiler/qsc_qasm3/src/ast.rs | 11 ++++++++- compiler/qsc_qasm3/src/parser/stmt.rs | 10 +++++--- .../src/parser/stmt/tests/gate_call.rs | 24 +++++++++---------- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index dcbf18d2f6..fb00c6df1d 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -298,10 +298,12 @@ impl Display for UnaryOp { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub enum GateOperand { IndexedIdent(Box), HardwareQubit(Box), + #[default] + Err, } impl Display for GateOperand { @@ -309,10 +311,17 @@ impl Display for GateOperand { match self { GateOperand::IndexedIdent(ident) => write!(f, "GateOperand {ident}"), GateOperand::HardwareQubit(qubit) => write!(f, "GateOperand {qubit}"), + GateOperand::Err => write!(f, "Error"), } } } +impl WithSpan for GateOperand { + fn with_span(self, _span: Span) -> Self { + self + } +} + #[derive(Clone, Debug)] pub struct HardwareQubit { pub span: Span, diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 7bdaadd8f9..b63c099557 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -20,8 +20,8 @@ use crate::{ ArrayReferenceType, ArrayType, BitType, Block, BreakStmt, ClassicalDeclarationStmt, ComplexType, ConstantDeclaration, ContinueStmt, DefStmt, EndStmt, EnumerableSet, Expr, ExprKind, ExprStmt, ExternDecl, ExternParameter, FloatType, ForStmt, FunctionCall, - GateCall, GateModifierKind, IODeclaration, IOKeyword, Ident, Identifier, IfStmt, - IncludeStmt, IntType, List, LiteralKind, Pragma, QuantumGateDefinition, + GateCall, GateModifierKind, GateOperand, IODeclaration, IOKeyword, Ident, Identifier, + IfStmt, IncludeStmt, IntType, List, LiteralKind, Pragma, QuantumGateDefinition, QuantumGateModifier, QuantumStmt, QubitDeclaration, RangeDefinition, ReturnStmt, ScalarType, ScalarTypeKind, Stmt, StmtKind, SwitchStmt, TypeDef, TypedParameter, UIntType, WhileLoop, @@ -1101,7 +1101,7 @@ fn parse_gate_call_stmt(s: &mut ParserContext) -> Result { let gate_or_expr = expr::expr(s)?; let duration = opt(s, designator)?; - let qubits = list_from_iter(many(s, gate_operand)?); + let qubits = gate_operands(s)?; recovering_semi(s); // If didn't parse modifiers, a duration, nor qubit args then this is an expr, not a gate call. @@ -1180,3 +1180,7 @@ fn gate_modifier(s: &mut ParserContext) -> Result { kind, }) } + +fn gate_operands(s: &mut ParserContext) -> Result> { + Ok(list_from_iter(seq(s, gate_operand)?.0)) +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs index ef2ff116a7..cf4d130848 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs @@ -35,14 +35,14 @@ fn gate_call_qubit_register() { fn gate_multiple_qubits() { check( parse, - "CNOT q0 q[4];", + "CNOT q0, q[4];", &expect![[r#" - Stmt [0-13] - StmtKind: QuantumStmt [0-13]: GateCall [0-13]: Ident [0-4] "CNOT" - GateOperand IndexedIdent [5-7]: Ident [5-7] "q0"[] - GateOperand IndexedIdent [8-12]: Ident [8-9] "q"[ - IndexElement: - IndexSetItem Expr [10-11]: Lit: Int(4)]"#]], + Stmt [0-14] + StmtKind: QuantumStmt [0-14]: GateCall [0-14]: Ident [0-4] "CNOT" + GateOperand IndexedIdent [5-7]: Ident [5-7] "q0"[] + GateOperand IndexedIdent [9-13]: Ident [9-10] "q"[ + IndexElement: + IndexSetItem Expr [11-12]: Lit: Int(4)]"#]], ); } @@ -88,15 +88,15 @@ fn gate_call_inv_modifier() { fn gate_call_ctrl_inv_modifiers() { check( parse, - "ctrl(2) @ inv @ Rx(pi / 2) c1 c2 q0;", + "ctrl(2) @ inv @ Rx(pi / 2) c1, c2, q0;", &expect![[r#" - Stmt [0-36] - StmtKind: QuantumStmt [0-36]: GateCall [0-36]: Ident [16-18] "Rx" + Stmt [0-38] + StmtKind: QuantumStmt [0-38]: GateCall [0-38]: Ident [16-18] "Rx" Expr [19-25]: BinOp (Div): Expr [19-21]: Ident [19-21] "pi" Expr [24-25]: Lit: Int(2) GateOperand IndexedIdent [27-29]: Ident [27-29] "c1"[] - GateOperand IndexedIdent [30-32]: Ident [30-32] "c2"[] - GateOperand IndexedIdent [33-35]: Ident [33-35] "q0"[]"#]], + GateOperand IndexedIdent [31-33]: Ident [31-33] "c2"[] + GateOperand IndexedIdent [35-37]: Ident [35-37] "q0"[]"#]], ); } From 6be9453eeca9c3f90dbed44ae870295381ad098b Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Tue, 25 Feb 2025 14:34:26 -0800 Subject: [PATCH 25/98] add barrier, box, calgrammar, cal, defcal, delay, measure arrow, and reset statements --- compiler/qsc_qasm3/src/ast.rs | 100 +++------ compiler/qsc_qasm3/src/parser/error.rs | 4 + compiler/qsc_qasm3/src/parser/expr.rs | 4 +- compiler/qsc_qasm3/src/parser/stmt.rs | 281 +++++++++++++++++++++++-- 4 files changed, 288 insertions(+), 101 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index fb00c6df1d..3a81cdb765 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -409,13 +409,14 @@ pub enum StmtKind { ExternDecl(ExternDecl), For(ForStmt), If(IfStmt), + GateCall(GateCall), + GPhase(QuantumPhase), Include(IncludeStmt), IODeclaration(IODeclaration), Measure(MeasureStmt), Pragma(Pragma), QuantumGateDefinition(QuantumGateDefinition), QuantumDecl(QubitDeclaration), - Quantum(QuantumStmt), Reset(ResetStmt), Return(ReturnStmt), Switch(SwitchStmt), @@ -449,6 +450,8 @@ impl Display for StmtKind { 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}"), @@ -456,7 +459,6 @@ impl Display for StmtKind { StmtKind::Pragma(pragma) => write!(f, "{pragma}"), StmtKind::QuantumGateDefinition(gate) => write!(f, "{gate}"), StmtKind::QuantumDecl(decl) => write!(f, "{decl}"), - StmtKind::Quantum(quantum_stmt) => write!(f, "{quantum_stmt}"), StmtKind::Reset(reset_stmt) => write!(f, "{reset_stmt}"), StmtKind::Return(return_stmt) => write!(f, "{return_stmt}"), StmtKind::Switch(switch_stmt) => write!(f, "{switch_stmt}"), @@ -479,11 +481,13 @@ impl Display for CalibrationGrammarStmt { } #[derive(Clone, Debug)] -pub struct DefCalStmt {} +pub struct DefCalStmt { + pub span: Span, +} impl Display for DefCalStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "DefCalStmt") + write!(f, "DefCalStmt {}", self.span) } } @@ -512,18 +516,6 @@ impl Display for IfStmt { } } -#[derive(Clone, Debug)] -pub struct DelayStmt { - pub span: Span, - pub duration: Expr, -} - -impl Display for DelayStmt { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "DelayStmt {}: {}", self.span, self.duration) - } -} - #[derive(Clone, Debug)] pub struct BarrierStmt { pub span: Span, @@ -725,18 +717,6 @@ impl Display for GateModifierKind { } } -#[derive(Clone, Debug)] -pub struct QuantumMeasurement { - pub span: Span, - pub qubit: Box, -} - -impl Display for QuantumMeasurement { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "QuantumMeasurement {}: {}", self.span, self.qubit) - } -} - #[derive(Clone, Debug)] pub struct ClassicalArgument { pub span: Span, @@ -1184,41 +1164,6 @@ impl Display for ExternDecl { } } -#[derive(Clone, Debug)] -pub struct QuantumStmt { - pub span: Span, - pub kind: QuantumStmtKind, -} - -impl Display for QuantumStmt { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "QuantumStmt {}: {}", self.span, self.kind) - } -} - -#[derive(Clone, Debug)] -pub enum QuantumStmtKind { - Gate(GateCall), - Phase(QuantumPhase), - Barrier(List), - Reset(List>), - DelayInstruction(DelayInstruction), - Box(BoxStmt), -} - -impl Display for QuantumStmtKind { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - QuantumStmtKind::Gate(gate) => write!(f, "{gate}"), - QuantumStmtKind::Phase(phase) => write!(f, "{phase}"), - QuantumStmtKind::Barrier(barrier) => write!(f, "{barrier:?}"), - QuantumStmtKind::Reset(reset) => write!(f, "{reset:?}"), - QuantumStmtKind::DelayInstruction(delay) => write!(f, "{delay:?}"), - QuantumStmtKind::Box(box_stmt) => write!(f, "{box_stmt:?}"), - } - } -} - #[derive(Clone, Debug)] pub struct GateCall { pub span: Span, @@ -1266,13 +1211,13 @@ impl Display for QuantumPhase { } #[derive(Clone, Debug)] -pub struct DelayInstruction { - span: Span, - duration: Expr, - qubits: List>, +pub struct DelayStmt { + pub span: Span, + pub duration: Expr, + pub qubits: List, } -impl Display for DelayInstruction { +impl Display for DelayStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut indent = set_indentation(indented(f), 0); write!(indent, "DelayInstruction {}: {}", self.span, self.duration)?; @@ -1287,7 +1232,7 @@ impl Display for DelayInstruction { pub struct BoxStmt { pub span: Span, pub duration: Option, - pub body: List, + pub body: List, } impl Display for BoxStmt { @@ -1308,16 +1253,20 @@ impl Display for BoxStmt { #[derive(Clone, Debug)] pub struct MeasureStmt { pub span: Span, - pub measure: QuantumMeasurement, - pub target: Option>, + pub measurement: MeasureExpr, + pub target: Option>, } impl Display for MeasureStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Some(target) = &self.target { - write!(f, "MeasureStmt {}: {}, {}", self.span, self.measure, target) + write!( + f, + "MeasureStmt {}: {}, {}", + self.span, self.measurement, target + ) } else { - write!(f, "MeasureStmt {}: {}", self.span, self.measure) + write!(f, "MeasureStmt {}: {}", self.span, self.measurement) } } } @@ -1417,13 +1366,12 @@ impl Display for CalibrationGrammarDeclaration { #[derive(Clone, Debug)] pub struct CalibrationStmt { - span: Span, - body: String, + pub span: Span, } impl Display for CalibrationStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "CalibrationStmt {}: {}", self.span, self.body) + write!(f, "CalibrationStmt {}", self.span) } } diff --git a/compiler/qsc_qasm3/src/parser/error.rs b/compiler/qsc_qasm3/src/parser/error.rs index a4853cc3b4..4129ede837 100644 --- a/compiler/qsc_qasm3/src/parser/error.rs +++ b/compiler/qsc_qasm3/src/parser/error.rs @@ -101,6 +101,9 @@ pub enum ErrorKind { #[error("expected {0}, found {1}")] #[diagnostic(code("Qasm3.Parse.Rule"))] Rule(&'static str, TokenKind, #[label] Span), + #[error("invalid classical statement in box")] + #[diagnostic(code("Qasm3.Parse.ClassicalStmtInBox"))] + ClassicalStmtInBox(#[label] Span), #[error("expected {0}, found {1}")] #[diagnostic(code("Qasm3.Parse.Convert"))] Convert(&'static str, &'static str, #[label] Span), @@ -132,6 +135,7 @@ impl ErrorKind { Self::Escape(ch, span) => Self::Escape(ch, span + offset), Self::Token(expected, actual, span) => Self::Token(expected, actual, span + offset), Self::Rule(name, token, span) => Self::Rule(name, token, span + offset), + Self::ClassicalStmtInBox(span) => Self::ClassicalStmtInBox(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), diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index c2f4d085fa..1610bd39dc 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -721,7 +721,7 @@ pub(crate) fn expr_list(s: &mut ParserContext) -> Result> { seq(s, expr).map(|pair| pair.0) } -fn measure_expr(s: &mut ParserContext) -> Result { +pub(crate) fn measure_expr(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; token(s, TokenKind::Measure)?; @@ -749,7 +749,7 @@ fn hardware_qubit(s: &mut ParserContext) -> Result { }) } -fn indexed_identifier(s: &mut ParserContext) -> Result { +pub(crate) fn indexed_identifier(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; let name: Ident = ident(s)?; let indices = list_from_iter(many(s, index_operand)?); diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index b63c099557..6cb9fc6774 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -10,21 +10,22 @@ use std::rc::Rc; use super::{ completion::WordKinds, error::{Error, ErrorKind}, - expr::{self, designator, gate_operand}, + expr::{self, designator, gate_operand, indexed_identifier}, prim::{self, barrier, many, opt, recovering, recovering_semi, recovering_token, seq, shorten}, Result, }; use crate::{ ast::{ list_from_iter, AccessControl, AliasDeclStmt, AngleType, Annotation, ArrayBaseTypeKind, - ArrayReferenceType, ArrayType, BitType, Block, BreakStmt, ClassicalDeclarationStmt, - ComplexType, ConstantDeclaration, ContinueStmt, DefStmt, EndStmt, EnumerableSet, Expr, - ExprKind, ExprStmt, ExternDecl, ExternParameter, FloatType, ForStmt, FunctionCall, + ArrayReferenceType, ArrayType, BarrierStmt, BitType, Block, BoxStmt, BreakStmt, + CalibrationGrammarStmt, CalibrationStmt, ClassicalDeclarationStmt, ComplexType, + ConstantDeclaration, ContinueStmt, DefCalStmt, DefStmt, DelayStmt, EndStmt, EnumerableSet, + Expr, ExprKind, ExprStmt, ExternDecl, ExternParameter, FloatType, ForStmt, FunctionCall, GateCall, GateModifierKind, GateOperand, IODeclaration, IOKeyword, Ident, Identifier, - IfStmt, IncludeStmt, IntType, List, LiteralKind, Pragma, QuantumGateDefinition, - QuantumGateModifier, QuantumStmt, QubitDeclaration, RangeDefinition, ReturnStmt, - ScalarType, ScalarTypeKind, Stmt, StmtKind, SwitchStmt, TypeDef, TypedParameter, UIntType, - WhileLoop, + IfStmt, IncludeStmt, IntType, List, LiteralKind, MeasureStmt, Pragma, + QuantumGateDefinition, QuantumGateModifier, QubitDeclaration, RangeDefinition, ResetStmt, + ReturnStmt, ScalarType, ScalarTypeKind, Stmt, StmtKind, SwitchStmt, TypeDef, + TypedParameter, UIntType, WhileLoop, }, keyword::Keyword, lex::{ @@ -85,6 +86,22 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { Box::new(StmtKind::ExprStmt(stmt)) } else if let Some(alias) = opt(s, parse_alias_stmt)? { Box::new(StmtKind::Alias(alias)) + } else if let Some(stmt) = opt(s, parse_box)? { + Box::new(StmtKind::Box(stmt)) + } else if let Some(stmt) = opt(s, parse_calibration_grammar_stmt)? { + Box::new(StmtKind::CalibrationGrammar(stmt)) + } else if let Some(stmt) = opt(s, parse_defcal_stmt)? { + Box::new(StmtKind::DefCal(stmt)) + } else if let Some(stmt) = opt(s, parse_cal)? { + Box::new(StmtKind::Cal(stmt)) + } else if let Some(stmt) = opt(s, parse_barrier)? { + Box::new(StmtKind::Barrier(stmt)) + } else if let Some(stmt) = opt(s, parse_delay)? { + Box::new(StmtKind::DelayStmt(stmt)) + } else if let Some(stmt) = opt(s, parse_reset)? { + Box::new(StmtKind::Reset(stmt)) + } else if let Some(stmt) = opt(s, parse_measure_stmt)? { + Box::new(StmtKind::Measure(stmt)) } else { return Err(Error::new(ErrorKind::Rule( "statement", @@ -191,7 +208,7 @@ fn parse_include(s: &mut ParserContext) -> Result { span: s.span(lo), filename: v.to_string(), }; - token(s, TokenKind::Semicolon)?; + recovering_semi(s); return Ok(StmtKind::Include(r)); } }; @@ -409,6 +426,8 @@ fn return_sig(s: &mut ParserContext) -> Result { 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))?; @@ -1075,6 +1094,44 @@ fn parse_alias_stmt(s: &mut ParserContext) -> Result { }) } +fn parse_boxable_stmt(s: &mut ParserContext) -> Result { + let stmt = *parse(s)?; + match &*stmt.kind { + StmtKind::Barrier(_) | StmtKind::Break(_) | StmtKind::DelayStmt(_) | StmtKind::Reset(_) => { + Ok(stmt) + } + _ => Err(Error::new(ErrorKind::ClassicalStmtInBox(stmt.span))), + } +} + +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_boxable_stmt, + ) + }); + + Ok(list_from_iter(stmts?)) +} + +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) +} + /// In QASM3, it is hard to disambiguate between a quantum-gate-call missing modifiers /// and expression statements. Consider the following expressions: /// 1. `Rx(2, 3) q0;` @@ -1101,7 +1158,7 @@ fn parse_gate_call_stmt(s: &mut ParserContext) -> Result { let gate_or_expr = expr::expr(s)?; let duration = opt(s, designator)?; - let qubits = gate_operands(s)?; + let qubits = gate_operand_list(s)?; recovering_semi(s); // If didn't parse modifiers, a duration, nor qubit args then this is an expr, not a gate call. @@ -1123,19 +1180,14 @@ fn parse_gate_call_stmt(s: &mut ParserContext) -> Result { } }; - let quantum_stmt = QuantumStmt { + Ok(StmtKind::GateCall(GateCall { span: s.span(lo), - kind: crate::ast::QuantumStmtKind::Gate(GateCall { - span: s.span(lo), - modifiers, - name, - args, - qubits, - duration, - }), - }; - - Ok(StmtKind::Quantum(quantum_stmt)) + modifiers, + name, + args, + qubits, + duration, + })) } /// Grammar: @@ -1181,6 +1233,189 @@ fn gate_modifier(s: &mut ParserContext) -> Result { }) } -fn gate_operands(s: &mut ParserContext) -> Result> { +/// 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; + s.expect(WordKinds::DefCalGrammar); + token(s, TokenKind::Keyword(Keyword::DefCalGrammar))?; + let next = s.peek(); + let v = expr::lit(s)?; + + if let Some(v) = v { + recovering_semi(s); + if let LiteralKind::String(v) = v.kind { + return Ok(CalibrationGrammarStmt { + span: s.span(lo), + name: v.to_string(), + }); + } + }; + + Err(Error::new(ErrorKind::Rule( + "calibration grammar statement", + TokenKind::Literal(Literal::String), + next.span, + ))) +} + +/// We don't support `defcal` statements. +/// Grammar: `DEFCAL pushmode(eatUntilOpenBrace) pushmode(eatUntilBalancedClosingBrace)`. +fn parse_defcal_stmt(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + s.expect(WordKinds::DefCal); + 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) }); + } + } + _ => (), + } + } +} + +/// We don't support `cal` block statements. +/// Grammar: `CAL OPEN_BRACE pushmode(eatUntilBalancedClosingBrace)`. +fn parse_cal(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + s.expect(WordKinds::Cal); + 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) }); + } + } + _ => (), + } + } +} + +/// Grammar: `BARRIER gateOperandList? SEMICOLON`. +fn parse_barrier(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + s.expect(WordKinds::Barrier); + token(s, TokenKind::Keyword(Keyword::Barrier))?; + let qubits = gate_operand_list(s)?; + recovering_semi(s); + + Ok(BarrierStmt { + span: s.span(lo), + qubits, + }) +} + +/// Grammar: `BOX designator? scope`. +fn parse_box(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + s.expect(WordKinds::Box); + 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, + }) +} + +/// Grammar: `DELAY designator gateOperandList? SEMICOLON`. +fn parse_delay(s: &mut ParserContext) -> Result { + let lo = s.peek().span.lo; + s.expect(WordKinds::Delay); + 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; + s.expect(WordKinds::Barrier); + token(s, TokenKind::Keyword(Keyword::Barrier))?; + let operand = Box::new(gate_operand(s)?); + recovering_semi(s); + + Ok(ResetStmt { + span: s.span(lo), + 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(MeasureStmt { + span: s.span(lo), + measurement: measure, + target, + }) +} From c146bd456f9099c0074ea2157e3c881be53137ea Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Tue, 25 Feb 2025 14:36:12 -0800 Subject: [PATCH 26/98] update completions tests --- compiler/qsc_qasm3/src/parser/completion/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs index 2abda50e4e..40c5dc32cc 100644 --- a/compiler/qsc_qasm3/src/parser/completion/tests.rs +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -36,7 +36,7 @@ fn begin_document() { "|OPENQASM 3;", &expect![[r#" WordKinds( - Annotation | Break | Continue | CReg | Def | End | Extern | False | For | Gate | If | Include | Input | Let | OpenQASM | Output | Pragma | QReg | Qubit | True | Return | Switch | While, + Annotation | Barrier | Box | Break | Cal | Continue | CReg | Def | DefCal | DefCalGrammar | Delay | End | Extern | False | For | Gate | If | Include | Input | Let | OpenQASM | Output | Pragma | QReg | Qubit | True | Return | Switch | While, ) "#]], ); @@ -48,7 +48,7 @@ fn end_of_version() { "OPENQASM 3;|", &expect![[r#" WordKinds( - Annotation | Break | Continue | CReg | Def | End | Extern | False | For | Gate | If | Include | Input | Let | Output | Pragma | QReg | Qubit | True | Return | Switch | While, + Annotation | Barrier | Box | Break | Cal | Continue | CReg | Def | DefCal | DefCalGrammar | Delay | End | Extern | False | For | Gate | If | Include | Input | Let | Output | Pragma | QReg | Qubit | True | Return | Switch | While, ) "#]], ); From 3ceedb8c2e565850022eba2cd5140cb52babd99c Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Tue, 25 Feb 2025 15:33:45 -0800 Subject: [PATCH 27/98] add unit tests --- compiler/qsc_qasm3/src/parser/stmt.rs | 19 +++--- compiler/qsc_qasm3/src/parser/stmt/tests.rs | 8 +++ .../src/parser/stmt/tests/barrier.rs | 33 +++++++++++ .../src/parser/stmt/tests/box_stmt.rs | 58 +++++++++++++++++++ .../qsc_qasm3/src/parser/stmt/tests/cal.rs | 44 ++++++++++++++ .../src/parser/stmt/tests/cal_grammar.rs | 13 +++++ .../qsc_qasm3/src/parser/stmt/tests/defcal.rs | 44 ++++++++++++++ .../qsc_qasm3/src/parser/stmt/tests/delay.rs | 19 ++++++ .../src/parser/stmt/tests/gate_call.rs | 36 ++++++------ .../src/parser/stmt/tests/measure.rs | 40 +++++++++++++ .../qsc_qasm3/src/parser/stmt/tests/reset.rs | 29 ++++++++++ 11 files changed, 317 insertions(+), 26 deletions(-) create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/cal.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/cal_grammar.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/defcal.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 6cb9fc6774..8e22f6a53d 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -1097,9 +1097,12 @@ fn parse_alias_stmt(s: &mut ParserContext) -> Result { fn parse_boxable_stmt(s: &mut ParserContext) -> Result { let stmt = *parse(s)?; match &*stmt.kind { - StmtKind::Barrier(_) | StmtKind::Break(_) | StmtKind::DelayStmt(_) | StmtKind::Reset(_) => { - Ok(stmt) - } + StmtKind::Barrier(_) + | StmtKind::Break(_) + | StmtKind::DelayStmt(_) + | StmtKind::Reset(_) + | StmtKind::GateCall(_) + | StmtKind::GPhase(_) => Ok(stmt), _ => Err(Error::new(ErrorKind::ClassicalStmtInBox(stmt.span))), } } @@ -1302,7 +1305,7 @@ fn parse_defcal_stmt(s: &mut ParserContext) -> Result { return Ok(DefCalStmt { span: s.span(lo) }); } } - _ => (), + _ => s.advance(), } } } @@ -1337,7 +1340,7 @@ fn parse_cal(s: &mut ParserContext) -> Result { return Ok(CalibrationStmt { span: s.span(lo) }); } } - _ => (), + _ => s.advance(), } } } @@ -1375,7 +1378,7 @@ fn parse_box(s: &mut ParserContext) -> Result { fn parse_delay(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; s.expect(WordKinds::Delay); - token(s, TokenKind::Keyword(Keyword::Delay))?; + token(s, TokenKind::Delay)?; let duration = designator(s)?; let qubits = gate_operand_list(s)?; recovering_semi(s); @@ -1390,8 +1393,8 @@ fn parse_delay(s: &mut ParserContext) -> Result { /// Grammar: `RESET gateOperand SEMICOLON`. fn parse_reset(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; - s.expect(WordKinds::Barrier); - token(s, TokenKind::Keyword(Keyword::Barrier))?; + s.expect(WordKinds::Reset); + token(s, TokenKind::Reset)?; let operand = Box::new(gate_operand(s)?); recovering_semi(s); diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests.rs b/compiler/qsc_qasm3/src/parser/stmt/tests.rs index 14fcea83ad..9b63eefbb3 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests.rs @@ -3,8 +3,14 @@ mod alias; mod annotation; +mod barrier; +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; @@ -12,8 +18,10 @@ mod gate_call; mod gate_def; mod if_stmt; 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/barrier.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs new file mode 100644 index 0000000000..c925c89a8c --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs @@ -0,0 +1,33 @@ +// 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] + StmtKind: Barrier [0-20]: [ + GateOperand IndexedIdent [8-9]: Ident [8-9] "r"[] + GateOperand IndexedIdent [11-15]: Ident [11-12] "q"[ + IndexElement: + IndexSetItem Expr [13-14]: Lit: Int(0)] + GateOperand HardwareQubit [17-19]: 2]"#]], + ); +} + +#[test] +fn barrier_no_args() { + check( + parse, + "barrier;", + &expect![[r#" + Stmt [0-8] + StmtKind: Barrier [0-8]: []"#]], + ); +} 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..81e63ad80a --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs @@ -0,0 +1,58 @@ +// 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] + StmtKind: BoxStmt [5-50]: + Stmt [19-24] + StmtKind: GateCall [19-24]: Ident [19-20] "H" + GateOperand IndexedIdent [21-23]: Ident [21-23] "q0"[] + Stmt [33-44] + StmtKind: GateCall [33-44]: Ident [33-35] "Rx" + Expr [36-39]: Lit: Float(2.4) + GateOperand IndexedIdent [41-43]: Ident [41-43] "q1"[]"#]], + ); +} + +#[test] +fn box_stmt_with_invalid_instruction() { + check( + parse, + "box { + H q0; + 2 + 4; + }", + &expect![[r#" + Stmt [0-40] + StmtKind: BoxStmt [0-40]: + Stmt [14-19] + StmtKind: GateCall [14-19]: Ident [14-15] "H" + GateOperand IndexedIdent [16-18]: Ident [16-18] "q0"[] + Stmt [28-34] + StmtKind: Err + + [ + Error( + ClassicalStmtInBox( + Span { + lo: 28, + hi: 34, + }, + ), + ), + ]"#]], + ); +} 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..da1ce82d60 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/cal.rs @@ -0,0 +1,44 @@ +// 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] + StmtKind: 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..c1b1c7e6e7 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/cal_grammar.rs @@ -0,0 +1,13 @@ +// 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] + StmtKind: CalibrationGrammarStmt [0-26]: openpulse"#]]); +} 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..986f833810 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/defcal.rs @@ -0,0 +1,44 @@ +// 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] + StmtKind: 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..65954342e9 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs @@ -0,0 +1,19 @@ +// 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] + StmtKind: DelayInstruction [0-20]: Expr [6-7]: Ident [6-7] "a" + GateOperand IndexedIdent [9-13]: Ident [9-10] "q"[ + IndexElement: + IndexSetItem Expr [11-12]: Lit: Int(0)] + GateOperand IndexedIdent [15-19]: Ident [15-16] "q"[ + IndexElement: + IndexSetItem Expr [17-18]: Lit: Int(1)]"#]]); +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs index cf4d130848..3a9c4f4f27 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs @@ -11,9 +11,9 @@ fn gate_call() { parse, "H q0;", &expect![[r#" - Stmt [0-5] - StmtKind: QuantumStmt [0-5]: GateCall [0-5]: Ident [0-1] "H" - GateOperand IndexedIdent [2-4]: Ident [2-4] "q0"[]"#]], + Stmt [0-5] + StmtKind: GateCall [0-5]: Ident [0-1] "H" + GateOperand IndexedIdent [2-4]: Ident [2-4] "q0"[]"#]], ); } @@ -23,11 +23,11 @@ fn gate_call_qubit_register() { parse, "H q[2];", &expect![[r#" - Stmt [0-7] - StmtKind: QuantumStmt [0-7]: GateCall [0-7]: Ident [0-1] "H" - GateOperand IndexedIdent [2-6]: Ident [2-3] "q"[ - IndexElement: - IndexSetItem Expr [4-5]: Lit: Int(2)]"#]], + Stmt [0-7] + StmtKind: GateCall [0-7]: Ident [0-1] "H" + GateOperand IndexedIdent [2-6]: Ident [2-3] "q"[ + IndexElement: + IndexSetItem Expr [4-5]: Lit: Int(2)]"#]], ); } @@ -38,7 +38,7 @@ fn gate_multiple_qubits() { "CNOT q0, q[4];", &expect![[r#" Stmt [0-14] - StmtKind: QuantumStmt [0-14]: GateCall [0-14]: Ident [0-4] "CNOT" + StmtKind: GateCall [0-14]: Ident [0-4] "CNOT" GateOperand IndexedIdent [5-7]: Ident [5-7] "q0"[] GateOperand IndexedIdent [9-13]: Ident [9-10] "q"[ IndexElement: @@ -53,7 +53,7 @@ fn gate_no_qubits() { "inv @ H;", &expect![[r#" Stmt [0-8] - StmtKind: QuantumStmt [0-8]: GateCall [0-8]: Ident [6-7] "H""#]], + StmtKind: GateCall [0-8]: Ident [6-7] "H""#]], ); } @@ -63,12 +63,12 @@ fn gate_call_with_parameters() { parse, "Rx(pi / 2) q0;", &expect![[r#" - Stmt [0-14] - StmtKind: QuantumStmt [0-14]: GateCall [0-14]: Ident [0-2] "Rx" - Expr [3-9]: BinOp (Div): - Expr [3-5]: Ident [3-5] "pi" - Expr [8-9]: Lit: Int(2) - GateOperand IndexedIdent [11-13]: Ident [11-13] "q0"[]"#]], + Stmt [0-14] + StmtKind: GateCall [0-14]: Ident [0-2] "Rx" + Expr [3-9]: BinOp (Div): + Expr [3-5]: Ident [3-5] "pi" + Expr [8-9]: Lit: Int(2) + GateOperand IndexedIdent [11-13]: Ident [11-13] "q0"[]"#]], ); } @@ -79,7 +79,7 @@ fn gate_call_inv_modifier() { "inv @ H q0;", &expect![[r#" Stmt [0-11] - StmtKind: QuantumStmt [0-11]: GateCall [0-11]: Ident [6-7] "H" + StmtKind: GateCall [0-11]: Ident [6-7] "H" GateOperand IndexedIdent [8-10]: Ident [8-10] "q0"[]"#]], ); } @@ -91,7 +91,7 @@ fn gate_call_ctrl_inv_modifiers() { "ctrl(2) @ inv @ Rx(pi / 2) c1, c2, q0;", &expect![[r#" Stmt [0-38] - StmtKind: QuantumStmt [0-38]: GateCall [0-38]: Ident [16-18] "Rx" + StmtKind: GateCall [0-38]: Ident [16-18] "Rx" Expr [19-25]: BinOp (Div): Expr [19-21]: Ident [19-21] "pi" Expr [24-25]: Lit: Int(2) 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..946cd7b251 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs @@ -0,0 +1,40 @@ +// 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] + StmtKind: MeasureStmt [0-10]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-9]: Ident [8-9] "q"[]"#]]); +} + +#[test] +fn measure_indented_ident() { + check(parse, "measure q[2];", &expect![[r#" + Stmt [0-13] + StmtKind: MeasureStmt [0-13]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-12]: Ident [8-9] "q"[ + IndexElement: + IndexSetItem Expr [10-11]: Lit: Int(2)]"#]]); +} + +#[test] +fn measure_hardware_qubit() { + check(parse, "measure $42;", &expect![[r#" + Stmt [0-12] + StmtKind: MeasureStmt [0-12]: MeasureExpr [0-7]: GateOperand HardwareQubit [8-11]: 42"#]]); +} + +#[test] +fn measure_arrow() { + check(parse, "measure q[2] -> a[4];", &expect![[r#" + Stmt [0-21] + StmtKind: MeasureStmt [0-21]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-12]: Ident [8-9] "q"[ + IndexElement: + IndexSetItem Expr [10-11]: Lit: Int(2)], IndexedIdent [16-20]: Ident [16-17] "a"[ + IndexElement: + IndexSetItem Expr [18-19]: Lit: Int(4)]"#]]); +} 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..09bd24733f --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs @@ -0,0 +1,29 @@ +// 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] + StmtKind: ResetStmt [0-8]: GateOperand IndexedIdent [6-7]: Ident [6-7] "a"[]"#]]); +} + +#[test] +fn reset_indexed_ident() { + check(parse, "reset a[1];", &expect![[r#" + Stmt [0-11] + StmtKind: ResetStmt [0-11]: GateOperand IndexedIdent [6-10]: Ident [6-7] "a"[ + IndexElement: + IndexSetItem Expr [8-9]: Lit: Int(1)]"#]]); +} + +#[test] +fn reset_hardware_qubit() { + check(parse, "reset $42;", &expect![[r#" + Stmt [0-10] + StmtKind: ResetStmt [0-10]: GateOperand HardwareQubit [6-9]: 42"#]]); +} From f42289eb9d02cffd4f99b370b505f223a2ad796b Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Tue, 25 Feb 2025 15:41:20 -0800 Subject: [PATCH 28/98] fix formatting --- .../src/parser/stmt/tests/cal_grammar.rs | 8 +++-- .../qsc_qasm3/src/parser/stmt/tests/def.rs | 8 ++--- .../qsc_qasm3/src/parser/stmt/tests/delay.rs | 8 +++-- .../src/parser/stmt/tests/measure.rs | 32 ++++++++++++++----- .../qsc_qasm3/src/parser/stmt/tests/reset.rs | 24 ++++++++++---- 5 files changed, 58 insertions(+), 22 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/cal_grammar.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/cal_grammar.rs index c1b1c7e6e7..0416e8bb03 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/cal_grammar.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/cal_grammar.rs @@ -7,7 +7,11 @@ use expect_test::expect; #[test] fn defcalgrammar() { - check(parse, r#"defcalgrammar "openpulse";"#, &expect![[r#" + check( + parse, + r#"defcalgrammar "openpulse";"#, + &expect![[r#" Stmt [0-26] - StmtKind: CalibrationGrammarStmt [0-26]: openpulse"#]]); + StmtKind: CalibrationGrammarStmt [0-26]: openpulse"#]], + ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs index d9f82d4b77..7f1babf50d 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs @@ -47,7 +47,7 @@ fn missing_args_with_delim_error() { "def x(,) { }", &expect![[r#" Stmt [0-12] - StmtKind: DefStmt [0-12]: Ident [4-5] "x"([6-6] Ident [0-0] "": ClassicalType [0-0]: Err) + StmtKind: DefStmt [0-12]: Ident [4-5] "x"([6-6] Ident [0-0] "": ClassicalType [0-0]: Err) [ Error( @@ -69,7 +69,7 @@ fn args_with_extra_delim_err_ty() { "def x(int a,,int b) { }", &expect![[r#" Stmt [0-23] - StmtKind: DefStmt [0-23]: Ident [4-5] "x"([6-11] Ident [10-11] "a": ClassicalType [6-9]: IntType [6-9], [12-12] Ident [0-0] "": ClassicalType [0-0]: Err, [13-18] Ident [17-18] "b": ClassicalType [13-16]: IntType [13-16]) + StmtKind: DefStmt [0-23]: Ident [4-5] "x"([6-11] Ident [10-11] "a": ClassicalType [6-9]: IntType [6-9], [12-12] Ident [0-0] "": ClassicalType [0-0]: Err, [13-18] Ident [17-18] "b": ClassicalType [13-16]: IntType [13-16]) [ Error( @@ -91,7 +91,7 @@ fn classical_subroutine() { "def square(int[32] x) -> int { return x ** 2; }", &expect![[r#" Stmt [0-47] - StmtKind: DefStmt [0-47]: Ident [4-10] "square"([11-20] Ident [19-20] "x": ClassicalType [11-18]: IntType[Expr [15-17]: Lit: Int(32)]: [11-18]) + StmtKind: DefStmt [0-47]: Ident [4-10] "square"([11-20] Ident [19-20] "x": ClassicalType [11-18]: IntType[Expr [15-17]: Lit: Int(32)]: [11-18]) Stmt [31-45] StmtKind: ReturnStmt [31-45]: ValueExpression Expr [38-44]: BinOp (Exp): Expr [38-39]: Ident [38-39] "x" @@ -118,7 +118,7 @@ fn old_style_args() { "def test(creg c, qreg q, creg c2[2], qreg q4[4]) -> int { return x ** 2; }", &expect![[r#" Stmt [0-74] - StmtKind: DefStmt [0-74]: Ident [4-8] "test"([9-15] Ident [14-15] "c": ClassicalType [9-15]: BitType, [17-23] Ident [22-23] "q": qubit, [25-35] Ident [30-32] "c2": ClassicalType [25-35]: BitType [25-35]: Expr [33-34]: Lit: Int(2), [37-47] Ident [42-44] "q4": qubit[Expr [45-46]: Lit: Int(4)]) + StmtKind: DefStmt [0-74]: Ident [4-8] "test"([9-15] Ident [14-15] "c": ClassicalType [9-15]: BitType, [17-23] Ident [22-23] "q": qubit, [25-35] Ident [30-32] "c2": ClassicalType [25-35]: BitType [25-35]: Expr [33-34]: Lit: Int(2), [37-47] Ident [42-44] "q4": qubit[Expr [45-46]: Lit: Int(4)]) Stmt [58-72] StmtKind: ReturnStmt [58-72]: ValueExpression Expr [65-71]: BinOp (Exp): Expr [65-66]: Ident [65-66] "x" diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs index 65954342e9..463ba062f0 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs @@ -7,7 +7,10 @@ use expect_test::expect; #[test] fn delay() { - check(parse, "delay[a] q[0], q[1];", &expect![[r#" + check( + parse, + "delay[a] q[0], q[1];", + &expect![[r#" Stmt [0-20] StmtKind: DelayInstruction [0-20]: Expr [6-7]: Ident [6-7] "a" GateOperand IndexedIdent [9-13]: Ident [9-10] "q"[ @@ -15,5 +18,6 @@ fn delay() { IndexSetItem Expr [11-12]: Lit: Int(0)] GateOperand IndexedIdent [15-19]: Ident [15-16] "q"[ IndexElement: - IndexSetItem Expr [17-18]: Lit: Int(1)]"#]]); + IndexSetItem Expr [17-18]: Lit: Int(1)]"#]], + ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs index 946cd7b251..05950318df 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs @@ -7,34 +7,50 @@ use expect_test::expect; #[test] fn measure_identifier() { - check(parse, "measure q;", &expect![[r#" + check( + parse, + "measure q;", + &expect![[r#" Stmt [0-10] - StmtKind: MeasureStmt [0-10]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-9]: Ident [8-9] "q"[]"#]]); + StmtKind: MeasureStmt [0-10]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-9]: Ident [8-9] "q"[]"#]], + ); } #[test] fn measure_indented_ident() { - check(parse, "measure q[2];", &expect![[r#" + check( + parse, + "measure q[2];", + &expect![[r#" Stmt [0-13] StmtKind: MeasureStmt [0-13]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-12]: Ident [8-9] "q"[ IndexElement: - IndexSetItem Expr [10-11]: Lit: Int(2)]"#]]); + IndexSetItem Expr [10-11]: Lit: Int(2)]"#]], + ); } #[test] fn measure_hardware_qubit() { - check(parse, "measure $42;", &expect![[r#" + check( + parse, + "measure $42;", + &expect![[r#" Stmt [0-12] - StmtKind: MeasureStmt [0-12]: MeasureExpr [0-7]: GateOperand HardwareQubit [8-11]: 42"#]]); + StmtKind: MeasureStmt [0-12]: MeasureExpr [0-7]: GateOperand HardwareQubit [8-11]: 42"#]], + ); } #[test] fn measure_arrow() { - check(parse, "measure q[2] -> a[4];", &expect![[r#" + check( + parse, + "measure q[2] -> a[4];", + &expect![[r#" Stmt [0-21] StmtKind: MeasureStmt [0-21]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-12]: Ident [8-9] "q"[ IndexElement: IndexSetItem Expr [10-11]: Lit: Int(2)], IndexedIdent [16-20]: Ident [16-17] "a"[ IndexElement: - IndexSetItem Expr [18-19]: Lit: Int(4)]"#]]); + IndexSetItem Expr [18-19]: Lit: Int(4)]"#]], + ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs index 09bd24733f..863df34196 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs @@ -7,23 +7,35 @@ use expect_test::expect; #[test] fn reset_ident() { - check(parse, "reset a;", &expect![[r#" + check( + parse, + "reset a;", + &expect![[r#" Stmt [0-8] - StmtKind: ResetStmt [0-8]: GateOperand IndexedIdent [6-7]: Ident [6-7] "a"[]"#]]); + StmtKind: ResetStmt [0-8]: GateOperand IndexedIdent [6-7]: Ident [6-7] "a"[]"#]], + ); } #[test] fn reset_indexed_ident() { - check(parse, "reset a[1];", &expect![[r#" + check( + parse, + "reset a[1];", + &expect![[r#" Stmt [0-11] StmtKind: ResetStmt [0-11]: GateOperand IndexedIdent [6-10]: Ident [6-7] "a"[ IndexElement: - IndexSetItem Expr [8-9]: Lit: Int(1)]"#]]); + IndexSetItem Expr [8-9]: Lit: Int(1)]"#]], + ); } #[test] fn reset_hardware_qubit() { - check(parse, "reset $42;", &expect![[r#" + check( + parse, + "reset $42;", + &expect![[r#" Stmt [0-10] - StmtKind: ResetStmt [0-10]: GateOperand HardwareQubit [6-9]: 42"#]]); + StmtKind: ResetStmt [0-10]: GateOperand HardwareQubit [6-9]: 42"#]], + ); } From ddbb48a2cb537d49fdd2376cf665d720710e5c34 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Tue, 25 Feb 2025 15:43:04 -0800 Subject: [PATCH 29/98] update completions tests --- compiler/qsc_qasm3/src/parser/completion/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs index 40c5dc32cc..c4d70fab6e 100644 --- a/compiler/qsc_qasm3/src/parser/completion/tests.rs +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -36,7 +36,7 @@ fn begin_document() { "|OPENQASM 3;", &expect![[r#" WordKinds( - Annotation | Barrier | Box | Break | Cal | Continue | CReg | Def | DefCal | DefCalGrammar | Delay | End | Extern | False | For | Gate | If | Include | Input | Let | OpenQASM | Output | Pragma | QReg | Qubit | True | Return | Switch | While, + Annotation | Barrier | Box | Break | Cal | 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, ) "#]], ); @@ -48,7 +48,7 @@ fn end_of_version() { "OPENQASM 3;|", &expect![[r#" WordKinds( - Annotation | Barrier | Box | Break | Cal | Continue | CReg | Def | DefCal | DefCalGrammar | Delay | End | Extern | False | For | Gate | If | Include | Input | Let | Output | Pragma | QReg | Qubit | True | Return | Switch | While, + Annotation | Barrier | Box | Break | Cal | Continue | CReg | Def | DefCal | DefCalGrammar | Delay | End | Extern | False | For | Gate | If | Include | Input | Let | Output | Pragma | QReg | Qubit | Reset | True | Return | Switch | While, ) "#]], ); From ff2db3d3efcb3dfba9c50e35c576716a4a7d9fe3 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Tue, 25 Feb 2025 15:53:41 -0800 Subject: [PATCH 30/98] update unit tests --- compiler/qsc_qasm3/src/parser/stmt/tests/def.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs index 7f1babf50d..d9f82d4b77 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs @@ -47,7 +47,7 @@ fn missing_args_with_delim_error() { "def x(,) { }", &expect![[r#" Stmt [0-12] - StmtKind: DefStmt [0-12]: Ident [4-5] "x"([6-6] Ident [0-0] "": ClassicalType [0-0]: Err) + StmtKind: DefStmt [0-12]: Ident [4-5] "x"([6-6] Ident [0-0] "": ClassicalType [0-0]: Err) [ Error( @@ -69,7 +69,7 @@ fn args_with_extra_delim_err_ty() { "def x(int a,,int b) { }", &expect![[r#" Stmt [0-23] - StmtKind: DefStmt [0-23]: Ident [4-5] "x"([6-11] Ident [10-11] "a": ClassicalType [6-9]: IntType [6-9], [12-12] Ident [0-0] "": ClassicalType [0-0]: Err, [13-18] Ident [17-18] "b": ClassicalType [13-16]: IntType [13-16]) + StmtKind: DefStmt [0-23]: Ident [4-5] "x"([6-11] Ident [10-11] "a": ClassicalType [6-9]: IntType [6-9], [12-12] Ident [0-0] "": ClassicalType [0-0]: Err, [13-18] Ident [17-18] "b": ClassicalType [13-16]: IntType [13-16]) [ Error( @@ -91,7 +91,7 @@ fn classical_subroutine() { "def square(int[32] x) -> int { return x ** 2; }", &expect![[r#" Stmt [0-47] - StmtKind: DefStmt [0-47]: Ident [4-10] "square"([11-20] Ident [19-20] "x": ClassicalType [11-18]: IntType[Expr [15-17]: Lit: Int(32)]: [11-18]) + StmtKind: DefStmt [0-47]: Ident [4-10] "square"([11-20] Ident [19-20] "x": ClassicalType [11-18]: IntType[Expr [15-17]: Lit: Int(32)]: [11-18]) Stmt [31-45] StmtKind: ReturnStmt [31-45]: ValueExpression Expr [38-44]: BinOp (Exp): Expr [38-39]: Ident [38-39] "x" @@ -118,7 +118,7 @@ fn old_style_args() { "def test(creg c, qreg q, creg c2[2], qreg q4[4]) -> int { return x ** 2; }", &expect![[r#" Stmt [0-74] - StmtKind: DefStmt [0-74]: Ident [4-8] "test"([9-15] Ident [14-15] "c": ClassicalType [9-15]: BitType, [17-23] Ident [22-23] "q": qubit, [25-35] Ident [30-32] "c2": ClassicalType [25-35]: BitType [25-35]: Expr [33-34]: Lit: Int(2), [37-47] Ident [42-44] "q4": qubit[Expr [45-46]: Lit: Int(4)]) + StmtKind: DefStmt [0-74]: Ident [4-8] "test"([9-15] Ident [14-15] "c": ClassicalType [9-15]: BitType, [17-23] Ident [22-23] "q": qubit, [25-35] Ident [30-32] "c2": ClassicalType [25-35]: BitType [25-35]: Expr [33-34]: Lit: Int(2), [37-47] Ident [42-44] "q4": qubit[Expr [45-46]: Lit: Int(4)]) Stmt [58-72] StmtKind: ReturnStmt [58-72]: ValueExpression Expr [65-71]: BinOp (Exp): Expr [65-66]: Ident [65-66] "x" From dbe31d8e294ea7de14e22d4b3c977955410e56a8 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Wed, 26 Feb 2025 09:58:26 -0800 Subject: [PATCH 31/98] add support for timing literals --- compiler/qsc_qasm3/src/parser/expr.rs | 38 ++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 1610bd39dc..e424406cc5 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -18,12 +18,12 @@ use crate::{ ast::{ self, list_from_iter, AssignExpr, AssignOpExpr, BinOp, BinaryOpExpr, Cast, DiscreteSet, Expr, ExprKind, FunctionCall, GateOperand, HardwareQubit, Ident, IndexElement, IndexExpr, - IndexSetItem, IndexedIdent, List, Lit, LiteralKind, MeasureExpr, RangeDefinition, TypeDef, - UnaryOp, ValueExpression, Version, + IndexSetItem, IndexedIdent, List, Lit, LiteralKind, MeasureExpr, RangeDefinition, TimeUnit, + TypeDef, UnaryOp, ValueExpression, Version, }, keyword::Keyword, lex::{ - cooked::{ComparisonOp, Literal}, + cooked::{ComparisonOp, Literal, TimingLiteralKind}, ClosedBinOp, Delim, Radix, Token, TokenKind, }, parser::{ @@ -308,10 +308,7 @@ fn lit_token(lexeme: &str, token: Token) -> Result> { span: token.span, })) } - Literal::Timing(_timing_literal_kind) => Err(Error::new(ErrorKind::Lit( - "unimplemented: timing literal", - token.span, - ))), + Literal::Timing(kind) => timing_literal(lexeme, token, kind), }, TokenKind::Keyword(Keyword::True) => Ok(Some(Lit { kind: LiteralKind::Bool(true), @@ -410,6 +407,33 @@ fn lit_bigint(lexeme: &str, radix: u32) -> Option { } } +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))?; From 7aa42acc1c5eb2ded7109a758a6be2ce843cd4a6 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Wed, 26 Feb 2025 10:16:51 -0800 Subject: [PATCH 32/98] improve TODO messages --- compiler/qsc_qasm3/src/ast.rs | 7 +++++-- compiler/qsc_qasm3/src/parser/expr.rs | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index 3a81cdb765..f954f16220 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -26,8 +26,11 @@ fn set_indentation<'a, 'b>( } } -// TODO: profile this with iai-callgrind in a large OpenQASM3 -// sample to verify that is actually faster than using Vec. +// 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]>; diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index e424406cc5..5f7cd47cc1 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -59,7 +59,9 @@ enum OpKind { Index, } -// TODO: This seems to be an unnecessary wrapper. Consider removing. +// TODO: This seems to be an unnecessary wrapper. +// OpName::Keyword is never used. +// Consider removing. #[derive(Clone, Copy)] enum OpName { Token(TokenKind), From 8dd91124dc1ada22979c9f0ffb9928e7863a4d2f Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Wed, 26 Feb 2025 15:48:36 -0800 Subject: [PATCH 33/98] address comments in PR review --- compiler/qsc_qasm3/src/parser/stmt.rs | 15 ++++++++++++--- .../qsc_qasm3/src/parser/stmt/tests/box_stmt.rs | 8 ++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 8e22f6a53d..d3d125d340 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -1103,7 +1103,14 @@ fn parse_boxable_stmt(s: &mut ParserContext) -> Result { | StmtKind::Reset(_) | StmtKind::GateCall(_) | StmtKind::GPhase(_) => Ok(stmt), - _ => Err(Error::new(ErrorKind::ClassicalStmtInBox(stmt.span))), + _ => { + s.push_error(Error::new(ErrorKind::ClassicalStmtInBox(stmt.span))); + Ok(Stmt { + span: stmt.span, + annotations: stmt.annotations, + kind: Box::new(StmtKind::Err), + }) + } } } @@ -1266,7 +1273,8 @@ fn parse_calibration_grammar_stmt(s: &mut ParserContext) -> Result Result { let lo = s.peek().span.lo; @@ -1310,7 +1318,8 @@ fn parse_defcal_stmt(s: &mut ParserContext) -> Result { } } -/// We don't support `cal` block statements. +/// 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; diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs index 81e63ad80a..31bd544bba 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs @@ -34,15 +34,19 @@ fn box_stmt_with_invalid_instruction() { "box { H q0; 2 + 4; + X q1; }", &expect![[r#" - Stmt [0-40] - StmtKind: BoxStmt [0-40]: + Stmt [0-54] + StmtKind: BoxStmt [0-54]: Stmt [14-19] StmtKind: GateCall [14-19]: Ident [14-15] "H" GateOperand IndexedIdent [16-18]: Ident [16-18] "q0"[] Stmt [28-34] StmtKind: Err + Stmt [43-48] + StmtKind: GateCall [43-48]: Ident [43-44] "X" + GateOperand IndexedIdent [45-47]: Ident [45-47] "q1"[] [ Error( From 7df17ce1b19690182d81ef9b6a2c25b846c41175 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Wed, 26 Feb 2025 16:31:48 -0800 Subject: [PATCH 34/98] add gphase --- compiler/qsc_qasm3/src/ast.rs | 19 ++-- compiler/qsc_qasm3/src/parser/error.rs | 4 + compiler/qsc_qasm3/src/parser/stmt.rs | 41 ++++++- compiler/qsc_qasm3/src/parser/stmt/tests.rs | 1 + .../src/parser/stmt/tests/gate_call.rs | 13 ++- .../qsc_qasm3/src/parser/stmt/tests/gphase.rs | 102 ++++++++++++++++++ .../src/parser/stmt/tests/measure.rs | 25 +++-- 7 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index f954f16220..606d8d86ce 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -413,7 +413,7 @@ pub enum StmtKind { For(ForStmt), If(IfStmt), GateCall(GateCall), - GPhase(QuantumPhase), + GPhase(GPhase), Include(IncludeStmt), IODeclaration(IODeclaration), Measure(MeasureStmt), @@ -1195,20 +1195,27 @@ impl Display for GateCall { } #[derive(Clone, Debug)] -pub struct QuantumPhase { +pub struct GPhase { pub span: Span, pub modifiers: List, - pub arg: Expr, - pub qubits: List>, + pub args: List, + pub qubits: List, + pub duration: Option, } -impl Display for QuantumPhase { +impl Display for GPhase { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut indent = set_indentation(indented(f), 0); - write!(indent, "QuantumPhase {}: {}", self.span, self.arg)?; + write!(indent, "GPhase {}:", self.span)?; + for arg in &self.args { + write!(indent, "\n{arg}")?; + } for qubit in &self.qubits { write!(indent, "\n{qubit}")?; } + if let Some(duration) = &self.duration { + write!(indent, "\n{duration}")?; + } Ok(()) } } diff --git a/compiler/qsc_qasm3/src/parser/error.rs b/compiler/qsc_qasm3/src/parser/error.rs index 4129ede837..b01b3350d2 100644 --- a/compiler/qsc_qasm3/src/parser/error.rs +++ b/compiler/qsc_qasm3/src/parser/error.rs @@ -122,6 +122,9 @@ pub enum ErrorKind { #[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), @@ -143,6 +146,7 @@ impl ErrorKind { 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), } } diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index d3d125d340..1911554183 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -21,8 +21,8 @@ use crate::{ CalibrationGrammarStmt, CalibrationStmt, ClassicalDeclarationStmt, ComplexType, ConstantDeclaration, ContinueStmt, DefCalStmt, DefStmt, DelayStmt, EndStmt, EnumerableSet, Expr, ExprKind, ExprStmt, ExternDecl, ExternParameter, FloatType, ForStmt, FunctionCall, - GateCall, GateModifierKind, GateOperand, IODeclaration, IOKeyword, Ident, Identifier, - IfStmt, IncludeStmt, IntType, List, LiteralKind, MeasureStmt, Pragma, + GPhase, GateCall, GateModifierKind, GateOperand, IODeclaration, IOKeyword, Ident, + Identifier, IfStmt, IncludeStmt, IntType, List, LiteralKind, MeasureStmt, Pragma, QuantumGateDefinition, QuantumGateModifier, QubitDeclaration, RangeDefinition, ResetStmt, ReturnStmt, ScalarType, ScalarTypeKind, Stmt, StmtKind, SwitchStmt, TypeDef, TypedParameter, UIntType, WhileLoop, @@ -1164,6 +1164,12 @@ 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)); + } + // As explained in the docstring, we parse the gate using the expr parser. let gate_or_expr = expr::expr(s)?; @@ -1190,6 +1196,10 @@ fn parse_gate_call_stmt(s: &mut ParserContext) -> Result { } }; + if qubits.is_empty() { + s.push_error(Error::new(ErrorKind::MissingGateCallOperands(s.span(lo)))); + } + Ok(StmtKind::GateCall(GateCall { span: s.span(lo), modifiers, @@ -1200,6 +1210,33 @@ fn parse_gate_call_stmt(s: &mut ParserContext) -> Result { })) } +fn parse_gphase( + s: &mut ParserContext, + lo: u32, + modifiers: List, +) -> Result { + token(s, TokenKind::GPhase)?; + 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(); + + let duration = opt(s, designator)?; + let qubits = gate_operand_list(s)?; + recovering_semi(s); + + Ok(GPhase { + span: s.span(lo), + modifiers, + args, + qubits, + duration, + }) +} + /// Grammar: /// `( /// INV diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests.rs b/compiler/qsc_qasm3/src/parser/stmt/tests.rs index 9b63eefbb3..5e964c6104 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests.rs @@ -16,6 +16,7 @@ mod extern_decl; mod for_loops; mod gate_call; mod gate_def; +mod gphase; mod if_stmt; mod io_decl; mod measure; diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs index 3a9c4f4f27..4e273cdc96 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs @@ -53,7 +53,18 @@ fn gate_no_qubits() { "inv @ H;", &expect![[r#" Stmt [0-8] - StmtKind: GateCall [0-8]: Ident [6-7] "H""#]], + StmtKind: GateCall [0-8]: Ident [6-7] "H" + + [ + Error( + MissingGateCallOperands( + Span { + lo: 0, + hi: 8, + }, + ), + ), + ]"#]], ); } 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..0a04b90719 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs @@ -0,0 +1,102 @@ +// 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 q0;", + &expect![[r#" + Stmt [0-10] + StmtKind: GPhase [0-10]: + GateOperand IndexedIdent [7-9]: Ident [7-9] "q0"[]"#]], + ); +} + +#[test] +fn gphase_qubit_register() { + check( + parse, + "gphase q[2];", + &expect![[r#" + Stmt [0-12] + StmtKind: GPhase [0-12]: + GateOperand IndexedIdent [7-11]: Ident [7-8] "q"[ + IndexElement: + IndexSetItem Expr [9-10]: Lit: Int(2)]"#]], + ); +} + +#[test] +fn gphase_multiple_qubits() { + check( + parse, + "gphase q0, q[4];", + &expect![[r#" + Stmt [0-16] + StmtKind: GPhase [0-16]: + GateOperand IndexedIdent [7-9]: Ident [7-9] "q0"[] + GateOperand IndexedIdent [11-15]: Ident [11-12] "q"[ + IndexElement: + IndexSetItem Expr [13-14]: Lit: Int(4)]"#]], + ); +} + +#[test] +fn gphase_no_qubits() { + check( + parse, + "inv @ gphase;", + &expect![[r#" + Stmt [0-13] + StmtKind: GPhase [0-13]:"#]], + ); +} + +#[test] +fn gphase_with_parameters() { + check( + parse, + "gphase(pi / 2) q0;", + &expect![[r#" + Stmt [0-18] + StmtKind: GPhase [0-18]: + Expr [7-13]: BinOp (Div): + Expr [7-9]: Ident [7-9] "pi" + Expr [12-13]: Lit: Int(2) + GateOperand IndexedIdent [15-17]: Ident [15-17] "q0"[]"#]], + ); +} + +#[test] +fn gphase_inv_modifier() { + check( + parse, + "inv @ gphase q0;", + &expect![[r#" + Stmt [0-16] + StmtKind: GPhase [0-16]: + GateOperand IndexedIdent [13-15]: Ident [13-15] "q0"[]"#]], + ); +} + +#[test] +fn gphase_ctrl_inv_modifiers() { + check( + parse, + "ctrl(2) @ inv @ gphase(pi / 2) c1, c2, q0;", + &expect![[r#" + Stmt [0-42] + StmtKind: GPhase [0-42]: + Expr [23-29]: BinOp (Div): + Expr [23-25]: Ident [23-25] "pi" + Expr [28-29]: Lit: Int(2) + GateOperand IndexedIdent [31-33]: Ident [31-33] "c1"[] + GateOperand IndexedIdent [35-37]: Ident [35-37] "c2"[] + GateOperand IndexedIdent [39-41]: Ident [39-41] "q0"[]"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs index 05950318df..ffb621efa9 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs @@ -41,16 +41,25 @@ fn measure_hardware_qubit() { } #[test] -fn measure_arrow() { +fn measure_arrow_into_ident() { check( parse, - "measure q[2] -> a[4];", + "measure q -> a;", &expect![[r#" - Stmt [0-21] - StmtKind: MeasureStmt [0-21]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-12]: Ident [8-9] "q"[ - IndexElement: - IndexSetItem Expr [10-11]: Lit: Int(2)], IndexedIdent [16-20]: Ident [16-17] "a"[ - IndexElement: - IndexSetItem Expr [18-19]: Lit: Int(4)]"#]], + Stmt [0-15] + StmtKind: MeasureStmt [0-15]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-9]: Ident [8-9] "q"[], IndexedIdent [13-14]: Ident [13-14] "a"[]"#]], + ); +} + +#[test] +fn measure_arrow_into_indented_ident() { + check( + parse, + "measure q -> a[1];", + &expect![[r#" + Stmt [0-18] + StmtKind: MeasureStmt [0-18]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-9]: Ident [8-9] "q"[], IndexedIdent [13-17]: Ident [13-14] "a"[ + IndexElement: + IndexSetItem Expr [15-16]: Lit: Int(1)]"#]], ); } From f12c6f3e4a2ea8968a5d29a02a78d810c6b0670b Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 27 Feb 2025 09:04:36 -0800 Subject: [PATCH 35/98] add unit test for box with designator --- .../src/parser/stmt/tests/box_stmt.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs index 31bd544bba..463f5fa21c 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs @@ -27,6 +27,28 @@ fn box_stmt() { ); } +#[test] +fn box_stmt_with_designator() { + check( + parse, + " + box[4us] { + H q0; + Rx(2.4) q1; + }", + &expect![[r#" + Stmt [5-55] + StmtKind: BoxStmt [5-55]: Expr [9-12]: Lit: Duration(4.0, Us) + Stmt [24-29] + StmtKind: GateCall [24-29]: Ident [24-25] "H" + GateOperand IndexedIdent [26-28]: Ident [26-28] "q0"[] + Stmt [38-49] + StmtKind: GateCall [38-49]: Ident [38-40] "Rx" + Expr [41-44]: Lit: Float(2.4) + GateOperand IndexedIdent [46-48]: Ident [46-48] "q1"[]"#]], + ); +} + #[test] fn box_stmt_with_invalid_instruction() { check( From 19d0db343d964b65cc7d910f8f0f54c763e3b77e Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 27 Feb 2025 09:55:46 -0800 Subject: [PATCH 36/98] require exactly one angle argument in `gphase` --- compiler/qsc_qasm3/src/parser/error.rs | 4 + compiler/qsc_qasm3/src/parser/stmt.rs | 18 ++-- .../qsc_qasm3/src/parser/stmt/tests/gphase.rs | 95 ++++++++++++------- 3 files changed, 73 insertions(+), 44 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/error.rs b/compiler/qsc_qasm3/src/parser/error.rs index b01b3350d2..bfa8a30a98 100644 --- a/compiler/qsc_qasm3/src/parser/error.rs +++ b/compiler/qsc_qasm3/src/parser/error.rs @@ -128,6 +128,9 @@ pub enum ErrorKind { #[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), } impl ErrorKind { @@ -148,6 +151,7 @@ impl ErrorKind { 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), } } } diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 1911554183..1ba08f86fc 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -197,8 +197,7 @@ pub fn parse_annotation(s: &mut ParserContext) -> Result> { fn parse_include(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; - s.expect(WordKinds::Include); - token(s, TokenKind::Keyword(crate::keyword::Keyword::Include))?; + token(s, TokenKind::Keyword(Keyword::Include))?; let next = s.peek(); let v = expr::lit(s)?; @@ -1216,6 +1215,8 @@ fn parse_gphase( modifiers: List, ) -> Result { token(s, TokenKind::GPhase)?; + + let args_lo = s.peek().span.lo; let args = opt(s, |s| { token(s, TokenKind::Open(Delim::Paren))?; let exprs = expr::expr_list(s)?; @@ -1224,6 +1225,12 @@ fn parse_gphase( })? .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); @@ -1288,7 +1295,6 @@ fn gate_operand_list(s: &mut ParserContext) -> Result> { /// Grammar: `DEFCALGRAMMAR StringLiteral SEMICOLON`. fn parse_calibration_grammar_stmt(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; - s.expect(WordKinds::DefCalGrammar); token(s, TokenKind::Keyword(Keyword::DefCalGrammar))?; let next = s.peek(); let v = expr::lit(s)?; @@ -1315,7 +1321,6 @@ fn parse_calibration_grammar_stmt(s: &mut ParserContext) -> Result Result { let lo = s.peek().span.lo; - s.expect(WordKinds::DefCal); token(s, TokenKind::Keyword(Keyword::DefCal))?; // Once we have parsed the `defcal` token, we eat all the tokens until we see an open brace. @@ -1360,7 +1365,6 @@ fn parse_defcal_stmt(s: &mut ParserContext) -> Result { /// Grammar: `CAL OPEN_BRACE pushmode(eatUntilBalancedClosingBrace)`. fn parse_cal(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; - s.expect(WordKinds::Cal); token(s, TokenKind::Keyword(Keyword::Cal))?; token(s, TokenKind::Open(Delim::Brace))?; let mut level: u32 = 1; @@ -1394,7 +1398,6 @@ fn parse_cal(s: &mut ParserContext) -> Result { /// Grammar: `BARRIER gateOperandList? SEMICOLON`. fn parse_barrier(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; - s.expect(WordKinds::Barrier); token(s, TokenKind::Keyword(Keyword::Barrier))?; let qubits = gate_operand_list(s)?; recovering_semi(s); @@ -1408,7 +1411,6 @@ fn parse_barrier(s: &mut ParserContext) -> Result { /// Grammar: `BOX designator? scope`. fn parse_box(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; - s.expect(WordKinds::Box); token(s, TokenKind::Keyword(Keyword::Box))?; let duration = opt(s, designator)?; let body = parse_box_body(s)?; @@ -1423,7 +1425,6 @@ fn parse_box(s: &mut ParserContext) -> Result { /// Grammar: `DELAY designator gateOperandList? SEMICOLON`. fn parse_delay(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; - s.expect(WordKinds::Delay); token(s, TokenKind::Delay)?; let duration = designator(s)?; let qubits = gate_operand_list(s)?; @@ -1439,7 +1440,6 @@ fn parse_delay(s: &mut ParserContext) -> Result { /// Grammar: `RESET gateOperand SEMICOLON`. fn parse_reset(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; - s.expect(WordKinds::Reset); token(s, TokenKind::Reset)?; let operand = Box::new(gate_operand(s)?); recovering_semi(s); diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs index 0a04b90719..773611b83c 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs @@ -9,11 +9,26 @@ use expect_test::expect; fn gphase() { check( parse, - "gphase q0;", + "gphase(pi / 2);", &expect![[r#" - Stmt [0-10] - StmtKind: GPhase [0-10]: - GateOperand IndexedIdent [7-9]: Ident [7-9] "q0"[]"#]], + Stmt [0-15] + StmtKind: GPhase [0-15]: + Expr [7-13]: BinOp (Div): + Expr [7-9]: Ident [7-9] "pi" + Expr [12-13]: Lit: Int(2)"#]], + ); +} + +#[test] +fn gphase_qubit_ident() { + check( + parse, + "gphase(a) q0;", + &expect![[r#" + Stmt [0-13] + StmtKind: GPhase [0-13]: + Expr [7-8]: Ident [7-8] "a" + GateOperand IndexedIdent [10-12]: Ident [10-12] "q0"[]"#]], ); } @@ -21,13 +36,14 @@ fn gphase() { fn gphase_qubit_register() { check( parse, - "gphase q[2];", + "gphase(a) q[2];", &expect![[r#" - Stmt [0-12] - StmtKind: GPhase [0-12]: - GateOperand IndexedIdent [7-11]: Ident [7-8] "q"[ + Stmt [0-15] + StmtKind: GPhase [0-15]: + Expr [7-8]: Ident [7-8] "a" + GateOperand IndexedIdent [10-14]: Ident [10-11] "q"[ IndexElement: - IndexSetItem Expr [9-10]: Lit: Int(2)]"#]], + IndexSetItem Expr [12-13]: Lit: Int(2)]"#]], ); } @@ -35,25 +51,37 @@ fn gphase_qubit_register() { fn gphase_multiple_qubits() { check( parse, - "gphase q0, q[4];", + "gphase(a) q0, q[4];", &expect![[r#" - Stmt [0-16] - StmtKind: GPhase [0-16]: - GateOperand IndexedIdent [7-9]: Ident [7-9] "q0"[] - GateOperand IndexedIdent [11-15]: Ident [11-12] "q"[ + Stmt [0-19] + StmtKind: GPhase [0-19]: + Expr [7-8]: Ident [7-8] "a" + GateOperand IndexedIdent [10-12]: Ident [10-12] "q0"[] + GateOperand IndexedIdent [14-18]: Ident [14-15] "q"[ IndexElement: - IndexSetItem Expr [13-14]: Lit: Int(4)]"#]], + IndexSetItem Expr [16-17]: Lit: Int(4)]"#]], ); } #[test] -fn gphase_no_qubits() { +fn gphase_no_arguments() { check( parse, - "inv @ gphase;", + "gphase;", &expect![[r#" - Stmt [0-13] - StmtKind: GPhase [0-13]:"#]], + Stmt [0-7] + StmtKind: GPhase [0-7]: + + [ + Error( + GPhaseInvalidArguments( + Span { + lo: 6, + hi: 6, + }, + ), + ), + ]"#]], ); } @@ -61,14 +89,13 @@ fn gphase_no_qubits() { fn gphase_with_parameters() { check( parse, - "gphase(pi / 2) q0;", + "gphase(pi / 2);", &expect![[r#" - Stmt [0-18] - StmtKind: GPhase [0-18]: + Stmt [0-15] + StmtKind: GPhase [0-15]: Expr [7-13]: BinOp (Div): Expr [7-9]: Ident [7-9] "pi" - Expr [12-13]: Lit: Int(2) - GateOperand IndexedIdent [15-17]: Ident [15-17] "q0"[]"#]], + Expr [12-13]: Lit: Int(2)"#]], ); } @@ -76,11 +103,11 @@ fn gphase_with_parameters() { fn gphase_inv_modifier() { check( parse, - "inv @ gphase q0;", + "inv @ gphase(a);", &expect![[r#" Stmt [0-16] StmtKind: GPhase [0-16]: - GateOperand IndexedIdent [13-15]: Ident [13-15] "q0"[]"#]], + Expr [13-14]: Ident [13-14] "a""#]], ); } @@ -88,15 +115,13 @@ fn gphase_inv_modifier() { fn gphase_ctrl_inv_modifiers() { check( parse, - "ctrl(2) @ inv @ gphase(pi / 2) c1, c2, q0;", + "ctrl @ inv @ gphase(pi / 2) q0;", &expect![[r#" - Stmt [0-42] - StmtKind: GPhase [0-42]: - Expr [23-29]: BinOp (Div): - Expr [23-25]: Ident [23-25] "pi" - Expr [28-29]: Lit: Int(2) - GateOperand IndexedIdent [31-33]: Ident [31-33] "c1"[] - GateOperand IndexedIdent [35-37]: Ident [35-37] "c2"[] - GateOperand IndexedIdent [39-41]: Ident [39-41] "q0"[]"#]], + Stmt [0-31] + StmtKind: GPhase [0-31]: + Expr [20-26]: BinOp (Div): + Expr [20-22]: Ident [20-22] "pi" + Expr [25-26]: Lit: Int(2) + GateOperand IndexedIdent [28-30]: Ident [28-30] "q0"[]"#]], ); } From 610ff6b53550c98d1cb141e40292238266e6ed2c Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 27 Feb 2025 11:33:54 -0800 Subject: [PATCH 37/98] fix parse_include and parse_cal_grammar --- compiler/qsc_qasm3/src/parser/stmt.rs | 33 +++++++------- compiler/qsc_qasm3/src/parser/stmt/tests.rs | 1 + .../src/parser/stmt/tests/cal_grammar.rs | 44 +++++++++++++++++++ .../src/parser/stmt/tests/include.rs | 41 +++++++++++++++++ 4 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/include.rs diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 1ba08f86fc..9d682c6445 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -200,20 +200,20 @@ fn parse_include(s: &mut ParserContext) -> Result { token(s, TokenKind::Keyword(Keyword::Include))?; let next = s.peek(); - let v = expr::lit(s)?; - if let Some(v) = v { - if let LiteralKind::String(v) = v.kind { - let r = IncludeStmt { + 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(), - }; - recovering_semi(s); - return Ok(StmtKind::Include(r)); + })); } }; Err(Error::new(ErrorKind::Rule( - "include statement", - TokenKind::Literal(Literal::String), + "string literal", + next.kind, next.span, ))) } @@ -1296,22 +1296,23 @@ fn gate_operand_list(s: &mut ParserContext) -> Result> { 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 v = expr::lit(s)?; + let lit = expr::lit(s)?; - if let Some(v) = v { - recovering_semi(s); - if let LiteralKind::String(v) = v.kind { + recovering_semi(s); + if let Some(lit) = lit { + if let LiteralKind::String(name) = lit.kind { return Ok(CalibrationGrammarStmt { span: s.span(lo), - name: v.to_string(), + name: name.to_string(), }); } }; Err(Error::new(ErrorKind::Rule( - "calibration grammar statement", - TokenKind::Literal(Literal::String), + "string literal", + next.kind, next.span, ))) } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests.rs b/compiler/qsc_qasm3/src/parser/stmt/tests.rs index 5e964c6104..0e3a2f74a9 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests.rs @@ -18,6 +18,7 @@ mod gate_call; mod gate_def; mod gphase; mod if_stmt; +mod include; mod io_decl; mod measure; mod old_style_decl; diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/cal_grammar.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/cal_grammar.rs index 0416e8bb03..f5ea7b2380 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/cal_grammar.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/cal_grammar.rs @@ -15,3 +15,47 @@ fn defcalgrammar() { StmtKind: CalibrationGrammarStmt [0-26]: 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/include.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/include.rs new file mode 100644 index 0000000000..6bba818125 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/include.rs @@ -0,0 +1,41 @@ +// 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, + }, + ), + ) + "#]]); +} From 3a70241c31e49f6807cd4c3ba94075a87edb36ec Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:01:52 -0800 Subject: [PATCH 38/98] PR review fixes --- compiler/qsc_qasm3/src/ast.rs | 10 +-- compiler/qsc_qasm3/src/parser/stmt.rs | 97 ++++++++++++++------------- 2 files changed, 54 insertions(+), 53 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index 606d8d86ce..15f9fc028d 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -400,11 +400,11 @@ pub enum StmtKind { Cal(CalibrationStmt), CalibrationGrammar(CalibrationGrammarStmt), ClassicalDecl(ClassicalDeclarationStmt), - ConstDecl(ConstantDeclaration), + ConstDecl(ConstantDeclStmt), Continue(ContinueStmt), Def(DefStmt), DefCal(DefCalStmt), - DelayStmt(DelayStmt), + Delay(DelayStmt), /// An empty statement. Empty, End(EndStmt), @@ -447,7 +447,7 @@ impl Display for StmtKind { StmtKind::Continue(continue_stmt) => write!(f, "{continue_stmt}"), StmtKind::Def(def) => write!(f, "{def}"), StmtKind::DefCal(defcal) => write!(f, "{defcal}"), - StmtKind::DelayStmt(delay) => write!(f, "{delay}"), + StmtKind::Delay(delay) => write!(f, "{delay}"), StmtKind::Empty => write!(f, "Empty"), StmtKind::End(end_stmt) => write!(f, "{end_stmt}"), StmtKind::ExprStmt(expr) => write!(f, "{expr}"), @@ -1341,14 +1341,14 @@ impl Display for IODeclaration { } #[derive(Clone, Debug)] -pub struct ConstantDeclaration { +pub struct ConstantDeclStmt { pub span: Span, pub r#type: TypeDef, pub identifier: Box, pub init_expr: Expr, } -impl Display for ConstantDeclaration { +impl Display for ConstantDeclStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!( f, diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 9d682c6445..45639e812c 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -19,7 +19,7 @@ use crate::{ list_from_iter, AccessControl, AliasDeclStmt, AngleType, Annotation, ArrayBaseTypeKind, ArrayReferenceType, ArrayType, BarrierStmt, BitType, Block, BoxStmt, BreakStmt, CalibrationGrammarStmt, CalibrationStmt, ClassicalDeclarationStmt, ComplexType, - ConstantDeclaration, ContinueStmt, DefCalStmt, DefStmt, DelayStmt, EndStmt, EnumerableSet, + 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, IntType, List, LiteralKind, MeasureStmt, Pragma, @@ -28,10 +28,7 @@ use crate::{ TypedParameter, UIntType, WhileLoop, }, keyword::Keyword, - lex::{ - cooked::{Literal, Type}, - Delim, TokenKind, - }, + lex::{cooked::Type, Delim, TokenKind}, }; use super::{prim::token, ParserContext}; @@ -97,7 +94,7 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { } else if let Some(stmt) = opt(s, parse_barrier)? { Box::new(StmtKind::Barrier(stmt)) } else if let Some(stmt) = opt(s, parse_delay)? { - Box::new(StmtKind::DelayStmt(stmt)) + Box::new(StmtKind::Delay(stmt)) } else if let Some(stmt) = opt(s, parse_reset)? { Box::new(StmtKind::Reset(stmt)) } else if let Some(stmt) = opt(s, parse_measure_stmt)? { @@ -539,7 +536,7 @@ fn parse_classical_decl(s: &mut ParserContext) -> Result { token(s, TokenKind::Eq)?; let init_expr = expr::expr(s)?; recovering_semi(s); - let decl = ConstantDeclaration { + let decl = ConstantDeclStmt { span: s.span(lo), r#type: ty, identifier, @@ -1093,24 +1090,29 @@ fn parse_alias_stmt(s: &mut ParserContext) -> Result { }) } -fn parse_boxable_stmt(s: &mut ParserContext) -> Result { - let stmt = *parse(s)?; - match &*stmt.kind { - StmtKind::Barrier(_) - | StmtKind::Break(_) - | StmtKind::DelayStmt(_) - | StmtKind::Reset(_) - | StmtKind::GateCall(_) - | StmtKind::GPhase(_) => Ok(stmt), - _ => { - s.push_error(Error::new(ErrorKind::ClassicalStmtInBox(stmt.span))); - Ok(Stmt { - span: stmt.span, - annotations: stmt.annotations, - kind: Box::new(StmtKind::Err), - }) - } - } +/// 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> { @@ -1130,15 +1132,28 @@ fn parse_many_boxable_stmt(s: &mut ParserContext) -> Result> { Ok(list_from_iter(stmts?)) } -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) +/// These "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 parse_boxable_stmt(s: &mut ParserContext) -> Result { + let stmt = *parse(s)?; + match &*stmt.kind { + StmtKind::Barrier(_) + | StmtKind::Delay(_) + | StmtKind::Reset(_) + | StmtKind::GateCall(_) + | StmtKind::GPhase(_) + | StmtKind::Box(_) => Ok(stmt), + _ => { + s.push_error(Error::new(ErrorKind::ClassicalStmtInBox(stmt.span))); + Ok(Stmt { + span: stmt.span, + annotations: stmt.annotations, + kind: Box::new(StmtKind::Err), + }) + } + } } /// In QASM3, it is hard to disambiguate between a quantum-gate-call missing modifiers @@ -1409,20 +1424,6 @@ fn parse_barrier(s: &mut ParserContext) -> Result { }) } -/// 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, - }) -} - /// Grammar: `DELAY designator gateOperandList? SEMICOLON`. fn parse_delay(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; From 23d2ed6b847bff9e7b4639f7c05a50c4c775b3b8 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 27 Feb 2025 16:16:41 -0800 Subject: [PATCH 39/98] fixes during PR review --- .../src/parser/stmt/tests/gate_call.rs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs index 4e273cdc96..a2d3abc2bb 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs @@ -111,3 +111,64 @@ fn gate_call_ctrl_inv_modifiers() { GateOperand IndexedIdent [35-37]: Ident [35-37] "q0"[]"#]], ); } + +#[test] +fn function_call_plus_ident() { + check(parse, "Name(2, 3) + a;", &expect![]); +} + +#[test] +fn function_call() { + check(parse, "Name(2, 3);", &expect![]); +} + +#[test] +fn indexed_function_call() { + check(parse, "Name(2, 3)[1];", &expect![]); +} + +#[test] +fn multi_indexed_function_call() { + check(parse, "Name(2, 3)[1, 0];", &expect![]); +} + +#[test] +fn ident() { + check(parse, "Name;", &expect![]); +} + +#[test] +fn gate_call_with_designator() { + check( + parse, + "H[2us] q;", + &expect![[r#" + Stmt [0-9] + StmtKind: GateCall [0-9]: Ident [0-1] "H" + GateOperand IndexedIdent [7-8]: Ident [7-8] "q"[] + Expr [2-5]: Lit: Duration(2.0, Us)"#]], + ); +} + +#[test] +fn gate_call_with_invalid_designator() { + check( + parse, + "H[2us][3] q;", + &expect![[r#" + Error( + InvalidGateCallDesignator( + Span { + lo: 6, + hi: 9, + }, + ), + ) + "#]], + ); +} + +#[test] +fn index_expr() { + check(parse, "Name[1];", &expect![]); +} From 6c4f17ffe83c8ccf10b403e5c46ba2e7a69a6aea Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 27 Feb 2025 16:16:51 -0800 Subject: [PATCH 40/98] fixes during PR review --- compiler/qsc_qasm3/src/ast.rs | 4 +- compiler/qsc_qasm3/src/lex/cooked.rs | 6 -- compiler/qsc_qasm3/src/lex/cooked/tests.rs | 2 - compiler/qsc_qasm3/src/parser/error.rs | 4 + compiler/qsc_qasm3/src/parser/expr.rs | 2 +- compiler/qsc_qasm3/src/parser/stmt.rs | 82 ++++++++++++++++--- .../src/parser/stmt/tests/gate_call.rs | 38 +++++++-- 7 files changed, 109 insertions(+), 29 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index 15f9fc028d..d01e08b36d 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -1171,7 +1171,7 @@ impl Display for ExternDecl { pub struct GateCall { pub span: Span, pub modifiers: List, - pub name: Identifier, + pub name: Ident, pub args: List, pub qubits: List, pub duration: Option, @@ -1768,7 +1768,7 @@ impl Display for BinaryOpExpr { #[derive(Clone, Debug)] pub struct FunctionCall { pub span: Span, - pub name: Identifier, + pub name: Ident, pub args: List, } diff --git a/compiler/qsc_qasm3/src/lex/cooked.rs b/compiler/qsc_qasm3/src/lex/cooked.rs index e15544f0e5..c891ef4519 100644 --- a/compiler/qsc_qasm3/src/lex/cooked.rs +++ b/compiler/qsc_qasm3/src/lex/cooked.rs @@ -100,8 +100,6 @@ pub enum TokenKind { NegCtrl, Dim, DurationOf, - Delay, - Reset, Measure, Literal(Literal), @@ -159,8 +157,6 @@ impl Display for TokenKind { TokenKind::NegCtrl => write!(f, "negctrl"), TokenKind::Dim => write!(f, "dim"), TokenKind::DurationOf => write!(f, "durationof"), - TokenKind::Delay => write!(f, "delay"), - TokenKind::Reset => write!(f, "reset"), TokenKind::Measure => write!(f, "measure"), TokenKind::Literal(literal) => write!(f, "literal `{literal}`"), TokenKind::Open(Delim::Brace) => write!(f, "`{{`"), @@ -749,8 +745,6 @@ impl<'a> Lexer<'a> { "negctrl" => TokenKind::NegCtrl, "dim" => TokenKind::Dim, "durationof" => TokenKind::DurationOf, - "delay" => TokenKind::Delay, - "reset" => TokenKind::Reset, "measure" => TokenKind::Measure, ident => { if let Ok(keyword) = ident.parse::() { diff --git a/compiler/qsc_qasm3/src/lex/cooked/tests.rs b/compiler/qsc_qasm3/src/lex/cooked/tests.rs index 03d169d82f..89d2bf0b5a 100644 --- a/compiler/qsc_qasm3/src/lex/cooked/tests.rs +++ b/compiler/qsc_qasm3/src/lex/cooked/tests.rs @@ -35,8 +35,6 @@ fn op_string(kind: TokenKind) -> Option { TokenKind::NegCtrl => Some("negctrl".to_string()), TokenKind::Dim => Some("dim".to_string()), TokenKind::DurationOf => Some("durationof".to_string()), - TokenKind::Delay => Some("delay".to_string()), - TokenKind::Reset => Some("reset".to_string()), TokenKind::Measure => Some("measure".to_string()), TokenKind::Semicolon => Some(";".to_string()), TokenKind::Arrow => Some("->".to_string()), diff --git a/compiler/qsc_qasm3/src/parser/error.rs b/compiler/qsc_qasm3/src/parser/error.rs index bfa8a30a98..ce4d35dfe6 100644 --- a/compiler/qsc_qasm3/src/parser/error.rs +++ b/compiler/qsc_qasm3/src/parser/error.rs @@ -131,6 +131,9 @@ pub enum ErrorKind { #[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), } impl ErrorKind { @@ -152,6 +155,7 @@ impl ErrorKind { 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), } } } diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 5f7cd47cc1..139b494892 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -462,7 +462,7 @@ fn funcall(s: &mut ParserContext, ident: ast::Ident) -> Result { token(s, TokenKind::Close(Delim::Paren))?; Ok(ExprKind::FunctionCall(FunctionCall { span: s.span(lo), - name: ast::Identifier::Ident(Box::new(ident)), + name: ident, args: args.into_iter().map(Box::new).collect(), })) } diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 45639e812c..e2240c583f 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -22,10 +22,10 @@ use crate::{ 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, IntType, List, LiteralKind, MeasureStmt, Pragma, - QuantumGateDefinition, QuantumGateModifier, QubitDeclaration, RangeDefinition, ResetStmt, - ReturnStmt, ScalarType, ScalarTypeKind, Stmt, StmtKind, SwitchStmt, TypeDef, - TypedParameter, UIntType, WhileLoop, + Identifier, IfStmt, IncludeStmt, IndexElement, IndexExpr, IndexSetItem, IntType, List, + LiteralKind, MeasureStmt, Pragma, QuantumGateDefinition, QuantumGateModifier, + QubitDeclaration, RangeDefinition, ResetStmt, ReturnStmt, ScalarType, ScalarTypeKind, Stmt, + StmtKind, SwitchStmt, TypeDef, TypedParameter, UIntType, WhileLoop, }, keyword::Keyword, lex::{cooked::Type, Delim, TokenKind}, @@ -1158,10 +1158,14 @@ fn parse_boxable_stmt(s: &mut ParserContext) -> Result { /// In QASM3, it is hard to disambiguate between a quantum-gate-call missing modifiers /// and expression statements. Consider the following expressions: -/// 1. `Rx(2, 3) q0;` -/// 2. `Rx(2, 3) + q0;` -/// 3. `Rx(2, 3);` -/// 4. `Rx;` +/// 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 @@ -1184,10 +1188,19 @@ fn parse_gate_call_stmt(s: &mut ParserContext) -> Result { 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 duration = opt(s, designator)?; + let mut duration = opt(s, designator)?; let qubits = gate_operand_list(s)?; recovering_semi(s); @@ -1199,9 +1212,11 @@ fn parse_gate_call_stmt(s: &mut ParserContext) -> Result { })); } + // 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) => (Identifier::Ident(Box::new(ident)), Default::default()), + 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, @@ -1224,6 +1239,49 @@ fn parse_gate_call_stmt(s: &mut ParserContext) -> Result { })) } +/// 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.len() == 1 { + let first_elt: IndexSetItem = (*set[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, + ))) +} + fn parse_gphase( s: &mut ParserContext, lo: u32, @@ -1427,7 +1485,7 @@ fn parse_barrier(s: &mut ParserContext) -> Result { /// Grammar: `DELAY designator gateOperandList? SEMICOLON`. fn parse_delay(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; - token(s, TokenKind::Delay)?; + token(s, TokenKind::Keyword(Keyword::Delay))?; let duration = designator(s)?; let qubits = gate_operand_list(s)?; recovering_semi(s); @@ -1442,7 +1500,7 @@ fn parse_delay(s: &mut ParserContext) -> Result { /// Grammar: `RESET gateOperand SEMICOLON`. fn parse_reset(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; - token(s, TokenKind::Reset)?; + token(s, TokenKind::Keyword(Keyword::Reset))?; let operand = Box::new(gate_operand(s)?); recovering_semi(s); diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs index a2d3abc2bb..4f91d074cb 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs @@ -114,27 +114,50 @@ fn gate_call_ctrl_inv_modifiers() { #[test] fn function_call_plus_ident() { - check(parse, "Name(2, 3) + a;", &expect![]); + check(parse, "Name(2, 3) + a;", &expect![[r#" + Stmt [0-15] + StmtKind: ExprStmt [0-15]: Expr [0-14]: BinOp (Add): + Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" + Expr [5-6]: Lit: Int(2) + Expr [8-9]: Lit: Int(3) + Expr [13-14]: Ident [13-14] "a""#]]); } #[test] fn function_call() { - check(parse, "Name(2, 3);", &expect![]); + check(parse, "Name(2, 3);", &expect![[r#" + Stmt [0-11] + StmtKind: ExprStmt [0-11]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" + Expr [5-6]: Lit: Int(2) + Expr [8-9]: Lit: Int(3)"#]]); } #[test] fn indexed_function_call() { - check(parse, "Name(2, 3)[1];", &expect![]); + check(parse, "Name(2, 3)[1];", &expect![[r#" + Stmt [0-14] + StmtKind: ExprStmt [0-14]: Expr [0-13]: IndexExpr [10-13]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" + Expr [5-6]: Lit: Int(2) + Expr [8-9]: Lit: Int(3), IndexElement: + IndexSetItem Expr [11-12]: Lit: Int(1)"#]]); } #[test] fn multi_indexed_function_call() { - check(parse, "Name(2, 3)[1, 0];", &expect![]); + check(parse, "Name(2, 3)[1, 0];", &expect![[r#" + Stmt [0-17] + StmtKind: ExprStmt [0-17]: Expr [0-16]: IndexExpr [10-16]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" + Expr [5-6]: Lit: Int(2) + Expr [8-9]: Lit: Int(3), IndexElement: + IndexSetItem Expr [11-12]: Lit: Int(1) + IndexSetItem Expr [14-15]: Lit: Int(0)"#]]); } #[test] fn ident() { - check(parse, "Name;", &expect![]); + check(parse, "Name;", &expect![[r#" + Stmt [0-5] + StmtKind: ExprStmt [0-5]: Expr [0-4]: Ident [0-4] "Name""#]]); } #[test] @@ -170,5 +193,8 @@ fn gate_call_with_invalid_designator() { #[test] fn index_expr() { - check(parse, "Name[1];", &expect![]); + check(parse, "Name[1];", &expect![[r#" + Stmt [0-8] + StmtKind: ExprStmt [0-8]: Expr [0-7]: IndexExpr [4-7]: Expr [0-4]: Ident [0-4] "Name", IndexElement: + IndexSetItem Expr [5-6]: Lit: Int(1)"#]]); } From 482d56ef59ba4f6e25ed7a8222eb0df2aa66cb6e Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Fri, 28 Feb 2025 00:07:52 -0800 Subject: [PATCH 41/98] add unit tests for gate_call --- .../src/parser/stmt/tests/expr_stmt.rs | 122 +++++++++++++++++- .../src/parser/stmt/tests/gate_call.rs | 49 +++---- 2 files changed, 143 insertions(+), 28 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs index 964a6f3e76..20af84e5d1 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs @@ -29,14 +29,128 @@ fn identifier_plus_number() { ); } +// 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] + StmtKind: ExprStmt [0-15]: Expr [0-14]: BinOp (Add): + Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" + Expr [5-6]: Lit: Int(2) + Expr [8-9]: Lit: Int(3) + Expr [13-14]: Ident [13-14] "a""#]], + ); +} + #[test] fn function_call() { check( parse, - "f(2);", + "Name(2, 3);", + &expect![[r#" + Stmt [0-11] + StmtKind: ExprStmt [0-11]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" + 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] + StmtKind: ExprStmt [0-14]: Expr [0-13]: IndexExpr [10-13]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" + Expr [5-6]: Lit: Int(2) + Expr [8-9]: Lit: Int(3), IndexElement: + IndexSetItem Expr [11-12]: Lit: Int(1)"#]], + ); +} + +#[test] +fn multi_indexed_function_call() { + check( + parse, + "Name(2, 3)[1, 0];", + &expect![[r#" + Stmt [0-17] + StmtKind: ExprStmt [0-17]: Expr [0-16]: IndexExpr [10-16]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" + Expr [5-6]: Lit: Int(2) + Expr [8-9]: Lit: Int(3), IndexElement: + IndexSetItem Expr [11-12]: Lit: Int(1) + IndexSetItem Expr [14-15]: Lit: Int(0)"#]], + ); +} + +#[test] +fn ident() { + check( + parse, + "Name;", + &expect![[r#" + Stmt [0-5] + StmtKind: ExprStmt [0-5]: Expr [0-4]: Ident [0-4] "Name""#]], + ); +} + +#[test] +fn index_expr() { + check( + parse, + "Name[1];", + &expect![[r#" + Stmt [0-8] + StmtKind: ExprStmt [0-8]: Expr [0-7]: IndexExpr [4-7]: Expr [0-4]: Ident [0-4] "Name", IndexElement: + IndexSetItem Expr [5-6]: Lit: Int(1)"#]], + ); +} + +#[test] +fn cast_expr() { + check( + parse, + "bit(0);", + &expect![[r#" + Error( + Rule( + "identifier", + Open( + Paren, + ), + Span { + lo: 3, + hi: 4, + }, + ), + ) + "#]], + ); +} + +#[test] +fn cast_expr_with_designator() { + check( + parse, + "bit[45](0);", &expect![[r#" - Stmt [0-5] - StmtKind: ExprStmt [0-5]: Expr [0-4]: FunctionCall [0-4]: Ident [0-1] "f" - Expr [2-3]: Lit: Int(2)"#]], + Error( + Rule( + "identifier", + Open( + Paren, + ), + Span { + lo: 7, + hi: 8, + }, + ), + ) + "#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs index 4f91d074cb..a155b823f0 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs @@ -114,50 +114,59 @@ fn gate_call_ctrl_inv_modifiers() { #[test] fn function_call_plus_ident() { - check(parse, "Name(2, 3) + a;", &expect![[r#" + check( + parse, + "Name(2, 3) + a q;", + &expect![[r#" Stmt [0-15] StmtKind: ExprStmt [0-15]: Expr [0-14]: BinOp (Add): Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" Expr [5-6]: Lit: Int(2) Expr [8-9]: Lit: Int(3) - Expr [13-14]: Ident [13-14] "a""#]]); + Expr [13-14]: Ident [13-14] "a""#]], + ); } #[test] fn function_call() { - check(parse, "Name(2, 3);", &expect![[r#" + check( + parse, + "Name(2, 3) q;", + &expect![[r#" Stmt [0-11] StmtKind: ExprStmt [0-11]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" Expr [5-6]: Lit: Int(2) - Expr [8-9]: Lit: Int(3)"#]]); + Expr [8-9]: Lit: Int(3)"#]], + ); } #[test] fn indexed_function_call() { - check(parse, "Name(2, 3)[1];", &expect![[r#" + check( + parse, + "Name(2, 3)[1] q;", + &expect![[r#" Stmt [0-14] StmtKind: ExprStmt [0-14]: Expr [0-13]: IndexExpr [10-13]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" Expr [5-6]: Lit: Int(2) Expr [8-9]: Lit: Int(3), IndexElement: - IndexSetItem Expr [11-12]: Lit: Int(1)"#]]); + IndexSetItem Expr [11-12]: Lit: Int(1)"#]], + ); } #[test] -fn multi_indexed_function_call() { - check(parse, "Name(2, 3)[1, 0];", &expect![[r#" +fn multi_indexed_gate_call() { + check( + parse, + "Name(2, 3)[1, 0] q;", + &expect![[r#" Stmt [0-17] StmtKind: ExprStmt [0-17]: Expr [0-16]: IndexExpr [10-16]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" Expr [5-6]: Lit: Int(2) Expr [8-9]: Lit: Int(3), IndexElement: IndexSetItem Expr [11-12]: Lit: Int(1) - IndexSetItem Expr [14-15]: Lit: Int(0)"#]]); -} - -#[test] -fn ident() { - check(parse, "Name;", &expect![[r#" - Stmt [0-5] - StmtKind: ExprStmt [0-5]: Expr [0-4]: Ident [0-4] "Name""#]]); + IndexSetItem Expr [14-15]: Lit: Int(0)"#]], + ); } #[test] @@ -190,11 +199,3 @@ fn gate_call_with_invalid_designator() { "#]], ); } - -#[test] -fn index_expr() { - check(parse, "Name[1];", &expect![[r#" - Stmt [0-8] - StmtKind: ExprStmt [0-8]: Expr [0-7]: IndexExpr [4-7]: Expr [0-4]: Ident [0-4] "Name", IndexElement: - IndexSetItem Expr [5-6]: Lit: Int(1)"#]]); -} From 35528fa233a8ac64b5be79d7112e53ee8d1d02a9 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Fri, 28 Feb 2025 09:24:38 -0800 Subject: [PATCH 42/98] change unit tests names --- .../src/parser/stmt/tests/gate_call.rs | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs index a155b823f0..98af770cd0 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs @@ -47,7 +47,7 @@ fn gate_multiple_qubits() { } #[test] -fn gate_no_qubits() { +fn gate_with_no_qubits() { check( parse, "inv @ H;", @@ -113,44 +113,50 @@ fn gate_call_ctrl_inv_modifiers() { } #[test] -fn function_call_plus_ident() { +fn binary_expr_qubit() { check( parse, "Name(2, 3) + a q;", &expect![[r#" - Stmt [0-15] - StmtKind: ExprStmt [0-15]: Expr [0-14]: BinOp (Add): - Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" - Expr [5-6]: Lit: Int(2) - Expr [8-9]: Lit: Int(3) - Expr [13-14]: Ident [13-14] "a""#]], + Error( + ExpectedItem( + Identifier, + Span { + lo: 0, + hi: 14, + }, + ), + ) + "#]], ); } #[test] -fn function_call() { +fn parametrized_gate_call() { check( parse, "Name(2, 3) q;", &expect![[r#" - Stmt [0-11] - StmtKind: ExprStmt [0-11]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" + Stmt [0-13] + StmtKind: GateCall [0-13]: Ident [0-4] "Name" Expr [5-6]: Lit: Int(2) - Expr [8-9]: Lit: Int(3)"#]], + Expr [8-9]: Lit: Int(3) + GateOperand IndexedIdent [11-12]: Ident [11-12] "q"[]"#]], ); } #[test] -fn indexed_function_call() { +fn parametrized_gate_call_with_designator() { check( parse, "Name(2, 3)[1] q;", &expect![[r#" - Stmt [0-14] - StmtKind: ExprStmt [0-14]: Expr [0-13]: IndexExpr [10-13]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" + Stmt [0-16] + StmtKind: GateCall [0-16]: Ident [0-4] "Name" Expr [5-6]: Lit: Int(2) - Expr [8-9]: Lit: Int(3), IndexElement: - IndexSetItem Expr [11-12]: Lit: Int(1)"#]], + Expr [8-9]: Lit: Int(3) + GateOperand IndexedIdent [14-15]: Ident [14-15] "q"[] + Expr [11-12]: Lit: Int(1)"#]], ); } @@ -160,12 +166,15 @@ fn multi_indexed_gate_call() { parse, "Name(2, 3)[1, 0] q;", &expect![[r#" - Stmt [0-17] - StmtKind: ExprStmt [0-17]: Expr [0-16]: IndexExpr [10-16]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" - Expr [5-6]: Lit: Int(2) - Expr [8-9]: Lit: Int(3), IndexElement: - IndexSetItem Expr [11-12]: Lit: Int(1) - IndexSetItem Expr [14-15]: Lit: Int(0)"#]], + Error( + InvalidGateCallDesignator( + Span { + lo: 10, + hi: 16, + }, + ), + ) + "#]], ); } From 8b4f4f733543538406998e0ba51a1c97eb276d7b Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Fri, 28 Feb 2025 09:35:00 -0800 Subject: [PATCH 43/98] format file --- .../qsc_qasm3/src/parser/stmt/tests/include.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/include.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/include.rs index 6bba818125..6acdd8c0b7 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/include.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/include.rs @@ -6,7 +6,10 @@ use expect_test::expect; #[test] fn include_with_no_literal() { - check(parse, "include;", &expect![[r#" + check( + parse, + "include;", + &expect![[r#" Error( Rule( "string literal", @@ -17,12 +20,16 @@ fn include_with_no_literal() { }, ), ) - "#]]); + "#]], + ); } #[test] fn include_with_non_string_literal() { - check(parse, "include 5;", &expect![[r#" + check( + parse, + "include 5;", + &expect![[r#" Error( Rule( "string literal", @@ -37,5 +44,6 @@ fn include_with_non_string_literal() { }, ), ) - "#]]); + "#]], + ); } From 9371b4374a114926e9315c3a6e99415a6dac5432 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Fri, 28 Feb 2025 13:39:34 -0800 Subject: [PATCH 44/98] disambiguate between cast expr_stmts and classical decls --- compiler/qsc_qasm3/src/ast.rs | 10 ++ .../qsc_qasm3/src/parser/completion/tests.rs | 4 +- compiler/qsc_qasm3/src/parser/expr.rs | 41 +++-- compiler/qsc_qasm3/src/parser/stmt.rs | 144 ++++++++++-------- .../src/parser/stmt/tests/expr_stmt.rs | 34 +---- .../src/parser/stmt/tests/if_stmt.rs | 35 ++--- .../src/parser/stmt/tests/switch_stmt.rs | 18 +-- .../src/parser/stmt/tests/while_loops.rs | 28 ++-- 8 files changed, 148 insertions(+), 166 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index d01e08b36d..295a59f16f 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -957,6 +957,16 @@ pub enum TypeDef { 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 { diff --git a/compiler/qsc_qasm3/src/parser/completion/tests.rs b/compiler/qsc_qasm3/src/parser/completion/tests.rs index c4d70fab6e..13d3ebe40b 100644 --- a/compiler/qsc_qasm3/src/parser/completion/tests.rs +++ b/compiler/qsc_qasm3/src/parser/completion/tests.rs @@ -36,7 +36,7 @@ fn begin_document() { "|OPENQASM 3;", &expect![[r#" WordKinds( - Annotation | Barrier | Box | Break | Cal | 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, + 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, ) "#]], ); @@ -48,7 +48,7 @@ fn end_of_version() { "OPENQASM 3;|", &expect![[r#" WordKinds( - Annotation | Barrier | Box | Break | Cal | Continue | CReg | Def | DefCal | DefCalGrammar | Delay | End | Extern | False | For | Gate | If | Include | Input | Let | Output | Pragma | QReg | Qubit | Reset | True | Return | Switch | While, + 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/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 139b494892..033fa86cf7 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -68,10 +68,12 @@ enum OpName { 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), - Stmt, } #[derive(Clone, Copy)] @@ -86,14 +88,13 @@ pub(super) fn expr(s: &mut ParserContext) -> Result { expr_op(s, OpContext::Precedence(0)) } -pub(super) fn expr_stmt(s: &mut ParserContext) -> Result { - expr_op(s, OpContext::Stmt) +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 mut lhs = if let Some(op) = prefix_op(op_name(s)) { + let lhs = if let Some(op) = prefix_op(op_name(s)) { s.advance(); let rhs = expr_op(s, OpContext::Precedence(op.precedence))?; Expr { @@ -107,10 +108,13 @@ fn expr_op(s: &mut ParserContext, context: OpContext) -> Result { expr_base(s)? }; - let min_precedence = match context { - OpContext::Precedence(p) => p, - OpContext::Stmt => 0, - }; + 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 { @@ -166,15 +170,9 @@ fn expr_base(s: &mut ParserContext) -> Result { Ok(Some(r#type)) => { // If we have a type, we expect to see a // parenthesized expression next. - token(s, TokenKind::Open(Delim::Paren))?; - let arg = paren_expr(s, lo)?; Ok(Expr { span: s.span(lo), - kind: Box::new(ExprKind::Cast(Cast { - span: s.span(lo), - r#type, - arg, - })), + kind: Box::new(cast_op(s, r#type)?), }) } Ok(None) => { @@ -468,13 +466,10 @@ fn funcall(s: &mut ParserContext, ident: ast::Ident) -> Result { } fn cast_op(s: &mut ParserContext, r#type: TypeDef) -> Result { - let lo = match &r#type { - TypeDef::Scalar(ident) => ident.span.lo, - TypeDef::Array(array) => array.span.lo, - TypeDef::ArrayReference(array) => array.span.lo, - }; - let arg = paren_expr(s, lo)?; - token(s, TokenKind::Close(Delim::Paren))?; + 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), r#type, diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index e2240c583f..13508e287c 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -18,7 +18,7 @@ use crate::{ ast::{ list_from_iter, AccessControl, AliasDeclStmt, AngleType, Annotation, ArrayBaseTypeKind, ArrayReferenceType, ArrayType, BarrierStmt, BitType, Block, BoxStmt, BreakStmt, - CalibrationGrammarStmt, CalibrationStmt, ClassicalDeclarationStmt, ComplexType, + 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, @@ -33,6 +33,7 @@ use crate::{ use super::{prim::token, ParserContext}; +#[allow(clippy::too_many_lines)] pub(super) fn parse(s: &mut ParserContext) -> Result> { let lo = s.peek().span.lo; if let Some(pragma) = opt(s, parse_pragma)? { @@ -57,7 +58,35 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { Box::new(decl) } else if let Some(include) = opt(s, parse_include)? { Box::new(include) - } else if let Some(decl) = opt(s, parse_local)? { + } else if let Some(ty) = opt(s, scalar_or_array_type)? { + if matches!(s.peek().kind, TokenKind::Identifier) { + Box::new(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(lo), + kind: Box::new(ExprKind::Cast(Cast { + span: s.span(lo), + r#type: ty, + arg, + })), + }; + Box::new(StmtKind::ExprStmt(ExprStmt { + span: s.span(lo), + expr: cast_expr, + })) + } + } else if let Some(decl) = opt(s, parse_constant_classical_decl)? { + Box::new(decl) + } else if let Some(decl) = opt(s, parse_quantum_decl)? { + Box::new(decl) + } else if let Some(decl) = opt(s, parse_io_decl)? { + Box::new(decl) + } else if let Some(decl) = opt(s, qreg_decl)? { + Box::new(decl) + } else if let Some(decl) = opt(s, creg_decl)? { Box::new(decl) } else if let Some(decl) = opt(s, parse_extern)? { Box::new(decl) @@ -79,7 +108,7 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { Box::new(StmtKind::End(stmt)) } else if let Some(stmt_kind) = opt(s, parse_gate_call_stmt)? { Box::new(stmt_kind) - } else if let Some(stmt) = opt(s, parse_expression_stmt)? { + } else if let Some(stmt) = opt(s, |s| parse_expression_stmt(s, None))? { Box::new(StmtKind::ExprStmt(stmt)) } else if let Some(alias) = opt(s, parse_alias_stmt)? { Box::new(StmtKind::Alias(alias)) @@ -262,29 +291,6 @@ fn parse_pragma(s: &mut ParserContext) -> Result { }) } -// qreg and creg are separate from classical and quantum declarations -// simply for performance reasons. The latter are more common and old -// style declarations should be rare. -fn parse_local(s: &mut ParserContext) -> Result { - if let Some(decl) = opt(s, parse_classical_decl)? { - Ok(decl) - } else if let Some(decl) = opt(s, parse_quantum_decl)? { - Ok(decl) - } else if let Some(decl) = opt(s, parse_io_decl)? { - Ok(decl) - } else if let Some(decl) = opt(s, qreg_decl)? { - Ok(decl) - } else if let Some(decl) = opt(s, creg_decl)? { - Ok(decl) - } else { - Err(Error::new(ErrorKind::Rule( - "local declaration", - s.peek().kind, - s.peek().span, - ))) - } -} - fn parse_extern(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; token(s, TokenKind::Keyword(crate::keyword::Keyword::Extern))?; @@ -520,47 +526,45 @@ pub fn scalar_or_array_type(s: &mut ParserContext) -> Result { ))) } -fn parse_classical_decl(s: &mut ParserContext) -> Result { - let lo = s.peek().span.lo; - let is_const = if s.peek().kind == TokenKind::Keyword(crate::keyword::Keyword::Const) { +fn parse_non_constant_classical_decl( + s: &mut ParserContext, + ty: TypeDef, + lo: u32, +) -> Result { + let identifier = Box::new(prim::ident(s)?); + let init_expr = if s.peek().kind == TokenKind::Eq { s.advance(); - true + Some(expr::value_expr(s)?) } else { - false + None + }; + recovering_semi(s); + let decl = ClassicalDeclarationStmt { + span: s.span(lo), + r#type: ty, + identifier, + init_expr, }; - let ty = scalar_or_array_type(s)?; - let identifier = Box::new(prim::ident(s)?); + Ok(StmtKind::ClassicalDecl(decl)) +} - let stmt = if is_const { - token(s, TokenKind::Eq)?; - let init_expr = expr::expr(s)?; - recovering_semi(s); - let decl = ConstantDeclStmt { - span: s.span(lo), - r#type: ty, - identifier, - init_expr, - }; - StmtKind::ConstDecl(decl) - } else { - let init_expr = if s.peek().kind == TokenKind::Eq { - s.advance(); - Some(expr::value_expr(s)?) - } else { - None - }; - recovering_semi(s); - let decl = ClassicalDeclarationStmt { - span: s.span(lo), - r#type: ty, - identifier, - init_expr, - }; - StmtKind::ClassicalDecl(decl) +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::expr(s)?; + recovering_semi(s); + let decl = ConstantDeclStmt { + span: s.span(lo), + r#type: ty, + identifier, + init_expr, }; - Ok(stmt) + Ok(StmtKind::ConstDecl(decl)) } pub(super) fn array_type(s: &mut ParserContext) -> Result { @@ -880,7 +884,8 @@ pub fn parse_switch_stmt(s: &mut ParserContext) -> Result { // Controlling expression. token(s, TokenKind::Open(Delim::Paren))?; - let controlling_expr = expr::paren_expr(s, lo)?; + let controlling_expr = expr::expr(s)?; + recovering_token(s, TokenKind::Close(Delim::Paren)); // Open cases bracket. token(s, TokenKind::Open(Delim::Brace))?; @@ -940,9 +945,10 @@ fn parse_block_or_stmt(s: &mut ParserContext) -> Result> { pub fn parse_if_stmt(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; token(s, TokenKind::Keyword(Keyword::If))?; - let paren_expr_lo = s.peek().span.lo; token(s, TokenKind::Open(Delim::Paren))?; - let condition = expr::paren_expr(s, paren_expr_lo)?; + 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)?) @@ -1027,9 +1033,9 @@ pub fn parse_for_loop(s: &mut ParserContext) -> Result { pub fn parse_while_loop(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; token(s, TokenKind::Keyword(Keyword::While))?; - let paren_expr_lo = s.peek().span.lo; token(s, TokenKind::Open(Delim::Paren))?; - let while_condition = expr::paren_expr(s, paren_expr_lo)?; + let while_condition = expr::expr(s)?; + recovering_token(s, TokenKind::Close(Delim::Paren)); let block = parse_block_or_stmt(s)?; Ok(WhileLoop { @@ -1064,9 +1070,13 @@ fn parse_end_stmt(s: &mut ParserContext) -> Result { } /// Grammar: `expression SEMICOLON`. -fn parse_expression_stmt(s: &mut ParserContext) -> Result { +fn parse_expression_stmt(s: &mut ParserContext, lhs: Option) -> Result { let lo = s.peek().span.lo; - let expr = expr::expr(s)?; + 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(lo), diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs index 20af84e5d1..7799100efb 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs @@ -117,19 +117,10 @@ fn cast_expr() { parse, "bit(0);", &expect![[r#" - Error( - Rule( - "identifier", - Open( - Paren, - ), - Span { - lo: 3, - hi: 4, - }, - ), - ) - "#]], + Stmt [0-6] + StmtKind: ExprStmt [0-6]: Expr [0-6]: Cast [0-6]: + ClassicalType [0-3]: BitType + Expr [4-5]: Lit: Int(0)"#]], ); } @@ -139,18 +130,9 @@ fn cast_expr_with_designator() { parse, "bit[45](0);", &expect![[r#" - Error( - Rule( - "identifier", - Open( - Paren, - ), - Span { - lo: 7, - hi: 8, - }, - ), - ) - "#]], + Stmt [0-10] + StmtKind: ExprStmt [0-10]: Expr [0-10]: Cast [0-10]: + ClassicalType [0-7]: BitType [0-7]: Expr [4-6]: Lit: Int(45) + Expr [8-9]: Lit: Int(0)"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs index c4239cade0..4d99ccfda8 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs @@ -17,10 +17,9 @@ fn simple_if_stmt() { ", &expect![[r#" Stmt [5-67] - StmtKind: IfStmt [5-67]: Expr [8-16]: Paren: - Expr [9-15]: BinOp (Eq): - Expr [9-10]: Ident [9-10] "x" - Expr [14-15]: Ident [14-15] "y" + StmtKind: IfStmt [5-67]: Expr [9-15]: BinOp (Eq): + Expr [9-10]: Ident [9-10] "x" + Expr [14-15]: Ident [14-15] "y" Stmt [27-33] StmtKind: ExprStmt [27-33]: Expr [27-32]: Assign: Expr [27-28]: Ident [27-28] "a" @@ -44,10 +43,9 @@ fn if_stmt_missing_else() { ", &expect![[r#" Stmt [5-39] - StmtKind: IfStmt [5-39]: Expr [8-16]: Paren: - Expr [9-15]: BinOp (Eq): - Expr [9-10]: Ident [9-10] "x" - Expr [14-15]: Ident [14-15] "y" + StmtKind: IfStmt [5-39]: Expr [9-15]: BinOp (Eq): + Expr [9-10]: Ident [9-10] "x" + Expr [14-15]: Ident [14-15] "y" Stmt [27-33] StmtKind: ExprStmt [27-33]: Expr [27-32]: Assign: Expr [27-28]: Ident [27-28] "a" @@ -76,15 +74,13 @@ fn nested_if_stmts() { ", &expect![[r#" Stmt [5-215] - StmtKind: IfStmt [5-215]: Expr [8-16]: Paren: - Expr [9-15]: BinOp (Eq): - Expr [9-10]: Ident [9-10] "x" - Expr [14-15]: Ident [14-15] "y" + StmtKind: IfStmt [5-215]: Expr [9-15]: BinOp (Eq): + Expr [9-10]: Ident [9-10] "x" + Expr [14-15]: Ident [14-15] "y" Stmt [27-107] - StmtKind: IfStmt [27-107]: Expr [30-40]: Paren: - Expr [31-39]: BinOp (Eq): - Expr [31-33]: Ident [31-33] "x1" - Expr [37-39]: Ident [37-39] "y1" + StmtKind: IfStmt [27-107]: Expr [31-39]: BinOp (Eq): + Expr [31-33]: Ident [31-33] "x1" + Expr [37-39]: Ident [37-39] "y1" Stmt [55-61] StmtKind: ExprStmt [55-61]: Expr [55-60]: Assign: Expr [55-56]: Ident [55-56] "a" @@ -96,10 +92,9 @@ fn nested_if_stmts() { Expr [95-96]: Lit: Int(1) Else: Stmt [129-209] - StmtKind: IfStmt [129-209]: Expr [132-142]: Paren: - Expr [133-141]: BinOp (Eq): - Expr [133-135]: Ident [133-135] "x2" - Expr [139-141]: Ident [139-141] "y2" + StmtKind: IfStmt [129-209]: Expr [133-141]: BinOp (Eq): + Expr [133-135]: Ident [133-135] "x2" + Expr [139-141]: Ident [139-141] "y2" Stmt [157-163] StmtKind: ExprStmt [157-163]: Expr [157-162]: Assign: Expr [157-158]: Ident [157-158] "a" diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs index d32cce4a7f..cc6cfdd706 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs @@ -16,8 +16,7 @@ fn simple_switch() { ", &expect![[r#" SwitchStmt [9-72]: - Target: Expr [9-19]: Paren: - Expr [17-18]: Ident [17-18] "x" + Target: Expr [17-18]: Ident [17-18] "x" Cases: Labels: Expr [37-38]: Lit: Int(1) @@ -36,8 +35,7 @@ fn no_cases_no_default() { ", &expect![[r#" SwitchStmt [9-22]: - Target: Expr [9-19]: Paren: - Expr [17-18]: Ident [17-18] "x" + Target: Expr [17-18]: Ident [17-18] "x" @@ -65,8 +63,7 @@ fn no_cases() { ", &expect![[r#" SwitchStmt [9-52]: - Target: Expr [9-19]: Paren: - Expr [17-18]: Ident [17-18] "x" + Target: Expr [17-18]: Ident [17-18] "x" Default Case: Block [40-42]: @@ -95,8 +92,7 @@ fn no_default() { ", &expect![[r#" SwitchStmt [9-54]: - Target: Expr [9-19]: Paren: - Expr [17-18]: Ident [17-18] "x" + Target: Expr [17-18]: Ident [17-18] "x" Cases: Labels: Expr [37-38]: Lit: Int(0) @@ -117,8 +113,7 @@ fn case_with_no_labels() { ", &expect![[r#" SwitchStmt [9-49]: - Target: Expr [9-19]: Paren: - Expr [17-18]: Ident [17-18] "x" + Target: Expr [17-18]: Ident [17-18] "x" Cases: Block [37-39]: @@ -149,8 +144,7 @@ fn multiple_cases() { ", &expect![[r#" SwitchStmt [9-95]: - Target: Expr [9-19]: Paren: - Expr [17-18]: Ident [17-18] "x" + Target: Expr [17-18]: Ident [17-18] "x" Cases: Labels: Expr [37-38]: Lit: Int(0) diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs index f444682c90..fe7576bbb7 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs @@ -14,10 +14,9 @@ fn simple_while() { }", &expect![[r#" Stmt [5-42] - StmtKind: WhileLoop [5-42]: Expr [11-19]: Paren: - Expr [12-18]: BinOp (Neq): - Expr [12-13]: Ident [12-13] "x" - Expr [17-18]: Lit: Int(2) + StmtKind: WhileLoop [5-42]: Expr [12-18]: BinOp (Neq): + Expr [12-13]: Ident [12-13] "x" + Expr [17-18]: Lit: Int(2) Stmt [30-36] StmtKind: ExprStmt [30-36]: Expr [30-35]: Assign: Expr [30-31]: Ident [30-31] "a" @@ -34,10 +33,9 @@ fn while_stmt_body() { a = 0;", &expect![[r#" Stmt [5-34] - StmtKind: WhileLoop [5-34]: Expr [11-19]: Paren: - Expr [12-18]: BinOp (Neq): - Expr [12-13]: Ident [12-13] "x" - Expr [17-18]: Lit: Int(2) + StmtKind: WhileLoop [5-34]: Expr [12-18]: BinOp (Neq): + Expr [12-13]: Ident [12-13] "x" + Expr [17-18]: Lit: Int(2) Stmt [28-34] StmtKind: ExprStmt [28-34]: Expr [28-33]: Assign: Expr [28-29]: Ident [28-29] "a" @@ -56,10 +54,9 @@ fn while_loop_with_continue_stmt() { }", &expect![[r#" Stmt [5-60] - StmtKind: WhileLoop [5-60]: Expr [11-19]: Paren: - Expr [12-18]: BinOp (Neq): - Expr [12-13]: Ident [12-13] "x" - Expr [17-18]: Lit: Int(2) + StmtKind: WhileLoop [5-60]: Expr [12-18]: BinOp (Neq): + Expr [12-13]: Ident [12-13] "x" + Expr [17-18]: Lit: Int(2) Stmt [30-36] StmtKind: ExprStmt [30-36]: Expr [30-35]: Assign: Expr [30-31]: Ident [30-31] "a" @@ -80,10 +77,9 @@ fn while_loop_with_break_stmt() { }", &expect![[r#" Stmt [5-57] - StmtKind: WhileLoop [5-57]: Expr [11-19]: Paren: - Expr [12-18]: BinOp (Neq): - Expr [12-13]: Ident [12-13] "x" - Expr [17-18]: Lit: Int(2) + StmtKind: WhileLoop [5-57]: Expr [12-18]: BinOp (Neq): + Expr [12-13]: Ident [12-13] "x" + Expr [17-18]: Lit: Int(2) Stmt [30-36] StmtKind: ExprStmt [30-36]: Expr [30-35]: Assign: Expr [30-31]: Ident [30-31] "a" From f6786e98938cf476f1d057511afe32bd606fcf56 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Fri, 28 Feb 2025 13:46:30 -0800 Subject: [PATCH 45/98] update cast unit tests --- compiler/qsc_qasm3/src/parser/expr/tests.rs | 100 ++++++++------------ 1 file changed, 40 insertions(+), 60 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index 655084d9d5..6e53530b95 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -751,10 +751,9 @@ fn cast_to_bit() { expr, "bit(0)", &expect![[r#" - Expr [0-6]: Cast [0-6]: + Expr [0-3]: Cast [0-6]: ClassicalType [0-3]: BitType - Expr [0-6]: Paren: - Expr [4-5]: Lit: Int(0)"#]], + Expr [4-5]: Lit: Int(0)"#]], ); } @@ -764,10 +763,9 @@ fn cast_to_bit_with_designator() { expr, "bit[4](0)", &expect![[r#" - Expr [0-9]: Cast [0-9]: + Expr [0-6]: Cast [0-9]: ClassicalType [0-6]: BitType [0-6]: Expr [4-5]: Lit: Int(4) - Expr [0-9]: Paren: - Expr [7-8]: Lit: Int(0)"#]], + Expr [7-8]: Lit: Int(0)"#]], ); } @@ -777,10 +775,9 @@ fn cast_to_int() { expr, "int(0)", &expect![[r#" - Expr [0-6]: Cast [0-6]: + Expr [0-3]: Cast [0-6]: ClassicalType [0-3]: IntType [0-3] - Expr [0-6]: Paren: - Expr [4-5]: Lit: Int(0)"#]], + Expr [4-5]: Lit: Int(0)"#]], ); } @@ -790,10 +787,9 @@ fn cast_to_int_with_designator() { expr, "int[64](0)", &expect![[r#" - Expr [0-10]: Cast [0-10]: + Expr [0-7]: Cast [0-10]: ClassicalType [0-7]: IntType[Expr [4-6]: Lit: Int(64)]: [0-7] - Expr [0-10]: Paren: - Expr [8-9]: Lit: Int(0)"#]], + Expr [8-9]: Lit: Int(0)"#]], ); } @@ -803,10 +799,9 @@ fn cast_to_uint() { expr, "uint(0)", &expect![[r#" - Expr [0-7]: Cast [0-7]: + Expr [0-4]: Cast [0-7]: ClassicalType [0-4]: UIntType [0-4] - Expr [0-7]: Paren: - Expr [5-6]: Lit: Int(0)"#]], + Expr [5-6]: Lit: Int(0)"#]], ); } @@ -816,10 +811,9 @@ fn cast_to_uint_with_designator() { expr, "uint[64](0)", &expect![[r#" - Expr [0-11]: Cast [0-11]: + Expr [0-8]: Cast [0-11]: ClassicalType [0-8]: UIntType[Expr [5-7]: Lit: Int(64)]: [0-8] - Expr [0-11]: Paren: - Expr [9-10]: Lit: Int(0)"#]], + Expr [9-10]: Lit: Int(0)"#]], ); } @@ -829,10 +823,9 @@ fn cast_to_float() { expr, "float(0)", &expect![[r#" - Expr [0-8]: Cast [0-8]: + Expr [0-5]: Cast [0-8]: ClassicalType [0-5]: FloatType [0-5] - Expr [0-8]: Paren: - Expr [6-7]: Lit: Int(0)"#]], + Expr [6-7]: Lit: Int(0)"#]], ); } @@ -842,10 +835,9 @@ fn cast_to_float_with_designator() { expr, "float[64](0)", &expect![[r#" - Expr [0-12]: Cast [0-12]: + Expr [0-9]: Cast [0-12]: ClassicalType [0-9]: FloatType[Expr [6-8]: Lit: Int(64)]: [0-9] - Expr [0-12]: Paren: - Expr [10-11]: Lit: Int(0)"#]], + Expr [10-11]: Lit: Int(0)"#]], ); } @@ -855,10 +847,9 @@ fn cast_to_complex() { expr, "complex[float](0)", &expect![[r#" - Expr [0-17]: Cast [0-17]: + Expr [0-14]: Cast [0-17]: ClassicalType [0-14]: ComplexType[float[FloatType [8-13]]]: [0-14] - Expr [0-17]: Paren: - Expr [15-16]: Lit: Int(0)"#]], + Expr [15-16]: Lit: Int(0)"#]], ); } @@ -868,10 +859,9 @@ fn cast_to_complex_with_designator() { expr, "complex[float[64]](0)", &expect![[r#" - Expr [0-21]: Cast [0-21]: + Expr [0-18]: Cast [0-21]: ClassicalType [0-18]: ComplexType[float[FloatType[Expr [14-16]: Lit: Int(64)]: [8-17]]]: [0-18] - Expr [0-21]: Paren: - Expr [19-20]: Lit: Int(0)"#]], + Expr [19-20]: Lit: Int(0)"#]], ); } @@ -881,10 +871,9 @@ fn cast_to_bool() { expr, "bool(0)", &expect![[r#" - Expr [0-7]: Cast [0-7]: + Expr [0-4]: Cast [0-7]: ClassicalType [0-4]: BoolType - Expr [0-7]: Paren: - Expr [5-6]: Lit: Int(0)"#]], + Expr [5-6]: Lit: Int(0)"#]], ); } @@ -894,10 +883,9 @@ fn cast_to_duration() { expr, "duration(0)", &expect![[r#" - Expr [0-11]: Cast [0-11]: + Expr [0-8]: Cast [0-11]: ClassicalType [0-8]: Duration - Expr [0-11]: Paren: - Expr [9-10]: Lit: Int(0)"#]], + Expr [9-10]: Lit: Int(0)"#]], ); } @@ -907,10 +895,9 @@ fn cast_to_stretch() { expr, "stretch(0)", &expect![[r#" - Expr [0-10]: Cast [0-10]: + Expr [0-7]: Cast [0-10]: ClassicalType [0-7]: Stretch - Expr [0-10]: Paren: - Expr [8-9]: Lit: Int(0)"#]], + Expr [8-9]: Lit: Int(0)"#]], ); } @@ -920,11 +907,10 @@ fn cast_to_int_array() { expr, "array[int[64], 4](0)", &expect![[r#" - Expr [0-20]: Cast [0-20]: + Expr [0-17]: Cast [0-20]: ArrayType [0-17]: ArrayBaseTypeKind IntType[Expr [10-12]: Lit: Int(64)]: [6-13] Expr [15-16]: Lit: Int(4) - Expr [0-20]: Paren: - Expr [18-19]: Lit: Int(0)"#]], + Expr [18-19]: Lit: Int(0)"#]], ); } @@ -934,11 +920,10 @@ fn cast_to_uint_array() { expr, "array[uint[64], 4](0)", &expect![[r#" - Expr [0-21]: Cast [0-21]: + Expr [0-18]: Cast [0-21]: ArrayType [0-18]: ArrayBaseTypeKind UIntType[Expr [11-13]: Lit: Int(64)]: [6-14] Expr [16-17]: Lit: Int(4) - Expr [0-21]: Paren: - Expr [19-20]: Lit: Int(0)"#]], + Expr [19-20]: Lit: Int(0)"#]], ); } @@ -948,11 +933,10 @@ fn cast_to_float_array() { expr, "array[float[64], 4](0)", &expect![[r#" - Expr [0-22]: Cast [0-22]: + Expr [0-19]: Cast [0-22]: ArrayType [0-19]: ArrayBaseTypeKind FloatType[Expr [12-14]: Lit: Int(64)]: [6-15] Expr [17-18]: Lit: Int(4) - Expr [0-22]: Paren: - Expr [20-21]: Lit: Int(0)"#]], + Expr [20-21]: Lit: Int(0)"#]], ); } @@ -962,11 +946,10 @@ fn cast_to_angle_array() { expr, "array[angle[64], 4](0)", &expect![[r#" - Expr [0-22]: Cast [0-22]: + Expr [0-19]: Cast [0-22]: ArrayType [0-19]: ArrayBaseTypeKind AngleType [6-15]: Expr [12-14]: Lit: Int(64) Expr [17-18]: Lit: Int(4) - Expr [0-22]: Paren: - Expr [20-21]: Lit: Int(0)"#]], + Expr [20-21]: Lit: Int(0)"#]], ); } @@ -976,11 +959,10 @@ fn cast_to_bool_array() { expr, "array[bool, 4](0)", &expect![[r#" - Expr [0-17]: Cast [0-17]: + Expr [0-14]: Cast [0-17]: ArrayType [0-14]: ArrayBaseTypeKind BoolType Expr [12-13]: Lit: Int(4) - Expr [0-17]: Paren: - Expr [15-16]: Lit: Int(0)"#]], + Expr [15-16]: Lit: Int(0)"#]], ); } @@ -990,11 +972,10 @@ fn cast_to_duration_array() { expr, "array[duration, 4](0)", &expect![[r#" - Expr [0-21]: Cast [0-21]: + Expr [0-18]: Cast [0-21]: ArrayType [0-18]: ArrayBaseTypeKind DurationType Expr [16-17]: Lit: Int(4) - Expr [0-21]: Paren: - Expr [19-20]: Lit: Int(0)"#]], + Expr [19-20]: Lit: Int(0)"#]], ); } @@ -1004,11 +985,10 @@ fn cast_to_complex_array() { expr, "array[complex[float[32]], 4](0)", &expect![[r#" - Expr [0-31]: Cast [0-31]: + Expr [0-28]: Cast [0-31]: ArrayType [0-28]: ArrayBaseTypeKind ComplexType[float[FloatType[Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24] Expr [26-27]: Lit: Int(4) - Expr [0-31]: Paren: - Expr [29-30]: Lit: Int(0)"#]], + Expr [29-30]: Lit: Int(0)"#]], ); } From 8c3eed4a199e6aa8578f6240b4696f17687795f6 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Fri, 28 Feb 2025 15:40:19 -0800 Subject: [PATCH 46/98] make expr unit tests also run for expr_stmts by adding a semicolon at the end --- compiler/qsc_qasm3/src/parser/expr.rs | 3 +- compiler/qsc_qasm3/src/parser/expr/tests.rs | 431 +++++++----------- compiler/qsc_qasm3/src/parser/stmt.rs | 15 +- .../src/parser/stmt/tests/classical_decl.rs | 113 +---- compiler/qsc_qasm3/src/parser/tests.rs | 1 - 5 files changed, 192 insertions(+), 371 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 033fa86cf7..218569b7ba 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -170,9 +170,10 @@ fn expr_base(s: &mut ParserContext) -> Result { 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: Box::new(cast_op(s, r#type)?), + kind, }) } Ok(None) => { diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index 6e53530b95..e01d468c27 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -1,31 +1,72 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::parser::tests::check; - use super::expr; - -use expect_test::expect; +use crate::{ + ast::StmtKind, + parser::{scan::ParserContext, stmt, tests::check, Parser}, +}; +use expect_test::{expect, Expect}; + +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:#?}")), + } +} + +/// 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)"]); + 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)"]); + 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)"]); + check_expr("0123", &expect!["Expr [0-4]: Lit: Int(123)"]); } #[test] fn lit_int_max() { - check( - expr, + check_expr( "9_223_372_036_854_775_807", &expect!["Expr [0-25]: Lit: Int(9223372036854775807)"], ); @@ -38,8 +79,7 @@ fn lit_int_max() { // of i64::MIN also need to be tested. #[test] fn lit_int_overflow_min() { - check( - expr, + check_expr( "9_223_372_036_854_775_808", &expect!["Expr [0-25]: Lit: Int(-9223372036854775808)"], ); @@ -47,8 +87,7 @@ fn lit_int_overflow_min() { #[test] fn lit_int_overflow_min_hexadecimal() { - check( - expr, + check_expr( "0x8000000000000000", &expect!["Expr [0-18]: Lit: Int(-9223372036854775808)"], ); @@ -56,8 +95,7 @@ fn lit_int_overflow_min_hexadecimal() { #[test] fn lit_int_overflow_min_binary() { - check( - expr, + check_expr( "0b1000000000000000000000000000000000000000000000000000000000000000", &expect!["Expr [0-66]: Lit: Int(-9223372036854775808)"], ); @@ -65,8 +103,7 @@ fn lit_int_overflow_min_binary() { #[test] fn lit_int_too_big_for_i64() { - check( - expr, + check_expr( "9_223_372_036_854_775_809", &expect!["Expr [0-25]: Lit: BigInt(9223372036854775809)"], ); @@ -74,8 +111,7 @@ fn lit_int_too_big_for_i64() { #[test] fn lit_int_too_big_hexadecimal_promotes_to_bigint() { - check( - expr, + check_expr( "0x8000000000000001", &expect!["Expr [0-18]: Lit: BigInt(9223372036854775809)"], ); @@ -83,8 +119,7 @@ fn lit_int_too_big_hexadecimal_promotes_to_bigint() { #[test] fn lit_int_too_big_binary_promotes_to_bigint() { - check( - expr, + check_expr( "0b1000000000000000000000000000000000000000000000000000000000000001", &expect!["Expr [0-66]: Lit: BigInt(9223372036854775809)"], ); @@ -95,10 +130,8 @@ fn lit_int_too_big_binary_promotes_to_bigint() { // 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] -#[ignore = "Re-enable when we support unary ops"] fn lit_int_min() { - check( - expr, + check_expr( "-9_223_372_036_854_775_808", &expect![[r#" Expr [0-26]: UnOp (Neg): @@ -108,23 +141,22 @@ fn lit_int_min() { #[test] fn lit_int_hexadecimal() { - check(expr, "0x1a2b3c", &expect!["Expr [0-8]: Lit: Int(1715004)"]); + 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)"]); + 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)"]); + check_expr("0b10110", &expect!["Expr [0-7]: Lit: Int(22)"]); } #[test] fn lit_bigint_hexadecimal() { - check( - expr, + check_expr( "0x1a2b3c1a2b3c1a2b3c1a", &expect!["Expr [0-22]: Lit: BigInt(123579069371309093501978)"], ); @@ -132,8 +164,7 @@ fn lit_bigint_hexadecimal() { #[test] fn lit_bigint_hexadecimal_capital_x() { - check( - expr, + check_expr( "0X1a2b3c1a2b3c1a2b3c1a", &expect!["Expr [0-22]: Lit: BigInt(123579069371309093501978)"], ); @@ -141,8 +172,7 @@ fn lit_bigint_hexadecimal_capital_x() { #[test] fn lit_bigint_octal() { - check( - expr, + check_expr( "0o1234567123456712345671234", &expect!["Expr [0-27]: Lit: BigInt(6167970861177743307420)"], ); @@ -150,8 +180,7 @@ fn lit_bigint_octal() { #[test] fn lit_bigint_octal_capital_o() { - check( - expr, + check_expr( "0O1234567123456712345671234", &expect!["Expr [0-27]: Lit: BigInt(6167970861177743307420)"], ); @@ -159,8 +188,7 @@ fn lit_bigint_octal_capital_o() { #[test] fn lit_bigint_binary() { - check( - expr, + check_expr( "0b1011010110101101011010110101101011010110101101011010110101101011", &expect!["Expr [0-66]: Lit: BigInt(13091237729729359211)"], ); @@ -168,8 +196,7 @@ fn lit_bigint_binary() { #[test] fn lit_bigint_binary_capital_b() { - check( - expr, + check_expr( "0B1011010110101101011010110101101011010110101101011010110101101011", &expect!["Expr [0-66]: Lit: BigInt(13091237729729359211)"], ); @@ -177,37 +204,32 @@ fn lit_bigint_binary_capital_b() { #[test] fn lit_float() { - check(expr, "1.23", &expect!["Expr [0-4]: Lit: Float(1.23)"]); + 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)"]); + 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)"]); + 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)"], - ); + 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)"]); + check_expr("0.23", &expect!["Expr [0-4]: Lit: Float(0.23)"]); } #[test] fn lit_float_trailing_exp_0() { - check( - expr, + check_expr( "0e", &expect![[r#" Error( @@ -225,8 +247,7 @@ fn lit_float_trailing_exp_0() { #[test] fn lit_float_trailing_exp_1() { - check( - expr, + check_expr( "1e", &expect![[r#" Error( @@ -244,8 +265,7 @@ fn lit_float_trailing_exp_1() { #[test] fn lit_float_trailing_dot_trailing_exp() { - check( - expr, + check_expr( "1.e", &expect![[r#" Error( @@ -263,8 +283,7 @@ fn lit_float_trailing_dot_trailing_exp() { #[test] fn lit_float_dot_trailing_exp() { - check( - expr, + check_expr( "1.2e", &expect![[r#" Error( @@ -282,8 +301,7 @@ fn lit_float_dot_trailing_exp() { #[test] fn lit_float_trailing_exp_dot() { - check( - expr, + check_expr( "1e.", &expect![[r#" Error( @@ -300,55 +318,26 @@ fn lit_float_trailing_exp_dot() { } #[test] -#[ignore = "Re-enable when we support more than literals"] fn lit_int_hexadecimal_dot() { - check( - expr, + check_expr( "0x123.45", - &expect![[r#" - Expr [0-6]: Field: - Expr [0-5]: Lit: Int(291) - Err - - [ - Error( - Rule( - "identifier", - Int( - Decimal, - ), - Span { - lo: 6, - hi: 8, - }, - ), - ), - ]"#]], + &expect!["Expr [0-5]: Lit: Int(291)"], ); } #[test] fn lit_string() { - check( - expr, - r#""foo""#, - &expect![[r#"Expr [0-5]: Lit: String("foo")"#]], - ); + 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")"#]], - ); + check_expr(r#"'foo'"#, &expect![[r#"Expr [0-5]: Lit: String("foo")"#]]); } #[test] fn lit_string_escape_quote() { - check( - expr, + check_expr( r#""foo\"bar""#, &expect![[r#"Expr [0-10]: Lit: String("foo\"bar")"#]], ); @@ -356,8 +345,7 @@ fn lit_string_escape_quote() { #[test] fn lit_string_single_quote_escape_double_quote() { - check( - expr, + check_expr( r#"'foo\"bar'"#, &expect![[r#"Expr [0-10]: Lit: String("foo\"bar")"#]], ); @@ -365,80 +353,47 @@ fn lit_string_single_quote_escape_double_quote() { #[test] fn lit_string_escape_backslash() { - check( - expr, - r#""\\""#, - &expect![[r#"Expr [0-4]: Lit: String("\\")"#]], - ); + 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("\\")"#]], - ); + 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")"#]], - ); + 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")"#]], - ); + 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")"#]], - ); + 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")"#]], - ); + 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")"#]], - ); + 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")"#]], - ); + check_expr(r#"'\t'"#, &expect![[r#"Expr [0-4]: Lit: String("\t")"#]]); } #[test] fn lit_string_unknown_escape() { - check( - expr, + check_expr( r#""\x""#, &expect![[r#" Error( @@ -488,23 +443,22 @@ fn lit_string_unmatched_quote() { #[test] fn lit_string_empty() { - check(expr, r#""""#, &expect![[r#"Expr [0-2]: Lit: String("")"#]]); + check_expr(r#""""#, &expect![[r#"Expr [0-2]: Lit: String("")"#]]); } #[test] fn lit_false() { - check(expr, "false", &expect!["Expr [0-5]: Lit: Bool(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)"]); + check_expr("true", &expect!["Expr [0-4]: Lit: Bool(true)"]); } #[test] fn lit_bitstring() { - check( - expr, + check_expr( r#""101010101""#, &expect![[r#"Expr [0-11]: Lit: Bitstring("101010101")"#]], ); @@ -512,8 +466,7 @@ fn lit_bitstring() { #[test] fn lit_bitstring_preserves_leading_zeroes() { - check( - expr, + check_expr( r#""00011000""#, &expect![[r#"Expr [0-10]: Lit: Bitstring("00011000")"#]], ); @@ -521,8 +474,7 @@ fn lit_bitstring_preserves_leading_zeroes() { #[test] fn lit_bitstring_separators() { - check( - expr, + check_expr( r#""10_10_10_101""#, &expect![[r#"Expr [0-14]: Lit: Bitstring("101010101")"#]], ); @@ -562,50 +514,37 @@ fn lit_bitstring_unmatched_quote() { #[test] fn lit_float_imag() { - check( - expr, - r#"10.3im"#, - &expect!["Expr [0-6]: Lit: Imaginary(10.3)"], - ); + 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)"], - ); + check_expr(r#"10.3 im"#, &expect!["Expr [0-8]: Lit: Imaginary(10.3)"]); } #[test] fn lit_int_imag() { - check(expr, r#"10"#, &expect!["Expr [0-2]: Lit: Int(10)"]); + 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)"], - ); + 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)"]); + 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)"]); + check_expr("1.im", &expect!["Expr [0-4]: Lit: Imaginary(1.0)"]); } #[test] fn lit_float_imag_underscore() { - check( - expr, + check_expr( "123_456.78im", &expect!["Expr [0-12]: Lit: Imaginary(123456.78)"], ); @@ -613,13 +552,12 @@ fn lit_float_imag_underscore() { #[test] fn lit_float_imag_leading_zero() { - check(expr, "0.23im", &expect!["Expr [0-6]: Lit: Imaginary(0.23)"]); + check_expr("0.23im", &expect!["Expr [0-6]: Lit: Imaginary(0.23)"]); } #[test] fn pratt_parsing_mul_add() { - check( - expr, + check_expr( "1 + 2 * 3", &expect![[r#" Expr [0-9]: BinOp (Add): @@ -632,8 +570,7 @@ fn pratt_parsing_mul_add() { #[test] fn pratt_parsing_parens() { - check( - expr, + check_expr( "(1 + 2) * 3", &expect![[r#" Expr [0-11]: BinOp (Mul): @@ -647,8 +584,7 @@ fn pratt_parsing_parens() { #[test] fn prat_parsing_mul_unary() { - check( - expr, + check_expr( "2 * -3", &expect![[r#" Expr [0-6]: BinOp (Mul): @@ -660,8 +596,7 @@ fn prat_parsing_mul_unary() { #[test] fn prat_parsing_unary_mul() { - check( - expr, + check_expr( "-2 * 3", &expect![[r#" Expr [0-6]: BinOp (Mul): @@ -673,8 +608,7 @@ fn prat_parsing_unary_mul() { #[test] fn prat_parsing_exp_funcall() { - check( - expr, + check_expr( "2 ** square(3)", &expect![[r#" Expr [0-14]: BinOp (Exp): @@ -686,8 +620,7 @@ fn prat_parsing_exp_funcall() { #[test] fn prat_parsing_funcall_exp() { - check( - expr, + check_expr( "square(2) ** 3", &expect![[r#" Expr [0-14]: BinOp (Exp): @@ -699,8 +632,7 @@ fn prat_parsing_funcall_exp() { #[test] fn prat_parsing_funcall_exp_arg() { - check( - expr, + check_expr( "square(2 ** 3)", &expect![[r#" Expr [0-14]: FunctionCall [0-14]: Ident [0-6] "square" @@ -712,8 +644,7 @@ fn prat_parsing_funcall_exp_arg() { #[test] fn funcall() { - check( - expr, + check_expr( "square(2)", &expect![[r#" Expr [0-9]: FunctionCall [0-9]: Ident [0-6] "square" @@ -723,8 +654,7 @@ fn funcall() { #[test] fn funcall_multiple_args() { - check( - expr, + check_expr( "square(2, 3)", &expect![[r#" Expr [0-12]: FunctionCall [0-12]: Ident [0-6] "square" @@ -735,8 +665,7 @@ fn funcall_multiple_args() { #[test] fn funcall_multiple_args_trailing_comma() { - check( - expr, + check_expr( "square(2, 3,)", &expect![[r#" Expr [0-13]: FunctionCall [0-13]: Ident [0-6] "square" @@ -747,11 +676,10 @@ fn funcall_multiple_args_trailing_comma() { #[test] fn cast_to_bit() { - check( - expr, + check_expr( "bit(0)", &expect![[r#" - Expr [0-3]: Cast [0-6]: + Expr [0-6]: Cast [0-6]: ClassicalType [0-3]: BitType Expr [4-5]: Lit: Int(0)"#]], ); @@ -759,11 +687,10 @@ fn cast_to_bit() { #[test] fn cast_to_bit_with_designator() { - check( - expr, + check_expr( "bit[4](0)", &expect![[r#" - Expr [0-6]: Cast [0-9]: + Expr [0-9]: Cast [0-9]: ClassicalType [0-6]: BitType [0-6]: Expr [4-5]: Lit: Int(4) Expr [7-8]: Lit: Int(0)"#]], ); @@ -771,11 +698,10 @@ fn cast_to_bit_with_designator() { #[test] fn cast_to_int() { - check( - expr, + check_expr( "int(0)", &expect![[r#" - Expr [0-3]: Cast [0-6]: + Expr [0-6]: Cast [0-6]: ClassicalType [0-3]: IntType [0-3] Expr [4-5]: Lit: Int(0)"#]], ); @@ -783,11 +709,10 @@ fn cast_to_int() { #[test] fn cast_to_int_with_designator() { - check( - expr, + check_expr( "int[64](0)", &expect![[r#" - Expr [0-7]: Cast [0-10]: + Expr [0-10]: Cast [0-10]: ClassicalType [0-7]: IntType[Expr [4-6]: Lit: Int(64)]: [0-7] Expr [8-9]: Lit: Int(0)"#]], ); @@ -795,11 +720,10 @@ fn cast_to_int_with_designator() { #[test] fn cast_to_uint() { - check( - expr, + check_expr( "uint(0)", &expect![[r#" - Expr [0-4]: Cast [0-7]: + Expr [0-7]: Cast [0-7]: ClassicalType [0-4]: UIntType [0-4] Expr [5-6]: Lit: Int(0)"#]], ); @@ -807,11 +731,10 @@ fn cast_to_uint() { #[test] fn cast_to_uint_with_designator() { - check( - expr, + check_expr( "uint[64](0)", &expect![[r#" - Expr [0-8]: Cast [0-11]: + Expr [0-11]: Cast [0-11]: ClassicalType [0-8]: UIntType[Expr [5-7]: Lit: Int(64)]: [0-8] Expr [9-10]: Lit: Int(0)"#]], ); @@ -819,11 +742,10 @@ fn cast_to_uint_with_designator() { #[test] fn cast_to_float() { - check( - expr, + check_expr( "float(0)", &expect![[r#" - Expr [0-5]: Cast [0-8]: + Expr [0-8]: Cast [0-8]: ClassicalType [0-5]: FloatType [0-5] Expr [6-7]: Lit: Int(0)"#]], ); @@ -831,11 +753,10 @@ fn cast_to_float() { #[test] fn cast_to_float_with_designator() { - check( - expr, + check_expr( "float[64](0)", &expect![[r#" - Expr [0-9]: Cast [0-12]: + Expr [0-12]: Cast [0-12]: ClassicalType [0-9]: FloatType[Expr [6-8]: Lit: Int(64)]: [0-9] Expr [10-11]: Lit: Int(0)"#]], ); @@ -843,11 +764,10 @@ fn cast_to_float_with_designator() { #[test] fn cast_to_complex() { - check( - expr, + check_expr( "complex[float](0)", &expect![[r#" - Expr [0-14]: Cast [0-17]: + Expr [0-17]: Cast [0-17]: ClassicalType [0-14]: ComplexType[float[FloatType [8-13]]]: [0-14] Expr [15-16]: Lit: Int(0)"#]], ); @@ -855,11 +775,10 @@ fn cast_to_complex() { #[test] fn cast_to_complex_with_designator() { - check( - expr, + check_expr( "complex[float[64]](0)", &expect![[r#" - Expr [0-18]: Cast [0-21]: + Expr [0-21]: Cast [0-21]: ClassicalType [0-18]: ComplexType[float[FloatType[Expr [14-16]: Lit: Int(64)]: [8-17]]]: [0-18] Expr [19-20]: Lit: Int(0)"#]], ); @@ -867,11 +786,10 @@ fn cast_to_complex_with_designator() { #[test] fn cast_to_bool() { - check( - expr, + check_expr( "bool(0)", &expect![[r#" - Expr [0-4]: Cast [0-7]: + Expr [0-7]: Cast [0-7]: ClassicalType [0-4]: BoolType Expr [5-6]: Lit: Int(0)"#]], ); @@ -879,11 +797,10 @@ fn cast_to_bool() { #[test] fn cast_to_duration() { - check( - expr, + check_expr( "duration(0)", &expect![[r#" - Expr [0-8]: Cast [0-11]: + Expr [0-11]: Cast [0-11]: ClassicalType [0-8]: Duration Expr [9-10]: Lit: Int(0)"#]], ); @@ -891,11 +808,10 @@ fn cast_to_duration() { #[test] fn cast_to_stretch() { - check( - expr, + check_expr( "stretch(0)", &expect![[r#" - Expr [0-7]: Cast [0-10]: + Expr [0-10]: Cast [0-10]: ClassicalType [0-7]: Stretch Expr [8-9]: Lit: Int(0)"#]], ); @@ -903,11 +819,10 @@ fn cast_to_stretch() { #[test] fn cast_to_int_array() { - check( - expr, + check_expr( "array[int[64], 4](0)", &expect![[r#" - Expr [0-17]: Cast [0-20]: + Expr [0-20]: Cast [0-20]: ArrayType [0-17]: ArrayBaseTypeKind IntType[Expr [10-12]: Lit: Int(64)]: [6-13] Expr [15-16]: Lit: Int(4) Expr [18-19]: Lit: Int(0)"#]], @@ -916,11 +831,10 @@ fn cast_to_int_array() { #[test] fn cast_to_uint_array() { - check( - expr, + check_expr( "array[uint[64], 4](0)", &expect![[r#" - Expr [0-18]: Cast [0-21]: + Expr [0-21]: Cast [0-21]: ArrayType [0-18]: ArrayBaseTypeKind UIntType[Expr [11-13]: Lit: Int(64)]: [6-14] Expr [16-17]: Lit: Int(4) Expr [19-20]: Lit: Int(0)"#]], @@ -929,11 +843,10 @@ fn cast_to_uint_array() { #[test] fn cast_to_float_array() { - check( - expr, + check_expr( "array[float[64], 4](0)", &expect![[r#" - Expr [0-19]: Cast [0-22]: + Expr [0-22]: Cast [0-22]: ArrayType [0-19]: ArrayBaseTypeKind FloatType[Expr [12-14]: Lit: Int(64)]: [6-15] Expr [17-18]: Lit: Int(4) Expr [20-21]: Lit: Int(0)"#]], @@ -942,11 +855,10 @@ fn cast_to_float_array() { #[test] fn cast_to_angle_array() { - check( - expr, + check_expr( "array[angle[64], 4](0)", &expect![[r#" - Expr [0-19]: Cast [0-22]: + Expr [0-22]: Cast [0-22]: ArrayType [0-19]: ArrayBaseTypeKind AngleType [6-15]: Expr [12-14]: Lit: Int(64) Expr [17-18]: Lit: Int(4) Expr [20-21]: Lit: Int(0)"#]], @@ -955,11 +867,10 @@ fn cast_to_angle_array() { #[test] fn cast_to_bool_array() { - check( - expr, + check_expr( "array[bool, 4](0)", &expect![[r#" - Expr [0-14]: Cast [0-17]: + Expr [0-17]: Cast [0-17]: ArrayType [0-14]: ArrayBaseTypeKind BoolType Expr [12-13]: Lit: Int(4) Expr [15-16]: Lit: Int(0)"#]], @@ -968,11 +879,10 @@ fn cast_to_bool_array() { #[test] fn cast_to_duration_array() { - check( - expr, + check_expr( "array[duration, 4](0)", &expect![[r#" - Expr [0-18]: Cast [0-21]: + Expr [0-21]: Cast [0-21]: ArrayType [0-18]: ArrayBaseTypeKind DurationType Expr [16-17]: Lit: Int(4) Expr [19-20]: Lit: Int(0)"#]], @@ -981,11 +891,10 @@ fn cast_to_duration_array() { #[test] fn cast_to_complex_array() { - check( - expr, + check_expr( "array[complex[float[32]], 4](0)", &expect![[r#" - Expr [0-28]: Cast [0-31]: + Expr [0-31]: Cast [0-31]: ArrayType [0-28]: ArrayBaseTypeKind ComplexType[float[FloatType[Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24] Expr [26-27]: Lit: Int(4) Expr [29-30]: Lit: Int(0)"#]], @@ -994,8 +903,7 @@ fn cast_to_complex_array() { #[test] fn index_expr() { - check( - expr, + check_expr( "foo[1]", &expect![[r#" Expr [0-6]: IndexExpr [3-6]: Expr [0-3]: Ident [0-3] "foo", IndexElement: @@ -1005,8 +913,7 @@ fn index_expr() { #[test] fn index_set() { - check( - expr, + check_expr( "foo[{1, 4, 5}]", &expect![[r#" Expr [0-14]: IndexExpr [3-14]: Expr [0-3]: Ident [0-3] "foo", IndexElement DiscreteSet [4-13]: @@ -1018,8 +925,7 @@ fn index_set() { #[test] fn index_multiple_ranges() { - check( - expr, + check_expr( "foo[1:5, 3:7, 4:8]", &expect![[r#" Expr [0-18]: IndexExpr [3-18]: Expr [0-3]: Ident [0-3] "foo", IndexElement: @@ -1040,8 +946,7 @@ fn index_multiple_ranges() { #[test] fn index_range() { - check( - expr, + check_expr( "foo[1:5:2]", &expect![[r#" Expr [0-10]: IndexExpr [3-10]: Expr [0-3]: Ident [0-3] "foo", IndexElement: @@ -1054,8 +959,7 @@ fn index_range() { #[test] fn index_full_range() { - check( - expr, + check_expr( "foo[:]", &expect![[r#" Expr [0-6]: IndexExpr [3-6]: Expr [0-3]: Ident [0-3] "foo", IndexElement: @@ -1068,8 +972,7 @@ fn index_full_range() { #[test] fn index_range_start() { - check( - expr, + check_expr( "foo[1:]", &expect![[r#" Expr [0-7]: IndexExpr [3-7]: Expr [0-3]: Ident [0-3] "foo", IndexElement: @@ -1082,8 +985,7 @@ fn index_range_start() { #[test] fn index_range_end() { - check( - expr, + check_expr( "foo[:5]", &expect![[r#" Expr [0-7]: IndexExpr [3-7]: Expr [0-3]: Ident [0-3] "foo", IndexElement: @@ -1096,8 +998,7 @@ fn index_range_end() { #[test] fn index_range_step() { - check( - expr, + check_expr( "foo[:2:]", &expect![[r#" Expr [0-8]: IndexExpr [3-8]: Expr [0-3]: Ident [0-3] "foo", IndexElement: @@ -1135,8 +1036,7 @@ fn lit_array() { #[test] fn assignment_and_unop() { - check( - expr, + check_expr( "c = a && !b", &expect![[r#" Expr [0-11]: Assign: @@ -1150,8 +1050,7 @@ fn assignment_and_unop() { #[test] fn assignment_unop_and() { - check( - expr, + check_expr( "d = !a && b", &expect![[r#" Expr [0-11]: Assign: diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 13508e287c..a18ac394ff 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -66,17 +66,17 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { let arg = expr::expr(s)?; recovering_token(s, TokenKind::Close(Delim::Paren)); let cast_expr = Expr { - span: s.span(lo), + span: s.span(ty.span().lo), kind: Box::new(ExprKind::Cast(Cast { - span: s.span(lo), + span: s.span(ty.span().lo), r#type: ty, arg, })), }; - Box::new(StmtKind::ExprStmt(ExprStmt { - span: s.span(lo), - expr: cast_expr, - })) + Box::new(StmtKind::ExprStmt(parse_expression_stmt( + s, + Some(cast_expr), + )?)) } } else if let Some(decl) = opt(s, parse_constant_classical_decl)? { Box::new(decl) @@ -1071,7 +1071,6 @@ fn parse_end_stmt(s: &mut ParserContext) -> Result { /// Grammar: `expression SEMICOLON`. fn parse_expression_stmt(s: &mut ParserContext, lhs: Option) -> Result { - let lo = s.peek().span.lo; let expr = if let Some(lhs) = lhs { expr::expr_with_lhs(s, lhs)? } else { @@ -1079,7 +1078,7 @@ fn parse_expression_stmt(s: &mut ParserContext, lhs: Option) -> Result miette::Result<(), Vec> { let res = parse_all("source0.qasm", all_sources)?; assert!(res.source.includes().len() == 1); - println!("{}", res.source.program); Ok(()) } From 7ba15c650d676cae15e4b2bab48704695d026d68 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Fri, 28 Feb 2025 15:48:37 -0800 Subject: [PATCH 47/98] fix formatting --- compiler/qsc_qasm3/src/parser/expr/tests.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index e01d468c27..4d33c5c52e 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -319,10 +319,7 @@ fn lit_float_trailing_exp_dot() { #[test] fn lit_int_hexadecimal_dot() { - check_expr( - "0x123.45", - &expect!["Expr [0-5]: Lit: Int(291)"], - ); + check_expr("0x123.45", &expect!["Expr [0-5]: Lit: Int(291)"]); } #[test] @@ -1107,3 +1104,18 @@ fn measure_indexed_identifier() { IndexSetItem Expr [18-19]: Lit: Int(2)]"#]], ); } + +#[test] +fn addition_of_casts() { + check_expr( + "bit(0) + bit(1)", + &expect![[r#" + Expr [0-15]: BinOp (Add): + Expr [0-6]: Cast [0-6]: + ClassicalType [0-3]: BitType + Expr [4-5]: Lit: Int(0) + Expr [9-15]: Cast [9-15]: + ClassicalType [9-12]: BitType + Expr [13-14]: Lit: Int(1)"#]], + ); +} From be8f57ce7f64b8533704134294ad340a8fe7aed8 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Fri, 28 Feb 2025 16:29:21 -0800 Subject: [PATCH 48/98] fix lexer bug --- compiler/qsc_qasm3/src/lex/cooked.rs | 15 ++++------ compiler/qsc_qasm3/src/lex/cooked/tests.rs | 31 ++++++++++++++++++++ compiler/qsc_qasm3/src/lex/raw/tests.rs | 32 +++++++++++++++++++++ compiler/qsc_qasm3/src/parser/expr/tests.rs | 19 +----------- 4 files changed, 69 insertions(+), 28 deletions(-) diff --git a/compiler/qsc_qasm3/src/lex/cooked.rs b/compiler/qsc_qasm3/src/lex/cooked.rs index c891ef4519..d3c5c67857 100644 --- a/compiler/qsc_qasm3/src/lex/cooked.rs +++ b/compiler/qsc_qasm3/src/lex/cooked.rs @@ -554,19 +554,14 @@ impl<'a> Lexer<'a> { 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. - match (self.first(), self.second()) { - (Some(raw::TokenKind::LiteralFragment(fragment)), _) - | ( - Some(raw::TokenKind::Whitespace), - Some(raw::TokenKind::LiteralFragment(fragment)), - ) => { + self.next_if_eq(raw::TokenKind::Whitespace); + + match self.first() { + Some(raw::TokenKind::LiteralFragment(fragment)) => { use self::Literal::{Imaginary, Timing}; use TokenKind::Literal; - // if first() was a whitespace, we need to consume an extra token - if self.first() == Some(raw::TokenKind::Whitespace) { - self.next(); - } + // Consume the fragment. self.next(); Ok(Some(match fragment { diff --git a/compiler/qsc_qasm3/src/lex/cooked/tests.rs b/compiler/qsc_qasm3/src/lex/cooked/tests.rs index 89d2bf0b5a..c721b5e97b 100644 --- a/compiler/qsc_qasm3/src/lex/cooked/tests.rs +++ b/compiler/qsc_qasm3/src/lex/cooked/tests.rs @@ -363,6 +363,37 @@ fn imag_with_whitespace() { ); } +#[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( diff --git a/compiler/qsc_qasm3/src/lex/raw/tests.rs b/compiler/qsc_qasm3/src/lex/raw/tests.rs index af9f480aff..88bbc351cc 100644 --- a/compiler/qsc_qasm3/src/lex/raw/tests.rs +++ b/compiler/qsc_qasm3/src/lex/raw/tests.rs @@ -1274,3 +1274,35 @@ fn hardware_qubit_with_underscore_in_the_middle() { "#]], ); } + +#[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/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index 4d33c5c52e..4cb856efdf 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -4,27 +4,10 @@ use super::expr; use crate::{ ast::StmtKind, - parser::{scan::ParserContext, stmt, tests::check, Parser}, + parser::{scan::ParserContext, stmt, tests::check}, }; use expect_test::{expect, Expect}; -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:#?}")), - } -} - /// 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` From 6dcccc7512026de65a1b5181636ad5f23ffe844f Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Fri, 28 Feb 2025 16:31:24 -0800 Subject: [PATCH 49/98] format code --- compiler/qsc_qasm3/src/lex/raw/tests.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/qsc_qasm3/src/lex/raw/tests.rs b/compiler/qsc_qasm3/src/lex/raw/tests.rs index 88bbc351cc..aee13c0ab1 100644 --- a/compiler/qsc_qasm3/src/lex/raw/tests.rs +++ b/compiler/qsc_qasm3/src/lex/raw/tests.rs @@ -1277,7 +1277,9 @@ fn hardware_qubit_with_underscore_in_the_middle() { #[test] fn decimal_space_imag_semicolon() { - check("10 im;", &expect![[r#" + check( + "10 im;", + &expect![[r#" [ Token { kind: Number( @@ -1304,5 +1306,6 @@ fn decimal_space_imag_semicolon() { offset: 6, }, ] - "#]]); + "#]], + ); } From 4f6d5c09e2f4a6942cd0f5c7f77e51bca0f18d78 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Fri, 28 Feb 2025 16:54:30 -0800 Subject: [PATCH 50/98] fix lexer bug and remove `second` from cooked lexer --- compiler/qsc_qasm3/src/lex/cooked.rs | 49 ++++++++++----------- compiler/qsc_qasm3/src/parser/expr/tests.rs | 11 +++++ 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/compiler/qsc_qasm3/src/lex/cooked.rs b/compiler/qsc_qasm3/src/lex/cooked.rs index d3c5c67857..78f1008158 100644 --- a/compiler/qsc_qasm3/src/lex/cooked.rs +++ b/compiler/qsc_qasm3/src/lex/cooked.rs @@ -461,14 +461,6 @@ impl<'a> Lexer<'a> { self.tokens.peek().map(|i| i.kind) } - /// Returns the second token ahead of the cursor without consuming it. This is slower - /// than [`first`] and should be avoided when possible. - fn second(&self) -> Option { - let mut tokens = self.tokens.clone(); - tokens.next(); - tokens.next().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; @@ -554,26 +546,31 @@ impl<'a> Lexer<'a> { 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); - match self.first() { - Some(raw::TokenKind::LiteralFragment(fragment)) => { - 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)), - })) - } - _ => Ok(Some(number.into())), + 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) => { diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index 4cb856efdf..455a3bcdff 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -535,6 +535,17 @@ 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]: BinOp (Add): + Expr [0-1]: Lit: Int(1) + Expr [4-5]: Lit: Int(2)"#]], + ); +} + #[test] fn pratt_parsing_mul_add() { check_expr( From fa78d2470d0a1b2c97b21e83b7231eda8f005872 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Fri, 28 Feb 2025 17:00:12 -0800 Subject: [PATCH 51/98] update test --- compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs index 7799100efb..f5a5082ad7 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs @@ -117,8 +117,8 @@ fn cast_expr() { parse, "bit(0);", &expect![[r#" - Stmt [0-6] - StmtKind: ExprStmt [0-6]: Expr [0-6]: Cast [0-6]: + Stmt [0-7] + StmtKind: ExprStmt [0-7]: Expr [0-6]: Cast [0-6]: ClassicalType [0-3]: BitType Expr [4-5]: Lit: Int(0)"#]], ); @@ -130,8 +130,8 @@ fn cast_expr_with_designator() { parse, "bit[45](0);", &expect![[r#" - Stmt [0-10] - StmtKind: ExprStmt [0-10]: Expr [0-10]: Cast [0-10]: + Stmt [0-11] + StmtKind: ExprStmt [0-11]: Expr [0-10]: Cast [0-10]: ClassicalType [0-7]: BitType [0-7]: Expr [4-6]: Lit: Int(45) Expr [8-9]: Lit: Int(0)"#]], ); From 34fa49bc93c63fce80a95f13f73860fe9193de81 Mon Sep 17 00:00:00 2001 From: Oscar Puente <156957451+orpuente-MS@users.noreply.github.com> Date: Mon, 3 Mar 2025 09:29:30 -0800 Subject: [PATCH 52/98] remove extra scope --- compiler/qsc_qasm3/src/parser/expr.rs | 44 +++++++++++++-------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 218569b7ba..586951d57a 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -409,30 +409,28 @@ fn lit_bigint(lexeme: &str, radix: u32) -> Option { } 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, - }; + 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 }, - })) - } + Ok(Some(Lit { + span: token.span, + kind: LiteralKind::Duration { value, unit }, + })) } pub(crate) fn paren_expr(s: &mut ParserContext, lo: u32) -> Result { From 23327c146701db9da5bc59c54c18079f20a87bc4 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Wed, 5 Mar 2025 11:54:53 -0800 Subject: [PATCH 53/98] Tidy up Display functions in QASM3 parser (#2209) This PR doesn't change any behavior. It just touches the Display functions in the QASM3 parser to make unit tests easier to read. --- compiler/qsc_qasm3/src/ast.rs | 981 ++++++------------ compiler/qsc_qasm3/src/ast/display_utils.rs | 206 ++++ compiler/qsc_qasm3/src/parser/expr.rs | 20 +- compiler/qsc_qasm3/src/parser/expr/tests.rs | 449 +++++--- compiler/qsc_qasm3/src/parser/prim/tests.rs | 42 +- compiler/qsc_qasm3/src/parser/stmt.rs | 80 +- .../qsc_qasm3/src/parser/stmt/tests/alias.rs | 52 +- .../src/parser/stmt/tests/annotation.rs | 20 +- .../src/parser/stmt/tests/barrier.rs | 27 +- .../src/parser/stmt/tests/box_stmt.rs | 114 +- .../qsc_qasm3/src/parser/stmt/tests/cal.rs | 5 +- .../src/parser/stmt/tests/cal_grammar.rs | 6 +- .../src/parser/stmt/tests/classical_decl.rs | 528 +++++++--- .../qsc_qasm3/src/parser/stmt/tests/def.rs | 177 +++- .../qsc_qasm3/src/parser/stmt/tests/defcal.rs | 5 +- .../qsc_qasm3/src/parser/stmt/tests/delay.rs | 25 +- .../src/parser/stmt/tests/expr_stmt.rs | 121 ++- .../src/parser/stmt/tests/extern_decl.rs | 156 ++- .../src/parser/stmt/tests/for_loops.rs | 208 ++-- .../src/parser/stmt/tests/gate_call.rs | 186 +++- .../src/parser/stmt/tests/gate_def.rs | 146 ++- .../qsc_qasm3/src/parser/stmt/tests/gphase.rs | 134 ++- .../src/parser/stmt/tests/if_stmt.rs | 139 ++- .../src/parser/stmt/tests/io_decl.rs | 266 +++-- .../src/parser/stmt/tests/measure.rs | 62 +- .../src/parser/stmt/tests/old_style_decl.rs | 32 +- .../qsc_qasm3/src/parser/stmt/tests/pragma.rs | 49 +- .../src/parser/stmt/tests/quantum_decl.rs | 44 +- .../qsc_qasm3/src/parser/stmt/tests/reset.rs | 27 +- .../src/parser/stmt/tests/switch_stmt.rs | 91 +- .../src/parser/stmt/tests/while_loops.rs | 112 +- 31 files changed, 2873 insertions(+), 1637 deletions(-) create mode 100644 compiler/qsc_qasm3/src/ast/display_utils.rs diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index 295a59f16f..24ca614a38 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -4,28 +4,20 @@ // while we work through the conversion, allow dead code to avoid warnings #![allow(dead_code)] -use indenter::{indented, Indented}; +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, Write}, + fmt::{self, Display, Formatter}, hash::Hash, rc::Rc, }; -fn set_indentation<'a, 'b>( - indent: Indented<'a, Formatter<'b>>, - level: usize, -) -> Indented<'a, Formatter<'b>> { - match level { - 0 => indent.with_str(""), - 1 => indent.with_str(" "), - 2 => indent.with_str(" "), - 3 => indent.with_str(" "), - _ => unimplemented!("indentation level not supported"), - } -} - // 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 @@ -47,16 +39,9 @@ pub struct Program { impl Display for Program { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "Program {}:", self.span)?; - indent = set_indentation(indent, 1); - if let Some(version) = &self.version { - write!(indent, "\nVersion {version}")?; - } - for stmt in &self.statements { - write!(indent, "\n{stmt}")?; - } - Ok(()) + writeln_header(f, "Program", self.span)?; + writeln_opt_field(f, "version", self.version.as_ref())?; + write_list_field(f, "statements", &self.statements) } } @@ -69,14 +54,9 @@ pub struct Stmt { impl Display for Stmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "Stmt {}", self.span)?; - indent = set_indentation(indent, 1); - for annotation in &self.annotations { - write!(indent, "\n{annotation}")?; - } - write!(indent, "\n{}", self.kind)?; - Ok(()) + writeln_header(f, "Stmt", self.span)?; + writeln_list_field(f, "annotations", &self.annotations)?; + write_field(f, "kind", &self.kind) } } @@ -88,15 +68,11 @@ pub struct Annotation { } impl Display for Annotation { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(value) = &self.value { - write!( - f, - "Annotation {}: ({}, {})", - self.span, self.identifier, value - ) - } else { - write!(f, "Annotation {}: ({})", self.span, self.identifier) - } + 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()) } } @@ -118,18 +94,13 @@ impl Default for PathKind { impl Display for PathKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - PathKind::Ok(path) => write!(f, "{path}")?, + PathKind::Ok(path) => write!(f, "{path}"), PathKind::Err(Some(incomplete_path)) => { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "Err IncompletePath {}:", incomplete_path.span)?; - indent = set_indentation(indent, 1); - for part in &incomplete_path.segments { - write!(indent, "\n{part}")?; - } + write!(f, "Err IncompletePath {}:", incomplete_path.span)?; + write_list_field(f, "segments", &incomplete_path.segments) } - PathKind::Err(None) => write!(f, "Err",)?, + PathKind::Err(None) => write!(f, "Err",), } - Ok(()) } } @@ -162,20 +133,9 @@ pub struct Path { impl Display for Path { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if self.segments.is_none() { - write!(f, "Path {} ({})", self.span, self.name)?; - } else { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "Path {}:", self.span)?; - indent = set_indentation(indent, 1); - if let Some(parts) = &self.segments { - for part in parts { - write!(indent, "\n{part}")?; - } - } - write!(indent, "\n{}", self.name)?; - } - Ok(()) + writeln_header(f, "Path", self.span)?; + writeln_field(f, "name", &self.name)?; + write_opt_list_field(f, "segments", self.segments.as_ref()) } } @@ -208,9 +168,11 @@ pub struct MeasureExpr { impl Display for MeasureExpr { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "MeasureExpr {}: {}", self.span, self.operand) + writeln_header(f, "MeasureExpr", self.span)?; + write_field(f, "operand", &self.operand) } } + /// A binary operator. #[derive(Clone, Copy, Debug)] pub enum BinOp { @@ -312,8 +274,8 @@ pub enum GateOperand { impl Display for GateOperand { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - GateOperand::IndexedIdent(ident) => write!(f, "GateOperand {ident}"), - GateOperand::HardwareQubit(qubit) => write!(f, "GateOperand {qubit}"), + GateOperand::IndexedIdent(ident) => write!(f, "{ident}"), + GateOperand::HardwareQubit(qubit) => write!(f, "{qubit}"), GateOperand::Err => write!(f, "Error"), } } @@ -339,51 +301,16 @@ impl Display for HardwareQubit { #[derive(Clone, Debug)] pub struct AliasDeclStmt { + pub span: Span, pub ident: Identifier, pub exprs: List, - pub span: Span, } impl Display for AliasDeclStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "Alias {}: {}", self.span, self.ident)?; - indent = set_indentation(indent, 1); - for expr in &*self.exprs { - write!(indent, "\n{expr}")?; - } - Ok(()) - } -} - -#[derive(Clone, Debug)] -pub struct Assign { - pub ident: Box, - pub expr: Box, - pub span: Span, -} - -impl Display for Assign { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "Assign {}: {}, {}", self.span, self.ident, self.expr) - } -} - -#[derive(Clone, Debug)] -pub struct AssignOp { - pub op: BinOp, - pub ident: Box, - pub expr: Box, - pub span: Span, -} - -impl Display for AssignOp { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "AssignOp {}: {}, {}, {}", - self.span, self.op, self.ident, self.expr - ) + writeln_header(f, "AliasDeclStmt", self.span)?; + writeln_field(f, "ident", &self.ident)?; + write_list_field(f, "exprs", &self.exprs) } } @@ -391,8 +318,6 @@ impl Display for AssignOp { #[derive(Clone, Debug, Default)] pub enum StmtKind { Alias(AliasDeclStmt), - Assign(Assign), - AssignOp(AssignOp), Barrier(BarrierStmt), Box(BoxStmt), Break(BreakStmt), @@ -431,11 +356,8 @@ pub enum StmtKind { impl Display for StmtKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "StmtKind: ")?; match self { StmtKind::Alias(alias) => write!(f, "{alias}"), - StmtKind::Assign(assign) => write!(f, "{assign}"), - StmtKind::AssignOp(assign_op) => write!(f, "{assign_op}"), StmtKind::Barrier(barrier) => write!(f, "{barrier}"), StmtKind::Box(box_stmt) => write!(f, "{box_stmt}"), StmtKind::Break(break_stmt) => write!(f, "{break_stmt}"), @@ -479,7 +401,8 @@ pub struct CalibrationGrammarStmt { impl Display for CalibrationGrammarStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "CalibrationGrammarStmt {}: {}", self.span, self.name) + writeln_header(f, "CalibrationGrammarStmt", self.span)?; + write_field(f, "name", &self.name) } } @@ -504,18 +427,10 @@ pub struct IfStmt { impl Display for IfStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "IfStmt {}: {}", self.span, self.condition)?; - for stmt in &self.if_block { - write!(indent, "\n{stmt}")?; - } - if let Some(else_block) = &self.else_block { - write!(indent, "\nElse:")?; - for stmt in else_block { - write!(indent, "\n{stmt}")?; - } - } - Ok(()) + writeln_header(f, "IfStmt", self.span)?; + writeln_field(f, "condition", &self.condition)?; + writeln_list_field(f, "if_block", &self.if_block)?; + write_opt_list_field(f, "else_block", self.else_block.as_ref()) } } @@ -527,12 +442,8 @@ pub struct BarrierStmt { impl Display for BarrierStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "Barrier {}: [", self.span)?; - for qubit in &self.qubits { - write!(indent, "\n{qubit}")?; - } - write!(indent, "]") + writeln_header(f, "BarrierStmt", self.span)?; + write_list_field(f, "operands", &self.qubits) } } @@ -544,7 +455,8 @@ pub struct ResetStmt { impl Display for ResetStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "ResetStmt {}: {}", self.span, self.operand) + writeln_header(f, "ResetStmt", self.span)?; + write_field(f, "operand", &self.operand) } } @@ -559,17 +471,8 @@ pub struct Block { impl Display for Block { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if self.stmts.is_empty() { - write!(f, "Block {}: ", self.span)?; - } else { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "Block {}:", self.span)?; - indent = set_indentation(indent, 1); - for s in &self.stmts { - write!(indent, "\n{s}")?; - } - } - Ok(()) + write_header(f, "Block", self.span)?; + write_indented_list(f, &self.stmts) } } @@ -624,12 +527,9 @@ pub struct IndexedIdent { impl Display for IndexedIdent { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "IndexedIdent {}: {}[", self.span, self.name)?; - - for index in &self.indices { - write!(f, "\n{index}")?; - } - write!(f, "]") + writeln_header(f, "IndexedIdent", self.span)?; + writeln_field(f, "name", &self.name)?; + write_list_field(f, "indices", &self.indices) } } @@ -641,7 +541,8 @@ pub struct ExprStmt { impl Display for ExprStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "ExprStmt {}: {}", self.span, self.expr) + writeln_header(f, "ExprStmt", self.span)?; + write_field(f, "expr", &self.expr) } } @@ -666,18 +567,26 @@ impl Display for Expr { #[derive(Clone, Debug)] pub struct DiscreteSet { pub span: Span, - pub values: Box<[Expr]>, + pub values: List, } impl Display for DiscreteSet { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "DiscreteSet {}:", self.span)?; - indent = set_indentation(indent, 1); - for value in &self.values { - write!(indent, "\n{value}")?; - } - Ok(()) + 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) } } @@ -689,6 +598,15 @@ pub struct RangeDefinition { pub step: Option, } +impl Display for RangeDefinition { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "RangeDefinition", self.span)?; + writeln_opt_field(f, "start", self.start.as_ref())?; + writeln_opt_field(f, "step", self.step.as_ref())?; + write_opt_field(f, "end", self.end.as_ref()) + } +} + #[derive(Clone, Debug)] pub struct QuantumGateModifier { pub span: Span, @@ -793,7 +711,7 @@ pub struct ScalarType { impl Display for ScalarType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "ClassicalType {}: {}", self.span, self.kind) + write!(f, "ScalarType {}: {}", self.span, self.kind) } } @@ -856,97 +774,79 @@ impl Display for ArrayBaseTypeKind { #[derive(Clone, Debug)] pub struct IntType { - pub size: Option, pub span: Span, + pub size: Option, } impl Display for IntType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(size) = &self.size { - write!(f, "IntType[{}]: {}", size, self.span) - } else { - write!(f, "IntType {}", self.span) - } + writeln_header(f, "IntType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) } } #[derive(Clone, Debug)] pub struct UIntType { - pub size: Option, pub span: Span, + pub size: Option, } impl Display for UIntType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(size) = &self.size { - write!(f, "UIntType[{}]: {}", size, self.span) - } else { - write!(f, "UIntType {}", self.span) - } + writeln_header(f, "UIntType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) } } #[derive(Clone, Debug)] pub struct FloatType { - pub size: Option, pub span: Span, + pub size: Option, } impl Display for FloatType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(size) = &self.size { - write!(f, "FloatType[{}]: {}", size, self.span) - } else { - write!(f, "FloatType {}", self.span) - } + writeln_header(f, "FloatType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) } } #[derive(Clone, Debug)] pub struct ComplexType { - pub base_size: Option, pub span: Span, + pub base_size: Option, } impl Display for ComplexType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(size) = &self.base_size { - write!(f, "ComplexType[float[{}]]: {}", size, self.span) - } else { - write!(f, "ComplexType {}", self.span) - } + writeln_header(f, "ComplexType", self.span)?; + write_opt_field(f, "base_size", self.base_size.as_ref()) } } #[derive(Clone, Debug)] pub struct AngleType { - pub size: Option, pub span: Span, + pub size: Option, } impl Display for AngleType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(size) = &self.size { - write!(f, "AngleType {}: {}", self.span, size) - } else { - write!(f, "AngleType {}", self.span) - } + writeln_header(f, "AngleType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) } } #[derive(Clone, Debug)] pub struct BitType { - pub size: Option, pub span: Span, + pub size: Option, } impl Display for BitType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(size) = &self.size { - write!(f, "BitType {}: {}", self.span, size) - } else { - write!(f, "BitType") - } + writeln_header(f, "BitType", self.span)?; + write_opt_field(f, "size", self.size.as_ref()) } } @@ -986,12 +886,9 @@ pub struct ArrayType { impl Display for ArrayType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "ArrayType {}: {}", self.span, self.base_type)?; - for dimension in &self.dimensions { - write!(indent, "\n{dimension}")?; - } - Ok(()) + writeln_header(f, "ArrayType", self.span)?; + writeln_field(f, "base_type", &self.base_type)?; + write_list_field(f, "dimensions", &self.dimensions) } } @@ -1005,16 +902,10 @@ pub struct ArrayReferenceType { impl Display for ArrayReferenceType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!( - indent, - "ArrayReferenceType {}: {}", - self.span, self.base_type - )?; - for dimension in &self.dimensions { - write!(indent, "\n{dimension}")?; - } - Ok(()) + 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) } } @@ -1041,7 +932,8 @@ pub struct QuantumArgument { impl Display for QuantumArgument { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "QuantumArgument {}: {:?}", self.span, self.expr) + writeln_header(f, "QuantumArgument", self.span)?; + write_opt_field(f, "expr", self.expr.as_ref()) } } @@ -1054,28 +946,11 @@ pub struct Pragma { impl Display for Pragma { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(value) = &self.value { - write!(f, "Pragma {}: ({}, {})", self.span, self.identifier, value) - } else { - write!(f, "Pragma {}: ({})", self.span, self.identifier) - } - } -} - -#[derive(Clone, Debug)] -pub struct CompoundStmt { - pub span: Span, - pub statements: List, -} - -impl Display for CompoundStmt { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "CompoundStmt {}:", self.span)?; - for stmt in &self.statements { - write!(indent, "\n{stmt}")?; - } - Ok(()) + 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()) } } @@ -1087,28 +962,23 @@ pub struct IncludeStmt { impl Display for IncludeStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "IncludeStmt {}: {}", self.span, self.filename) + writeln_header(f, "IncludeStmt", self.span)?; + write_field(f, "filename", &self.filename) } } #[derive(Clone, Debug)] pub struct QubitDeclaration { pub span: Span, - pub qubit: Box, + pub qubit: Ident, pub size: Option, } impl Display for QubitDeclaration { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(size) = &self.size { - write!( - f, - "QubitDeclaration {}: {}, {}", - self.span, self.qubit, size - ) - } else { - write!(f, "QubitDeclaration {}: {}", self.span, self.qubit) - } + writeln_header(f, "QubitDeclaration", self.span)?; + writeln_field(f, "ident", &self.qubit)?; + write_opt_field(f, "size", self.size.as_ref()) } } @@ -1123,35 +993,11 @@ pub struct QuantumGateDefinition { impl Display for QuantumGateDefinition { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "Gate {}: {}", self.span, self.ident)?; - write!(indent, "(")?; - if self.params.is_empty() { - write!(indent, "")?; - } else { - let param_str = self - .params - .iter() - .map(std::string::ToString::to_string) - .collect::>() - .join(", "); - write!(indent, "{param_str}")?; - } - write!(indent, ") ")?; - - let qubit_str = self - .qubits - .iter() - .map(std::string::ToString::to_string) - .collect::>() - .join(", "); - write!(indent, "{qubit_str}")?; - - writeln!(indent)?; - for stmt in &self.body.stmts { - write!(indent, "\n{stmt}")?; - } - Ok(()) + 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) } } @@ -1165,15 +1011,10 @@ pub struct ExternDecl { impl Display for ExternDecl { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "ExternDecl {}: {}", self.span, self.ident)?; - for arg in &self.params { - write!(indent, "\n{arg}")?; - } - if let Some(return_type) = &self.return_type { - write!(indent, "\n{return_type}")?; - } - Ok(()) + 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()) } } @@ -1189,18 +1030,12 @@ pub struct GateCall { impl Display for GateCall { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "GateCall {}: {}", self.span, self.name)?; - for arg in &self.args { - write!(indent, "\n{arg}")?; - } - for qubit in &self.qubits { - write!(indent, "\n{qubit}")?; - } - if let Some(duration) = &self.duration { - write!(indent, "\n{duration}")?; - } - Ok(()) + 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) } } @@ -1215,18 +1050,11 @@ pub struct GPhase { impl Display for GPhase { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "GPhase {}:", self.span)?; - for arg in &self.args { - write!(indent, "\n{arg}")?; - } - for qubit in &self.qubits { - write!(indent, "\n{qubit}")?; - } - if let Some(duration) = &self.duration { - write!(indent, "\n{duration}")?; - } - Ok(()) + writeln_header(f, "GPhase", self.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) } } @@ -1239,12 +1067,9 @@ pub struct DelayStmt { impl Display for DelayStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "DelayInstruction {}: {}", self.span, self.duration)?; - for qubit in &self.qubits { - write!(indent, "\n{qubit}")?; - } - Ok(()) + writeln_header(f, "DelayStmt", self.span)?; + writeln_field(f, "duration", &self.duration)?; + write_list_field(f, "qubits", &self.qubits) } } @@ -1257,16 +1082,9 @@ pub struct BoxStmt { impl Display for BoxStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - if let Some(duration) = &self.duration { - write!(indent, "BoxStmt {}: {}", self.span, duration)?; - } else { - write!(indent, "BoxStmt {}: ", self.span)?; - } - for stmt in &self.body { - write!(indent, "\n{stmt}")?; - } - Ok(()) + writeln_header(f, "BoxStmt", self.span)?; + writeln_opt_field(f, "duration", self.duration.as_ref())?; + write_list_field(f, "body", &self.body) } } @@ -1279,41 +1097,26 @@ pub struct MeasureStmt { impl Display for MeasureStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(target) = &self.target { - write!( - f, - "MeasureStmt {}: {}, {}", - self.span, self.measurement, target - ) - } else { - write!(f, "MeasureStmt {}: {}", self.span, self.measurement) - } + writeln_header(f, "MeasureStmt", 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 r#type: TypeDef, - pub identifier: Box, + pub r#type: Box, + pub identifier: Ident, pub init_expr: Option>, } impl Display for ClassicalDeclarationStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(init_expr) = &self.init_expr { - write!( - f, - "ClassicalDeclarationStmt {}: {}, {}, {}", - self.span, self.r#type, self.identifier, init_expr - ) - } else { - write!( - f, - "ClassicalDeclarationStmt {}: {}, {}", - self.span, self.r#type, self.identifier - ) - } + writeln_header(f, "ClassicalDeclarationStmt", self.span)?; + writeln_field(f, "type", &self.r#type)?; + writeln_field(f, "ident", &self.identifier)?; + write_opt_field(f, "init_expr", self.init_expr.as_ref()) } } @@ -1326,8 +1129,8 @@ pub enum ValueExpression { impl Display for ValueExpression { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - ValueExpression::Expr(expr) => write!(f, "ValueExpression {expr}"), - ValueExpression::Measurement(measure) => write!(f, "ValueExpression {measure}"), + ValueExpression::Expr(expr) => write!(f, "{expr}"), + ValueExpression::Measurement(measure) => write!(f, "{measure}"), } } } @@ -1342,11 +1145,10 @@ pub struct IODeclaration { impl Display for IODeclaration { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "IODeclaration {}: {}, {}, {}", - self.span, self.io_identifier, self.r#type, self.ident - ) + writeln_header(f, "IODeclaration", self.span)?; + writeln_field(f, "io_keyword", &self.io_identifier)?; + writeln_field(f, "type", &self.r#type)?; + write_field(f, "ident", &self.ident) } } @@ -1360,11 +1162,10 @@ pub struct ConstantDeclStmt { impl Display for ConstantDeclStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "ConstantDeclaration {}: {}, {}, {}", - self.span, self.r#type, self.identifier, self.init_expr - ) + writeln_header(f, "ConstantDeclStmt", self.span)?; + writeln_field(f, "type", &self.r#type)?; + writeln_field(f, "ident", &self.identifier)?; + write_field(f, "init_expr", &self.init_expr) } } @@ -1376,11 +1177,8 @@ pub struct CalibrationGrammarDeclaration { impl Display for CalibrationGrammarDeclaration { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "CalibrationGrammarDeclaration {}: {}", - self.span, self.name - ) + writeln_header(f, "CalibrationGrammarDeclaration", self.span)?; + write_field(f, "name", &self.name) } } @@ -1396,89 +1194,113 @@ impl Display for CalibrationStmt { } #[derive(Clone, Debug)] -pub struct CalibrationDefinition { - span: Span, - name: Identifier, - args: List, - qubits: List, - return_type: Option, - body: String, +pub enum TypedParameter { + Scalar(ScalarTypedParameter), + Quantum(QuantumTypedParameter), + ArrayReference(ArrayTypedParameter), } -impl Display for CalibrationDefinition { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "CalibrationDefinition {}: {}", self.span, self.name)?; - for arg in &self.args { - write!(indent, "\n{arg}")?; - } - for qubit in &self.qubits { - write!(indent, "\n{qubit}")?; +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)), } - if let Some(return_type) = &self.return_type { - write!(indent, "\n{return_type}")?; + } +} + +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}"), } - write!(indent, "\n{}", self.body) + } +} + +impl Default for TypedParameter { + fn default() -> Self { + Self::Scalar(ScalarTypedParameter { + span: Span::default(), + ident: Ident::default(), + r#type: Box::default(), + }) } } #[derive(Clone, Debug)] -pub enum CalibrationArgument { - Classical(ClassicalArgument), - Expr(Expr), +pub struct ScalarTypedParameter { + pub span: Span, + pub r#type: Box, + pub ident: Ident, } -impl Display for CalibrationArgument { +impl Display for ScalarTypedParameter { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - CalibrationArgument::Classical(arg) => write!(f, "CalibrationArgument {arg}"), - CalibrationArgument::Expr(expr) => write!(f, "CalibrationArgument {expr}"), + writeln_header(f, "ScalarTypedParameter", self.span)?; + writeln_field(f, "type", &self.r#type)?; + write_field(f, "ident", &self.ident) + } +} + +impl WithSpan for ScalarTypedParameter { + fn with_span(self, span: Span) -> Self { + let Self { r#type, ident, .. } = self; + Self { + span, + r#type, + ident, } } } #[derive(Clone, Debug)] -pub enum TypedParameter { - Scalar(ScalarType, Box, Span), - Quantum(Option, Box, Span), - ArrayReference(ArrayReferenceType, Box, Span), +pub struct QuantumTypedParameter { + pub span: Span, + pub size: Option, + pub ident: Ident, } -impl WithSpan for TypedParameter { +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 { - match self { - TypedParameter::Scalar(scalar, ident, _) => TypedParameter::Scalar(scalar, ident, span), - TypedParameter::Quantum(expr, ident, _) => TypedParameter::Quantum(expr, ident, span), - TypedParameter::ArrayReference(array, ident, _) => { - TypedParameter::ArrayReference(array, ident, span) - } - } + let Self { size, ident, .. } = self; + Self { span, size, ident } } } -impl Display for TypedParameter { +#[derive(Clone, Debug)] +pub struct ArrayTypedParameter { + pub span: Span, + pub r#type: Box, + pub ident: Ident, +} + +impl Display for ArrayTypedParameter { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - TypedParameter::Scalar(scalar, ident, span) => { - write!(f, "{span} {ident}: {scalar}") - } - TypedParameter::Quantum(expr, ident, span) => { - if let Some(expr) = expr { - write!(f, "{span} {ident}: qubit[{expr}]") - } else { - write!(f, "{span} {ident}: qubit") - } - } - TypedParameter::ArrayReference(array, ident, span) => { - write!(f, "{span} {ident}: {array}") - } - } + writeln_header(f, "ArrayTypedParameter", self.span)?; + writeln_field(f, "type", &self.r#type)?; + write_field(f, "ident", &self.ident) } } -impl Default for TypedParameter { - fn default() -> Self { - TypedParameter::Scalar(ScalarType::default(), Box::default(), Span::default()) +impl WithSpan for ArrayTypedParameter { + fn with_span(self, span: Span) -> Self { + let Self { r#type, ident, .. } = self; + Self { + span, + r#type, + ident, + } } } @@ -1493,29 +1315,11 @@ pub struct DefStmt { impl Display for DefStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "DefStmt {}: {}", self.span, self.name)?; - write!(indent, "(")?; - if self.params.is_empty() { - write!(indent, "")?; - } else { - let param_str = self - .params - .iter() - .map(std::string::ToString::to_string) - .collect::>() - .join(", "); - write!(indent, "{param_str}")?; - } - write!(indent, ") ")?; - - for stmt in &self.body.stmts { - write!(indent, "\n{stmt}")?; - } - if let Some(return_type) = &self.return_type { - write!(indent, "\n{return_type}")?; - } - Ok(()) + 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) } } @@ -1528,8 +1332,8 @@ pub enum Operand { impl Display for Operand { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - Operand::Classical(arg) => write!(f, "Operand {arg}"), - Operand::Quantum(arg) => write!(f, "Operand {arg}"), + Operand::Classical(arg) => write!(f, "{arg}"), + Operand::Quantum(arg) => write!(f, "{arg}"), } } } @@ -1542,11 +1346,8 @@ pub struct ReturnStmt { impl Display for ReturnStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(expr) = &self.expr { - write!(f, "ReturnStmt {}: {}", self.span, expr) - } else { - write!(f, "ReturnStmt {}: ", self.span) - } + writeln_header(f, "ReturnStmt", self.span)?; + write_opt_field(f, "expr", self.expr.as_ref()) } } @@ -1559,12 +1360,9 @@ pub struct WhileLoop { impl Display for WhileLoop { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "WhileLoop {}: {}", self.span, self.while_condition)?; - for stmt in &self.block { - write!(indent, "\n{stmt}")?; - } - Ok(()) + writeln_header(f, "WhileLoop", self.span)?; + writeln_field(f, "condition", &self.while_condition)?; + write_list_field(f, "block", &self.block) } } @@ -1579,16 +1377,11 @@ pub struct ForStmt { impl Display for ForStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!( - indent, - "ForStmt {}: {}, {}, {}", - self.span, self.r#type, self.identifier, self.set_declaration - )?; - for stmt in &self.block { - write!(indent, "\n{stmt}")?; - } - Ok(()) + writeln_header(f, "ForStmt", self.span)?; + writeln_field(f, "variable_type", &self.r#type)?; + writeln_field(f, "variable_name", &self.identifier)?; + writeln_field(f, "iterable", &self.set_declaration)?; + write_list_field(f, "block", &self.block) } } @@ -1603,10 +1396,7 @@ impl Display for EnumerableSet { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { EnumerableSet::DiscreteSet(set) => write!(f, "{set}"), - EnumerableSet::RangeDefinition(range) => { - let indent = set_indentation(indented(f), 0); - display_range(indent, range) - } + EnumerableSet::RangeDefinition(range) => write!(f, "{range}"), EnumerableSet::Expr(expr) => write!(f, "{expr}"), } } @@ -1616,7 +1406,7 @@ impl Display for EnumerableSet { pub struct SwitchStmt { pub span: Span, pub target: Expr, - pub cases: List<(List, Block)>, + 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. @@ -1625,63 +1415,25 @@ pub struct SwitchStmt { impl Display for SwitchStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "SwitchStmt {}:", self.span)?; - let mut indent = set_indentation(indented(f), 1); - write!(indent, "\nTarget: {}", self.target)?; - if self.cases.is_empty() { - write!(indent, "\n")?; - } else { - write!(indent, "\nCases:")?; - for elt in &self.cases { - let (labels, block) = &**elt; - indent = display_switch_case(indent, labels, block)?; - } - } - if let Some(default) = &self.default { - write!(indent, "\nDefault Case:")?; - indent = set_indentation(indent, 2); - write!(indent, "\n{default}")?; - } else { - write!(indent, "\n")?; - } - Ok(()) - } -} - -fn display_switch_case<'a, 'b>( - mut indent: Indented<'a, Formatter<'b>>, - labels: &List, - block: &Block, -) -> Result>, core::fmt::Error> { - indent = set_indentation(indent, 2); - if labels.is_empty() { - write!(indent, "\n")?; - } else { - write!(indent, "\nLabels:")?; - indent = set_indentation(indent, 3); - for label in labels { - write!(indent, "\n{label}")?; - } + 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()) } - indent = set_indentation(indent, 2); - write!(indent, "\n{block}")?; - Ok(indent) } #[derive(Clone, Debug)] -pub struct ClassicalAssignment { +pub struct SwitchCase { pub span: Span, - pub lvalue: Identifier, - pub op: AssignmentOp, + pub labels: List, + pub block: Block, } -impl Display for ClassicalAssignment { +impl Display for SwitchCase { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "ClassicalAssignment {}: {}, {}", - self.span, self.lvalue, self.op - ) + writeln_header(f, "SwitchCase", self.span)?; + writeln_list_field(f, "labels", &self.labels)?; + write_field(f, "block", &self.block) } } @@ -1704,19 +1456,18 @@ pub enum ExprKind { impl Display for ExprKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let indent = set_indentation(indented(f), 0); match self { ExprKind::Err => write!(f, "Err"), ExprKind::Ident(id) => write!(f, "{id}"), ExprKind::UnaryOp(expr) => write!(f, "{expr}"), - ExprKind::BinaryOp(expr) => display_bin_op(indent, expr), + ExprKind::BinaryOp(expr) => write!(f, "{expr}"), ExprKind::Lit(lit) => write!(f, "{lit}"), ExprKind::FunctionCall(call) => write!(f, "{call}"), - ExprKind::Cast(cast) => display_cast(indent, cast), - ExprKind::IndexExpr(index) => write!(f, "{index}"), + ExprKind::Cast(expr) => write!(f, "{expr}"), + ExprKind::IndexExpr(expr) => write!(f, "{expr}"), ExprKind::Assign(expr) => write!(f, "{expr}"), ExprKind::AssignOp(expr) => write!(f, "{expr}"), - ExprKind::Paren(expr) => display_paren(indent, expr), + ExprKind::Paren(expr) => write!(f, "Paren {expr}"), } } } @@ -1729,8 +1480,9 @@ pub struct AssignExpr { impl Display for AssignExpr { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let indent = set_indentation(indented(f), 0); - display_assign(indent, &self.lhs, &self.rhs) + writeln!(f, "AssignExpr:")?; + writeln_field(f, "lhs", &self.lhs)?; + write_field(f, "rhs", &self.rhs) } } @@ -1743,8 +1495,10 @@ pub struct AssignOpExpr { impl Display for AssignOpExpr { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let indent = set_indentation(indented(f), 0); - display_assign_op(indent, self.op, &self.lhs, &self.rhs) + writeln!(f, "AssignOpExpr:")?; + writeln_field(f, "op", &self.op)?; + writeln_field(f, "lhs", &self.lhs)?; + write_field(f, "rhs", &self.rhs) } } @@ -1756,8 +1510,9 @@ pub struct UnaryOpExpr { impl Display for UnaryOpExpr { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let indent = set_indentation(indented(f), 0); - display_un_op(indent, self.op, &self.expr) + writeln!(f, "UnaryOpExpr:")?; + writeln_field(f, "op", &self.op)?; + write_field(f, "expr", &self.expr) } } @@ -1770,8 +1525,10 @@ pub struct BinaryOpExpr { impl Display for BinaryOpExpr { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let indent = set_indentation(indented(f), 0); - display_bin_op(indent, self) + writeln!(f, "BinaryOpExpr:")?; + writeln_field(f, "op", &self.op)?; + writeln_field(f, "lhs", &self.lhs)?; + write_field(f, "rhs", &self.rhs) } } @@ -1784,13 +1541,9 @@ pub struct FunctionCall { impl Display for FunctionCall { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "FunctionCall {}: {}", self.span, self.name)?; - indent = set_indentation(indent, 1); - for arg in &self.args { - write!(indent, "\n{arg}")?; - } - Ok(()) + writeln_header(f, "FunctionCall", self.span)?; + writeln_field(f, "name", &self.name)?; + write_list_field(f, "args", &self.args) } } @@ -1803,7 +1556,9 @@ pub struct Cast { impl Display for Cast { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "Cast {}: {}, {}", self.span, self.r#type, self.arg) + writeln_header(f, "Cast", self.span)?; + writeln_field(f, "type", &self.r#type)?; + write_field(f, "arg", &self.arg) } } @@ -1816,11 +1571,9 @@ pub struct IndexExpr { impl Display for IndexExpr { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "IndexExpr {}: {}, {}", - self.span, self.collection, self.index - ) + writeln_header(f, "IndexExpr", self.span)?; + writeln_field(f, "collection", &self.collection)?; + write_field(f, "index", &self.index) } } @@ -1839,7 +1592,7 @@ impl Display for Lit { #[derive(Clone, Debug)] pub enum LiteralKind { Array(List), - Bitstring(BigInt, usize), + Bitstring { value: BigInt, width: usize }, Bool(bool), Duration { value: f64, unit: TimeUnit }, Float(f64), @@ -1852,16 +1605,8 @@ pub enum LiteralKind { impl Display for LiteralKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - LiteralKind::Array(exprs) => { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "Array:")?; - indent = set_indentation(indent, 1); - for expr in exprs { - write!(indent, "\n{expr:?}")?; - } - Ok(()) - } - LiteralKind::Bitstring(value, width) => { + LiteralKind::Array(exprs) => write_list_field(f, "Array", exprs), + LiteralKind::Bitstring { value, width } => { write!(f, "Bitstring(\"{:0>width$}\")", value.to_str_radix(2)) } LiteralKind::Bool(b) => write!(f, "Bool({b:?})"), @@ -1896,22 +1641,14 @@ impl fmt::Display for Version { #[derive(Clone, Debug)] pub enum IndexElement { DiscreteSet(DiscreteSet), - IndexSet(List), + IndexSet(IndexSet), } impl Display for IndexElement { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - IndexElement::DiscreteSet(set) => write!(f, "IndexElement {set}"), - IndexElement::IndexSet(items) => { - let mut indent = set_indentation(indented(f), 0); - write!(indent, "IndexElement:")?; - indent = set_indentation(indent, 1); - for item in items { - write!(indent, "\n{item}")?; - } - Ok(()) - } + IndexElement::DiscreteSet(set) => write!(f, "{set}"), + IndexElement::IndexSet(set) => write!(f, "{set}"), } } } @@ -1933,34 +1670,14 @@ impl WithSpan for IndexSetItem { impl Display for IndexSetItem { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let indent = set_indentation(indented(f), 0); match self { - IndexSetItem::RangeDefinition(range) => display_range(indent, range), - IndexSetItem::Expr(expr) => write!(f, "IndexSetItem {expr}"), + IndexSetItem::RangeDefinition(range) => write!(f, "{range}"), + IndexSetItem::Expr(expr) => write!(f, "{expr}"), IndexSetItem::Err => write!(f, "Err"), } } } -#[derive(Clone, Debug)] -pub enum AssignmentOp { - BinaryOp(BinOp), - /// `OpenQASM3` has the `~=` assignment operator. - /// This enum variant is meant to capture that. - UnaryOp(UnaryOp), - Assign, -} - -impl Display for AssignmentOp { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - AssignmentOp::BinaryOp(op) => write!(f, "AssignmentOp ({op:?})"), - AssignmentOp::UnaryOp(op) => write!(f, "AssignmentOp ({op:?})"), - AssignmentOp::Assign => write!(f, "AssignmentOp (Assign)"), - } - } -} - #[derive(Clone, Debug)] pub enum IOKeyword { Input, @@ -2008,7 +1725,7 @@ pub struct BreakStmt { impl Display for BreakStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "Break {}", self.span) + write!(f, "BreakStmt {}", self.span) } } @@ -2019,7 +1736,7 @@ pub struct ContinueStmt { impl Display for ContinueStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "Continue {}", self.span) + write!(f, "ContinueStmt {}", self.span) } } @@ -2030,82 +1747,6 @@ pub struct EndStmt { impl Display for EndStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "End {}", self.span) - } -} - -fn display_assign(mut indent: Indented, lhs: &Expr, rhs: &Expr) -> fmt::Result { - write!(indent, "Assign:")?; - indent = set_indentation(indent, 1); - write!(indent, "\n{lhs}")?; - write!(indent, "\n{rhs}")?; - Ok(()) -} - -fn display_assign_op( - mut indent: Indented, - op: BinOp, - lhs: &Expr, - rhs: &Expr, -) -> fmt::Result { - write!(indent, "AssignOp ({op:?}):")?; - indent = set_indentation(indent, 1); - write!(indent, "\n{lhs}")?; - write!(indent, "\n{rhs}")?; - Ok(()) -} - -fn display_bin_op(mut indent: Indented, expr: &BinaryOpExpr) -> fmt::Result { - write!(indent, "BinOp ({:?}):", expr.op)?; - indent = set_indentation(indent, 1); - write!(indent, "\n{}", expr.lhs)?; - write!(indent, "\n{}", expr.rhs)?; - Ok(()) -} - -fn display_un_op(mut indent: Indented, op: UnaryOp, expr: &Expr) -> fmt::Result { - write!(indent, "UnOp ({op}):")?; - indent = set_indentation(indent, 1); - write!(indent, "\n{expr}")?; - Ok(()) -} - -fn display_paren(mut indent: Indented, expr: &Expr) -> fmt::Result { - write!(indent, "Paren:")?; - indent = set_indentation(indent, 1); - write!(indent, "\n{expr}")?; - Ok(()) -} -fn display_cast(mut indent: Indented, cast: &Cast) -> fmt::Result { - let Cast { span, r#type, arg } = cast; - write!(indent, "Cast {span}:")?; - indent = set_indentation(indent, 1); - write!(indent, "\n{type}\n{arg}")?; - Ok(()) -} - -fn display_while(mut indent: Indented, cond: &Expr, block: &Block) -> fmt::Result { - write!(indent, "While:")?; - indent = set_indentation(indent, 1); - write!(indent, "\n{cond}")?; - write!(indent, "\n{block}")?; - Ok(()) -} - -fn display_range(mut indent: Indented, range: &RangeDefinition) -> fmt::Result { - write!(indent, "Range: {}", range.span)?; - indent = set_indentation(indent, 1); - match &range.start { - Some(e) => write!(indent, "\nstart: {e}")?, - None => write!(indent, "\n")?, - } - match &range.step { - Some(e) => write!(indent, "\nstep: {e}")?, - None => write!(indent, "\n")?, - } - match &range.end { - Some(e) => write!(indent, "\nend: {e}")?, - None => write!(indent, "\n")?, + write!(f, "EndStmt {}", self.span) } - Ok(()) } diff --git a/compiler/qsc_qasm3/src/ast/display_utils.rs b/compiler/qsc_qasm3/src/ast/display_utils.rs new file mode 100644 index 0000000000..9088590bcf --- /dev/null +++ b/compiler/qsc_qasm3/src/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(super) 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(super) 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(super) 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(super) 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(super) 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(super) 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(super) 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 an field of a structure to the given buffer +/// or stream with an additional indententation level. +/// The field must be an iterable. +pub(super) 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 an 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(super) 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(super) 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(super) 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/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 586951d57a..3e19032516 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -18,8 +18,8 @@ use crate::{ ast::{ self, list_from_iter, AssignExpr, AssignOpExpr, BinOp, BinaryOpExpr, Cast, DiscreteSet, Expr, ExprKind, FunctionCall, GateOperand, HardwareQubit, Ident, IndexElement, IndexExpr, - IndexSetItem, IndexedIdent, List, Lit, LiteralKind, MeasureExpr, RangeDefinition, TimeUnit, - TypeDef, UnaryOp, ValueExpression, Version, + IndexSet, IndexSetItem, IndexedIdent, List, Lit, LiteralKind, MeasureExpr, RangeDefinition, + TimeUnit, TypeDef, UnaryOp, ValueExpression, Version, }, keyword::Keyword, lex::{ @@ -291,7 +291,7 @@ fn lit_token(lexeme: &str, token: Token) -> Result> { Ok(Some(Lit { span: token.span, - kind: LiteralKind::Bitstring(value, width), + kind: LiteralKind::Bitstring { value, width }, })) } Literal::Imaginary => { @@ -492,13 +492,13 @@ fn index_element(s: &mut ParserContext) -> Result { 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 = exprs - .into_iter() - .map(Box::new) - .collect::>() - .into_boxed_slice(); - IndexElement::IndexSet(exprs) + let exprs = list_from_iter(exprs); + IndexElement::IndexSet(IndexSet { + span: s.span(lo), + values: exprs, + }) } }; Ok(index) @@ -556,7 +556,7 @@ pub(crate) fn set_expr(s: &mut ParserContext) -> Result { recovering_token(s, TokenKind::Close(Delim::Brace)); Ok(DiscreteSet { span: s.span(lo), - values: exprs.into_boxed_slice(), + values: list_from_iter(exprs), }) } diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index 455a3bcdff..1e8cec4ac5 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -117,8 +117,9 @@ fn lit_int_min() { check_expr( "-9_223_372_036_854_775_808", &expect![[r#" - Expr [0-26]: UnOp (Neg): - Expr [1-26]: Lit: Int(-9223372036854775808)"#]], + Expr [0-26]: UnaryOpExpr: + op: Neg + expr: Expr [1-26]: Lit: Int(-9223372036854775808)"#]], ); } @@ -540,9 +541,10 @@ fn pratt_parsing_binary_expr() { check_expr( "1 + 2", &expect![[r#" - Expr [0-5]: BinOp (Add): - Expr [0-1]: Lit: Int(1) - Expr [4-5]: Lit: Int(2)"#]], + Expr [0-5]: BinaryOpExpr: + op: Add + lhs: Expr [0-1]: Lit: Int(1) + rhs: Expr [4-5]: Lit: Int(2)"#]], ); } @@ -551,11 +553,13 @@ fn pratt_parsing_mul_add() { check_expr( "1 + 2 * 3", &expect![[r#" - Expr [0-9]: BinOp (Add): - Expr [0-1]: Lit: Int(1) - Expr [4-9]: BinOp (Mul): - Expr [4-5]: Lit: Int(2) - Expr [8-9]: Lit: Int(3)"#]], + 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)"#]], ); } @@ -564,12 +568,13 @@ fn pratt_parsing_parens() { check_expr( "(1 + 2) * 3", &expect![[r#" - Expr [0-11]: BinOp (Mul): - Expr [0-7]: Paren: - Expr [1-6]: BinOp (Add): - Expr [1-2]: Lit: Int(1) - Expr [5-6]: Lit: Int(2) - Expr [10-11]: Lit: Int(3)"#]], + 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)"#]], ); } @@ -578,10 +583,12 @@ fn prat_parsing_mul_unary() { check_expr( "2 * -3", &expect![[r#" - Expr [0-6]: BinOp (Mul): - Expr [0-1]: Lit: Int(2) - Expr [4-6]: UnOp (Neg): - Expr [5-6]: Lit: Int(3)"#]], + 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)"#]], ); } @@ -590,10 +597,12 @@ fn prat_parsing_unary_mul() { check_expr( "-2 * 3", &expect![[r#" - Expr [0-6]: BinOp (Mul): - Expr [0-2]: UnOp (Neg): - Expr [1-2]: Lit: Int(2) - Expr [5-6]: Lit: Int(3)"#]], + 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)"#]], ); } @@ -602,10 +611,13 @@ fn prat_parsing_exp_funcall() { check_expr( "2 ** square(3)", &expect![[r#" - Expr [0-14]: BinOp (Exp): - Expr [0-1]: Lit: Int(2) - Expr [5-14]: FunctionCall [5-14]: Ident [5-11] "square" - Expr [12-13]: Lit: Int(3)"#]], + 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)"#]], ); } @@ -614,10 +626,13 @@ fn prat_parsing_funcall_exp() { check_expr( "square(2) ** 3", &expect![[r#" - Expr [0-14]: BinOp (Exp): - Expr [0-9]: FunctionCall [0-9]: Ident [0-6] "square" - Expr [7-8]: Lit: Int(2) - Expr [13-14]: Lit: Int(3)"#]], + 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)"#]], ); } @@ -626,10 +641,13 @@ fn prat_parsing_funcall_exp_arg() { check_expr( "square(2 ** 3)", &expect![[r#" - Expr [0-14]: FunctionCall [0-14]: Ident [0-6] "square" - Expr [7-13]: BinOp (Exp): - Expr [7-8]: Lit: Int(2) - Expr [12-13]: Lit: Int(3)"#]], + 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)"#]], ); } @@ -638,8 +656,10 @@ fn funcall() { check_expr( "square(2)", &expect![[r#" - Expr [0-9]: FunctionCall [0-9]: Ident [0-6] "square" - Expr [7-8]: Lit: Int(2)"#]], + Expr [0-9]: FunctionCall [0-9]: + name: Ident [0-6] "square" + args: + Expr [7-8]: Lit: Int(2)"#]], ); } @@ -648,9 +668,11 @@ fn funcall_multiple_args() { check_expr( "square(2, 3)", &expect![[r#" - Expr [0-12]: FunctionCall [0-12]: Ident [0-6] "square" - Expr [7-8]: Lit: Int(2) - Expr [10-11]: Lit: Int(3)"#]], + Expr [0-12]: FunctionCall [0-12]: + name: Ident [0-6] "square" + args: + Expr [7-8]: Lit: Int(2) + Expr [10-11]: Lit: Int(3)"#]], ); } @@ -659,9 +681,11 @@ fn funcall_multiple_args_trailing_comma() { check_expr( "square(2, 3,)", &expect![[r#" - Expr [0-13]: FunctionCall [0-13]: Ident [0-6] "square" - Expr [7-8]: Lit: Int(2) - Expr [10-11]: Lit: Int(3)"#]], + Expr [0-13]: FunctionCall [0-13]: + name: Ident [0-6] "square" + args: + Expr [7-8]: Lit: Int(2) + Expr [10-11]: Lit: Int(3)"#]], ); } @@ -671,8 +695,9 @@ fn cast_to_bit() { "bit(0)", &expect![[r#" Expr [0-6]: Cast [0-6]: - ClassicalType [0-3]: BitType - Expr [4-5]: Lit: Int(0)"#]], + type: ScalarType [0-3]: BitType [0-3]: + size: + arg: Expr [4-5]: Lit: Int(0)"#]], ); } @@ -682,8 +707,9 @@ fn cast_to_bit_with_designator() { "bit[4](0)", &expect![[r#" Expr [0-9]: Cast [0-9]: - ClassicalType [0-6]: BitType [0-6]: Expr [4-5]: Lit: Int(4) - Expr [7-8]: Lit: Int(0)"#]], + type: ScalarType [0-6]: BitType [0-6]: + size: Expr [4-5]: Lit: Int(4) + arg: Expr [7-8]: Lit: Int(0)"#]], ); } @@ -693,8 +719,9 @@ fn cast_to_int() { "int(0)", &expect![[r#" Expr [0-6]: Cast [0-6]: - ClassicalType [0-3]: IntType [0-3] - Expr [4-5]: Lit: Int(0)"#]], + type: ScalarType [0-3]: IntType [0-3]: + size: + arg: Expr [4-5]: Lit: Int(0)"#]], ); } @@ -704,8 +731,9 @@ fn cast_to_int_with_designator() { "int[64](0)", &expect![[r#" Expr [0-10]: Cast [0-10]: - ClassicalType [0-7]: IntType[Expr [4-6]: Lit: Int(64)]: [0-7] - Expr [8-9]: Lit: Int(0)"#]], + type: ScalarType [0-7]: IntType [0-7]: + size: Expr [4-6]: Lit: Int(64) + arg: Expr [8-9]: Lit: Int(0)"#]], ); } @@ -715,8 +743,9 @@ fn cast_to_uint() { "uint(0)", &expect![[r#" Expr [0-7]: Cast [0-7]: - ClassicalType [0-4]: UIntType [0-4] - Expr [5-6]: Lit: Int(0)"#]], + type: ScalarType [0-4]: UIntType [0-4]: + size: + arg: Expr [5-6]: Lit: Int(0)"#]], ); } @@ -726,8 +755,9 @@ fn cast_to_uint_with_designator() { "uint[64](0)", &expect![[r#" Expr [0-11]: Cast [0-11]: - ClassicalType [0-8]: UIntType[Expr [5-7]: Lit: Int(64)]: [0-8] - Expr [9-10]: Lit: Int(0)"#]], + type: ScalarType [0-8]: UIntType [0-8]: + size: Expr [5-7]: Lit: Int(64) + arg: Expr [9-10]: Lit: Int(0)"#]], ); } @@ -737,8 +767,9 @@ fn cast_to_float() { "float(0)", &expect![[r#" Expr [0-8]: Cast [0-8]: - ClassicalType [0-5]: FloatType [0-5] - Expr [6-7]: Lit: Int(0)"#]], + type: ScalarType [0-5]: FloatType [0-5]: + size: + arg: Expr [6-7]: Lit: Int(0)"#]], ); } @@ -748,8 +779,9 @@ fn cast_to_float_with_designator() { "float[64](0)", &expect![[r#" Expr [0-12]: Cast [0-12]: - ClassicalType [0-9]: FloatType[Expr [6-8]: Lit: Int(64)]: [0-9] - Expr [10-11]: Lit: Int(0)"#]], + type: ScalarType [0-9]: FloatType [0-9]: + size: Expr [6-8]: Lit: Int(64) + arg: Expr [10-11]: Lit: Int(0)"#]], ); } @@ -759,8 +791,10 @@ fn cast_to_complex() { "complex[float](0)", &expect![[r#" Expr [0-17]: Cast [0-17]: - ClassicalType [0-14]: ComplexType[float[FloatType [8-13]]]: [0-14] - Expr [15-16]: Lit: Int(0)"#]], + type: ScalarType [0-14]: ComplexType [0-14]: + base_size: FloatType [8-13]: + size: + arg: Expr [15-16]: Lit: Int(0)"#]], ); } @@ -770,8 +804,10 @@ fn cast_to_complex_with_designator() { "complex[float[64]](0)", &expect![[r#" Expr [0-21]: Cast [0-21]: - ClassicalType [0-18]: ComplexType[float[FloatType[Expr [14-16]: Lit: Int(64)]: [8-17]]]: [0-18] - Expr [19-20]: Lit: Int(0)"#]], + 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)"#]], ); } @@ -781,8 +817,8 @@ fn cast_to_bool() { "bool(0)", &expect![[r#" Expr [0-7]: Cast [0-7]: - ClassicalType [0-4]: BoolType - Expr [5-6]: Lit: Int(0)"#]], + type: ScalarType [0-4]: BoolType + arg: Expr [5-6]: Lit: Int(0)"#]], ); } @@ -792,8 +828,8 @@ fn cast_to_duration() { "duration(0)", &expect![[r#" Expr [0-11]: Cast [0-11]: - ClassicalType [0-8]: Duration - Expr [9-10]: Lit: Int(0)"#]], + type: ScalarType [0-8]: Duration + arg: Expr [9-10]: Lit: Int(0)"#]], ); } @@ -803,8 +839,8 @@ fn cast_to_stretch() { "stretch(0)", &expect![[r#" Expr [0-10]: Cast [0-10]: - ClassicalType [0-7]: Stretch - Expr [8-9]: Lit: Int(0)"#]], + type: ScalarType [0-7]: Stretch + arg: Expr [8-9]: Lit: Int(0)"#]], ); } @@ -814,9 +850,12 @@ fn cast_to_int_array() { "array[int[64], 4](0)", &expect![[r#" Expr [0-20]: Cast [0-20]: - ArrayType [0-17]: ArrayBaseTypeKind IntType[Expr [10-12]: Lit: Int(64)]: [6-13] - Expr [15-16]: Lit: Int(4) - Expr [18-19]: Lit: Int(0)"#]], + 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)"#]], ); } @@ -826,9 +865,12 @@ fn cast_to_uint_array() { "array[uint[64], 4](0)", &expect![[r#" Expr [0-21]: Cast [0-21]: - ArrayType [0-18]: ArrayBaseTypeKind UIntType[Expr [11-13]: Lit: Int(64)]: [6-14] - Expr [16-17]: Lit: Int(4) - Expr [19-20]: Lit: Int(0)"#]], + 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)"#]], ); } @@ -838,9 +880,12 @@ fn cast_to_float_array() { "array[float[64], 4](0)", &expect![[r#" Expr [0-22]: Cast [0-22]: - ArrayType [0-19]: ArrayBaseTypeKind FloatType[Expr [12-14]: Lit: Int(64)]: [6-15] - Expr [17-18]: Lit: Int(4) - Expr [20-21]: Lit: Int(0)"#]], + 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)"#]], ); } @@ -850,9 +895,12 @@ fn cast_to_angle_array() { "array[angle[64], 4](0)", &expect![[r#" Expr [0-22]: Cast [0-22]: - ArrayType [0-19]: ArrayBaseTypeKind AngleType [6-15]: Expr [12-14]: Lit: Int(64) - Expr [17-18]: Lit: Int(4) - Expr [20-21]: Lit: Int(0)"#]], + 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)"#]], ); } @@ -862,9 +910,11 @@ fn cast_to_bool_array() { "array[bool, 4](0)", &expect![[r#" Expr [0-17]: Cast [0-17]: - ArrayType [0-14]: ArrayBaseTypeKind BoolType - Expr [12-13]: Lit: Int(4) - Expr [15-16]: Lit: Int(0)"#]], + type: ArrayType [0-14]: + base_type: ArrayBaseTypeKind BoolType + dimensions: + Expr [12-13]: Lit: Int(4) + arg: Expr [15-16]: Lit: Int(0)"#]], ); } @@ -874,9 +924,11 @@ fn cast_to_duration_array() { "array[duration, 4](0)", &expect![[r#" Expr [0-21]: Cast [0-21]: - ArrayType [0-18]: ArrayBaseTypeKind DurationType - Expr [16-17]: Lit: Int(4) - Expr [19-20]: Lit: Int(0)"#]], + type: ArrayType [0-18]: + base_type: ArrayBaseTypeKind DurationType + dimensions: + Expr [16-17]: Lit: Int(4) + arg: Expr [19-20]: Lit: Int(0)"#]], ); } @@ -886,9 +938,13 @@ fn cast_to_complex_array() { "array[complex[float[32]], 4](0)", &expect![[r#" Expr [0-31]: Cast [0-31]: - ArrayType [0-28]: ArrayBaseTypeKind ComplexType[float[FloatType[Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24] - Expr [26-27]: Lit: Int(4) - Expr [29-30]: Lit: Int(0)"#]], + 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)"#]], ); } @@ -897,8 +953,11 @@ fn index_expr() { check_expr( "foo[1]", &expect![[r#" - Expr [0-6]: IndexExpr [3-6]: Expr [0-3]: Ident [0-3] "foo", IndexElement: - IndexSetItem Expr [4-5]: Lit: Int(1)"#]], + Expr [0-6]: IndexExpr [3-6]: + collection: Expr [0-3]: Ident [0-3] "foo" + index: IndexSet [4-5]: + values: + Expr [4-5]: Lit: Int(1)"#]], ); } @@ -907,10 +966,13 @@ fn index_set() { check_expr( "foo[{1, 4, 5}]", &expect![[r#" - Expr [0-14]: IndexExpr [3-14]: Expr [0-3]: Ident [0-3] "foo", IndexElement DiscreteSet [4-13]: - Expr [5-6]: Lit: Int(1) - Expr [8-9]: Lit: Int(4) - Expr [11-12]: Lit: Int(5)"#]], + Expr [0-14]: IndexExpr [3-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)"#]], ); } @@ -919,19 +981,22 @@ fn index_multiple_ranges() { check_expr( "foo[1:5, 3:7, 4:8]", &expect![[r#" - Expr [0-18]: IndexExpr [3-18]: Expr [0-3]: Ident [0-3] "foo", IndexElement: - Range: [4-7] - start: Expr [4-5]: Lit: Int(1) - - end: Expr [6-7]: Lit: Int(5) - Range: [9-12] - start: Expr [9-10]: Lit: Int(3) - - end: Expr [11-12]: Lit: Int(7) - Range: [14-17] - start: Expr [14-15]: Lit: Int(4) - - end: Expr [16-17]: Lit: Int(8)"#]], + Expr [0-18]: IndexExpr [3-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)"#]], ); } @@ -940,11 +1005,14 @@ fn index_range() { check_expr( "foo[1:5:2]", &expect![[r#" - Expr [0-10]: IndexExpr [3-10]: Expr [0-3]: Ident [0-3] "foo", IndexElement: - Range: [4-9] - start: Expr [4-5]: Lit: Int(1) - step: Expr [6-7]: Lit: Int(5) - end: Expr [8-9]: Lit: Int(2)"#]], + Expr [0-10]: IndexExpr [3-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)"#]], ); } @@ -953,11 +1021,14 @@ fn index_full_range() { check_expr( "foo[:]", &expect![[r#" - Expr [0-6]: IndexExpr [3-6]: Expr [0-3]: Ident [0-3] "foo", IndexElement: - Range: [4-5] - - - "#]], + Expr [0-6]: IndexExpr [3-6]: + collection: Expr [0-3]: Ident [0-3] "foo" + index: IndexSet [4-5]: + values: + RangeDefinition [4-5]: + start: + step: + end: "#]], ); } @@ -966,11 +1037,14 @@ fn index_range_start() { check_expr( "foo[1:]", &expect![[r#" - Expr [0-7]: IndexExpr [3-7]: Expr [0-3]: Ident [0-3] "foo", IndexElement: - Range: [4-6] - start: Expr [4-5]: Lit: Int(1) - - "#]], + Expr [0-7]: IndexExpr [3-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: "#]], ); } @@ -979,11 +1053,14 @@ fn index_range_end() { check_expr( "foo[:5]", &expect![[r#" - Expr [0-7]: IndexExpr [3-7]: Expr [0-3]: Ident [0-3] "foo", IndexElement: - Range: [4-6] - - - end: Expr [5-6]: Lit: Int(5)"#]], + Expr [0-7]: IndexExpr [3-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)"#]], ); } @@ -992,11 +1069,14 @@ fn index_range_step() { check_expr( "foo[:2:]", &expect![[r#" - Expr [0-8]: IndexExpr [3-8]: Expr [0-3]: Ident [0-3] "foo", IndexElement: - Range: [4-7] - - step: Expr [5-6]: Lit: Int(2) - "#]], + Expr [0-8]: IndexExpr [3-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: "#]], ); } @@ -1006,10 +1086,11 @@ fn set_expr() { super::set_expr, "{2, 3, 4}", &expect![[r#" - DiscreteSet [0-9]: - Expr [1-2]: Lit: Int(2) - Expr [4-5]: Lit: Int(3) - Expr [7-8]: Lit: Int(4)"#]], + DiscreteSet [0-9]: + values: + Expr [1-2]: Lit: Int(2) + Expr [4-5]: Lit: Int(3) + Expr [7-8]: Lit: Int(4)"#]], ); } @@ -1019,9 +1100,15 @@ fn lit_array() { super::lit_array, "{{2, {5}}, 1 + z}", &expect![[r#" - Expr [0-17]: Lit: Array: - Expr { span: Span { lo: 1, hi: 9 }, kind: Lit(Lit { span: Span { lo: 1, hi: 9 }, kind: Array([Expr { span: Span { lo: 2, hi: 3 }, kind: Lit(Lit { span: Span { lo: 2, hi: 3 }, kind: Int(2) }) }, Expr { span: Span { lo: 5, hi: 8 }, kind: Lit(Lit { span: Span { lo: 5, hi: 8 }, kind: Array([Expr { span: Span { lo: 6, hi: 7 }, kind: Lit(Lit { span: Span { lo: 6, hi: 7 }, kind: Int(5) }) }]) }) }]) }) } - Expr { span: Span { lo: 11, hi: 16 }, kind: BinaryOp(BinaryOpExpr { op: Add, lhs: Expr { span: Span { lo: 11, hi: 12 }, kind: Lit(Lit { span: Span { lo: 11, hi: 12 }, kind: Int(1) }) }, rhs: Expr { span: Span { lo: 15, hi: 16 }, kind: Ident(Ident { span: Span { lo: 15, hi: 16 }, name: "z" }) } }) }"#]], + 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""#]], ); } @@ -1030,12 +1117,14 @@ fn assignment_and_unop() { check_expr( "c = a && !b", &expect![[r#" - Expr [0-11]: Assign: - Expr [0-1]: Ident [0-1] "c" - Expr [4-11]: BinOp (AndL): - Expr [4-5]: Ident [4-5] "a" - Expr [9-11]: UnOp (NotL): - Expr [10-11]: Ident [10-11] "b""#]], + Expr [0-11]: AssignExpr: + lhs: Expr [0-1]: Ident [0-1] "c" + 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""#]], ); } @@ -1044,12 +1133,14 @@ fn assignment_unop_and() { check_expr( "d = !a && b", &expect![[r#" - Expr [0-11]: Assign: - Expr [0-1]: Ident [0-1] "d" - Expr [4-11]: BinOp (AndL): - Expr [4-6]: UnOp (NotL): - Expr [5-6]: Ident [5-6] "a" - Expr [10-11]: Ident [10-11] "b""#]], + Expr [0-11]: AssignExpr: + lhs: Expr [0-1]: Ident [0-1] "d" + 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""#]], ); } @@ -1068,11 +1159,15 @@ fn indexed_identifier() { super::indexed_identifier, "arr[1][2]", &expect![[r#" - IndexedIdent [0-9]: Ident [0-3] "arr"[ - IndexElement: - IndexSetItem Expr [4-5]: Lit: Int(1) - IndexElement: - IndexSetItem Expr [7-8]: Lit: Int(2)]"#]], + IndexedIdent [0-9]: + name: Ident [0-3] "arr" + indices: + IndexSet [4-5]: + values: + Expr [4-5]: Lit: Int(1) + IndexSet [7-8]: + values: + Expr [7-8]: Lit: Int(2)"#]], ); } @@ -1081,7 +1176,9 @@ fn measure_hardware_qubit() { check( super::measure_expr, "measure $12", - &expect!["MeasureExpr [0-7]: GateOperand HardwareQubit [8-11]: 12"], + &expect![[r#" + MeasureExpr [0-7]: + operand: HardwareQubit [8-11]: 12"#]], ); } @@ -1091,11 +1188,16 @@ fn measure_indexed_identifier() { super::measure_expr, "measure qubits[1][2]", &expect![[r#" - MeasureExpr [0-7]: GateOperand IndexedIdent [8-20]: Ident [8-14] "qubits"[ - IndexElement: - IndexSetItem Expr [15-16]: Lit: Int(1) - IndexElement: - IndexSetItem Expr [18-19]: Lit: Int(2)]"#]], + MeasureExpr [0-7]: + operand: IndexedIdent [8-20]: + name: Ident [8-14] "qubits" + indices: + IndexSet [15-16]: + values: + Expr [15-16]: Lit: Int(1) + IndexSet [18-19]: + values: + Expr [18-19]: Lit: Int(2)"#]], ); } @@ -1104,12 +1206,15 @@ fn addition_of_casts() { check_expr( "bit(0) + bit(1)", &expect![[r#" - Expr [0-15]: BinOp (Add): - Expr [0-6]: Cast [0-6]: - ClassicalType [0-3]: BitType - Expr [4-5]: Lit: Int(0) - Expr [9-15]: Cast [9-15]: - ClassicalType [9-12]: BitType - Expr [13-14]: Lit: Int(1)"#]], + 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/prim/tests.rs b/compiler/qsc_qasm3/src/parser/prim/tests.rs index d21861f1cd..a86993fa83 100644 --- a/compiler/qsc_qasm3/src/parser/prim/tests.rs +++ b/compiler/qsc_qasm3/src/parser/prim/tests.rs @@ -87,7 +87,14 @@ fn ident_keyword() { #[test] fn path_single() { - check(path, "Foo", &expect![[r#"Path [0-3] (Ident [0-3] "Foo")"#]]); + check( + path, + "Foo", + &expect![[r#" + Path [0-3]: + name: Ident [0-3] "Foo" + segments: "#]], + ); } #[test] @@ -97,8 +104,9 @@ fn path_double() { "Foo.Bar", &expect![[r#" Path [0-7]: - Ident [0-3] "Foo" - Ident [4-7] "Bar""#]], + name: Ident [4-7] "Bar" + segments: + Ident [0-3] "Foo""#]], ); } @@ -109,9 +117,10 @@ fn path_triple() { "Foo.Bar.Baz", &expect![[r#" Path [0-11]: - Ident [0-3] "Foo" - Ident [4-7] "Bar" - Ident [8-11] "Baz""#]], + name: Ident [8-11] "Baz" + segments: + Ident [0-3] "Foo" + Ident [4-7] "Bar""#]], ); } @@ -121,9 +130,9 @@ fn path_trailing_dot() { path, "Foo.Bar.", &expect![[r#" - Err IncompletePath [0-8]: - Ident [0-3] "Foo" - Ident [4-7] "Bar" + Err IncompletePath [0-8]: segments: + Ident [0-3] "Foo" + Ident [4-7] "Bar" [ Error( @@ -146,9 +155,9 @@ fn path_followed_by_keyword() { path, "Foo.Bar.in", &expect![[r#" - Err IncompletePath [0-10]: - Ident [0-3] "Foo" - Ident [4-7] "Bar" + Err IncompletePath [0-10]: segments: + Ident [0-3] "Foo" + Ident [4-7] "Bar" [ Error( @@ -174,8 +183,9 @@ fn opt_succeed() { "Foo.Bar", &expect![[r#" Path [0-7]: - Ident [0-3] "Foo" - Ident [4-7] "Bar""#]], + name: Ident [4-7] "Bar" + segments: + Ident [0-3] "Foo""#]], ); } @@ -190,8 +200,8 @@ fn opt_fail_consume() { |s| opt(s, path), "Foo.$", &expect![[r#" - Err IncompletePath [0-5]: - Ident [0-3] "Foo" + Err IncompletePath [0-5]: segments: + Ident [0-3] "Foo" [ Error( diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index a18ac394ff..2dfa716727 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -17,15 +17,16 @@ use super::{ use crate::{ ast::{ list_from_iter, AccessControl, AliasDeclStmt, AngleType, Annotation, ArrayBaseTypeKind, - ArrayReferenceType, ArrayType, 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, IntType, List, - LiteralKind, MeasureStmt, Pragma, QuantumGateDefinition, QuantumGateModifier, - QubitDeclaration, RangeDefinition, ResetStmt, ReturnStmt, ScalarType, ScalarTypeKind, Stmt, - StmtKind, SwitchStmt, TypeDef, TypedParameter, UIntType, WhileLoop, + ArrayReferenceType, ArrayType, ArrayTypedParameter, 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, IntType, + List, LiteralKind, MeasureStmt, Pragma, QuantumGateDefinition, QuantumGateModifier, + QuantumTypedParameter, QubitDeclaration, RangeDefinition, ResetStmt, ReturnStmt, + ScalarType, ScalarTypeKind, ScalarTypedParameter, Stmt, StmtKind, SwitchCase, SwitchStmt, + TypeDef, TypedParameter, UIntType, WhileLoop, }, keyword::Keyword, lex::{cooked::Type, Delim, TokenKind}, @@ -359,10 +360,18 @@ fn arg_def(s: &mut ParserContext) -> Result { let kind = if let Ok(ty) = scalar_type(s) { let ident = prim::ident(s)?; - TypedParameter::Scalar(ty, Box::new(ident), s.span(lo)) + TypedParameter::Scalar(ScalarTypedParameter { + span: s.span(lo), + r#type: Box::new(ty), + ident, + }) } else if let Ok(size) = qubit_type(s) { let ident = prim::ident(s)?; - TypedParameter::Quantum(size, Box::new(ident), s.span(lo)) + 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), @@ -371,12 +380,24 @@ fn arg_def(s: &mut ParserContext) -> Result { span: s.span(lo), }), }; - TypedParameter::Scalar(ty, ident, s.span(lo)) + TypedParameter::Scalar(ScalarTypedParameter { + span: s.span(lo), + r#type: Box::new(ty), + ident, + }) } else if let Ok((ident, size)) = qreg_type(s) { - TypedParameter::Quantum(size, ident, s.span(lo)) + 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(ty, Box::new(ident), s.span(lo)) + TypedParameter::ArrayReference(ArrayTypedParameter { + span: s.span(lo), + r#type: Box::new(ty), + ident, + }) } else { return Err(Error::new(ErrorKind::Rule( "argument definition", @@ -472,7 +493,7 @@ fn parse_quantum_decl(s: &mut ParserContext) -> Result { recovering_semi(s); Ok(StmtKind::QuantumDecl(QubitDeclaration { span: s.span(lo), - qubit: Box::new(ident), + qubit: ident, size, })) } @@ -531,7 +552,7 @@ fn parse_non_constant_classical_decl( ty: TypeDef, lo: u32, ) -> Result { - let identifier = Box::new(prim::ident(s)?); + let identifier = prim::ident(s)?; let init_expr = if s.peek().kind == TokenKind::Eq { s.advance(); Some(expr::value_expr(s)?) @@ -541,7 +562,7 @@ fn parse_non_constant_classical_decl( recovering_semi(s); let decl = ClassicalDeclarationStmt { span: s.span(lo), - r#type: ty, + r#type: Box::new(ty), identifier, init_expr, }; @@ -654,13 +675,13 @@ fn creg_decl(s: &mut ParserContext) -> Result { recovering_semi(s); Ok(StmtKind::ClassicalDecl(ClassicalDeclarationStmt { span: s.span(lo), - r#type: TypeDef::Scalar(ScalarType { + r#type: Box::new(TypeDef::Scalar(ScalarType { span: s.span(lo), kind: ScalarTypeKind::Bit(BitType { size, span: s.span(lo), }), - }), + })), identifier, init_expr: None, })) @@ -683,16 +704,16 @@ fn extern_creg_type(s: &mut ParserContext) -> Result> { Ok(size) } -fn creg_type(s: &mut ParserContext) -> Result<(Box, Option)> { +fn creg_type(s: &mut ParserContext) -> Result<(Ident, Option)> { token(s, TokenKind::Keyword(crate::keyword::Keyword::CReg))?; - let name = Box::new(prim::ident(s)?); + let name = prim::ident(s)?; let size = opt(s, designator)?; Ok((name, size)) } -fn qreg_type(s: &mut ParserContext) -> Result<(Box, Option)> { +fn qreg_type(s: &mut ParserContext) -> Result<(Ident, Option)> { token(s, TokenKind::Keyword(crate::keyword::Keyword::QReg))?; - let name = Box::new(prim::ident(s)?); + let name = prim::ident(s)?; let size = opt(s, designator)?; Ok((name, size)) } @@ -911,7 +932,7 @@ pub fn parse_switch_stmt(s: &mut ParserContext) -> Result { }) } -fn case_stmt(s: &mut ParserContext) -> Result<(List, Block)> { +fn case_stmt(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; token(s, TokenKind::Keyword(Keyword::Case))?; @@ -921,7 +942,12 @@ fn case_stmt(s: &mut ParserContext) -> Result<(List, Block)> { } let block = parse_block(s).map(|block| *block)?; - Ok((list_from_iter(controlling_label), block)) + + Ok(SwitchCase { + span: s.span(lo), + labels: list_from_iter(controlling_label), + block, + }) } fn default_case_stmt(s: &mut ParserContext) -> Result { @@ -1266,8 +1292,8 @@ fn reinterpret_index_expr( } = index_expr; if let IndexElement::IndexSet(set) = index { - if set.len() == 1 { - let first_elt: IndexSetItem = (*set[0]).clone(); + 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 { diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs index 65d53fc6c7..9a918a3296 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs @@ -6,23 +6,47 @@ use crate::parser::tests::check; use expect_test::expect; #[test] -fn alias_decl_stmt() { +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] - StmtKind: Alias [0-32]: Ident [4-5] "x" - Expr [8-14]: IndexExpr [9-14]: Expr [8-9]: Ident [8-9] "a", IndexElement: - Range: [10-13] - start: Expr [10-11]: Lit: Int(1) - - end: Expr [12-13]: Lit: Int(2) - Expr [18-19]: Ident [18-19] "b" - Expr [23-31]: IndexExpr [24-31]: Expr [23-24]: Ident [23-24] "c", IndexElement: - Range: [25-30] - start: Expr [25-26]: Lit: Int(1) - step: Expr [27-28]: Lit: Int(2) - end: Expr [29-30]: Lit: Int(3)"#]], + Stmt [0-32]: + annotations: + kind: AliasDeclStmt [0-32]: + ident: Ident [4-5] "x" + exprs: + Expr [8-14]: IndexExpr [9-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 [24-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 index d2f5eb4942..a37d4e11ff 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/annotation.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/annotation.rs @@ -1,18 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use expect_test::expect; - -use crate::parser::tests::check; - 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!["Annotation [0-9]: (a.b.d, 23)"], + &expect![[r#" + Annotation [0-9]: + identifier: "a.b.d" + value: "23""#]], ); } @@ -21,7 +22,10 @@ fn annotation_ident_only() { check( parse_annotation, "@a.b.d", - &expect!["Annotation [0-6]: (a.b.d)"], + &expect![[r#" + Annotation [0-6]: + identifier: "a.b.d" + value: "#]], ); } @@ -31,7 +35,9 @@ fn annotation_missing_ident() { parse_annotation, "@", &expect![[r#" - Annotation [0-1]: () + Annotation [0-1]: + identifier: "" + value: [ Error( diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs index c925c89a8c..4bcd0c5776 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs @@ -11,13 +11,20 @@ fn barrier() { parse, "barrier r, q[0], $2;", &expect![[r#" - Stmt [0-20] - StmtKind: Barrier [0-20]: [ - GateOperand IndexedIdent [8-9]: Ident [8-9] "r"[] - GateOperand IndexedIdent [11-15]: Ident [11-12] "q"[ - IndexElement: - IndexSetItem Expr [13-14]: Lit: Int(0)] - GateOperand HardwareQubit [17-19]: 2]"#]], + Stmt [0-20]: + annotations: + kind: BarrierStmt [0-20]: + operands: + IndexedIdent [8-9]: + name: Ident [8-9] "r" + indices: + IndexedIdent [11-15]: + name: Ident [11-12] "q" + indices: + IndexSet [13-14]: + values: + Expr [13-14]: Lit: Int(0) + HardwareQubit [17-19]: 2"#]], ); } @@ -27,7 +34,9 @@ fn barrier_no_args() { parse, "barrier;", &expect![[r#" - Stmt [0-8] - StmtKind: Barrier [0-8]: []"#]], + Stmt [0-8]: + annotations: + kind: BarrierStmt [0-8]: + operands: "#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs index 463f5fa21c..6e500319fa 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs @@ -15,15 +15,34 @@ fn box_stmt() { Rx(2.4) q1; }", &expect![[r#" - Stmt [5-50] - StmtKind: BoxStmt [5-50]: - Stmt [19-24] - StmtKind: GateCall [19-24]: Ident [19-20] "H" - GateOperand IndexedIdent [21-23]: Ident [21-23] "q0"[] - Stmt [33-44] - StmtKind: GateCall [33-44]: Ident [33-35] "Rx" - Expr [36-39]: Lit: Float(2.4) - GateOperand IndexedIdent [41-43]: Ident [41-43] "q1"[]"#]], + 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: + IndexedIdent [21-23]: + name: Ident [21-23] "q0" + 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: + IndexedIdent [41-43]: + name: Ident [41-43] "q1" + indices: "#]], ); } @@ -37,15 +56,34 @@ fn box_stmt_with_designator() { Rx(2.4) q1; }", &expect![[r#" - Stmt [5-55] - StmtKind: BoxStmt [5-55]: Expr [9-12]: Lit: Duration(4.0, Us) - Stmt [24-29] - StmtKind: GateCall [24-29]: Ident [24-25] "H" - GateOperand IndexedIdent [26-28]: Ident [26-28] "q0"[] - Stmt [38-49] - StmtKind: GateCall [38-49]: Ident [38-40] "Rx" - Expr [41-44]: Lit: Float(2.4) - GateOperand IndexedIdent [46-48]: Ident [46-48] "q1"[]"#]], + 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: + IndexedIdent [26-28]: + name: Ident [26-28] "q0" + 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: + IndexedIdent [46-48]: + name: Ident [46-48] "q1" + indices: "#]], ); } @@ -59,16 +97,36 @@ fn box_stmt_with_invalid_instruction() { X q1; }", &expect![[r#" - Stmt [0-54] - StmtKind: BoxStmt [0-54]: - Stmt [14-19] - StmtKind: GateCall [14-19]: Ident [14-15] "H" - GateOperand IndexedIdent [16-18]: Ident [16-18] "q0"[] - Stmt [28-34] - StmtKind: Err - Stmt [43-48] - StmtKind: GateCall [43-48]: Ident [43-44] "X" - GateOperand IndexedIdent [45-47]: Ident [45-47] "q1"[] + Stmt [0-54]: + annotations: + kind: BoxStmt [0-54]: + duration: + body: + Stmt [14-19]: + annotations: + kind: GateCall [14-19]: + modifiers: + name: Ident [14-15] "H" + args: + duration: + qubits: + IndexedIdent [16-18]: + name: Ident [16-18] "q0" + indices: + Stmt [28-34]: + annotations: + kind: Err + Stmt [43-48]: + annotations: + kind: GateCall [43-48]: + modifiers: + name: Ident [43-44] "X" + args: + duration: + qubits: + IndexedIdent [45-47]: + name: Ident [45-47] "q1" + indices: [ Error( diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/cal.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/cal.rs index da1ce82d60..13633b5175 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/cal.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/cal.rs @@ -38,7 +38,8 @@ fn cal_block_accept_any_tokens_inside() { .314 }", &expect![[r#" - Stmt [5-69] - StmtKind: CalibrationStmt [5-69]"#]], + 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 index f5ea7b2380..da777fc9fa 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/cal_grammar.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/cal_grammar.rs @@ -11,8 +11,10 @@ fn defcalgrammar() { parse, r#"defcalgrammar "openpulse";"#, &expect![[r#" - Stmt [0-26] - StmtKind: CalibrationGrammarStmt [0-26]: openpulse"#]], + Stmt [0-26]: + annotations: + kind: CalibrationGrammarStmt [0-26]: + name: openpulse"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs index 1a6dd141fe..e9292638af 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs @@ -13,8 +13,13 @@ fn bit_decl() { parse, "bit b;", &expect![[r#" - Stmt [0-6] - StmtKind: ClassicalDeclarationStmt [0-6]: ClassicalType [0-3]: BitType, Ident [4-5] "b""#]], + Stmt [0-6]: + annotations: + kind: ClassicalDeclarationStmt [0-6]: + type: ScalarType [0-3]: BitType [0-3]: + size: + ident: Ident [4-5] "b" + init_expr: "#]], ); } @@ -24,8 +29,13 @@ fn bit_decl_bit_lit() { parse, "bit b = 1;", &expect![[r#" - Stmt [0-10] - StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-3]: BitType, Ident [4-5] "b", ValueExpression Expr [8-9]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -35,8 +45,13 @@ fn const_bit_decl_bit_lit() { parse, "const bit b = 1;", &expect![[r#" - Stmt [0-16] - StmtKind: ConstantDeclaration [0-16]: ClassicalType [6-9]: BitType, Ident [10-11] "b", Expr [14-15]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -46,8 +61,13 @@ fn bit_array_decl() { parse, "bit[2] b;", &expect![[r#" - Stmt [0-9] - StmtKind: ClassicalDeclarationStmt [0-9]: ClassicalType [0-6]: BitType [0-6]: Expr [4-5]: Lit: Int(2), Ident [7-8] "b""#]], + 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: "#]], ); } @@ -57,8 +77,13 @@ fn bit_array_decl_bit_lit() { parse, "bit[2] b = \"11\";", &expect![[r#" - Stmt [0-16] - StmtKind: ClassicalDeclarationStmt [0-16]: ClassicalType [0-6]: BitType [0-6]: Expr [4-5]: Lit: Int(2), Ident [7-8] "b", ValueExpression Expr [11-15]: Lit: Bitstring("11")"#]], + 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")"#]], ); } @@ -68,8 +93,13 @@ fn const_bit_array_decl_bit_lit() { parse, "const bit[2] b = \"11\";", &expect![[r#" - Stmt [0-22] - StmtKind: ConstantDeclaration [0-22]: ClassicalType [6-12]: BitType [6-12]: Expr [10-11]: Lit: Int(2), Ident [13-14] "b", Expr [17-21]: Lit: Bitstring("11")"#]], + 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")"#]], ); } @@ -79,8 +109,12 @@ fn bool_decl() { parse, "bool b;", &expect![[r#" - Stmt [0-7] - StmtKind: ClassicalDeclarationStmt [0-7]: ClassicalType [0-4]: BoolType, Ident [5-6] "b""#]], + Stmt [0-7]: + annotations: + kind: ClassicalDeclarationStmt [0-7]: + type: ScalarType [0-4]: BoolType + ident: Ident [5-6] "b" + init_expr: "#]], ); } @@ -90,8 +124,12 @@ fn bool_decl_int_lit() { parse, "bool b = 1;", &expect![[r#" - Stmt [0-11] - StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-4]: BoolType, Ident [5-6] "b", ValueExpression Expr [9-10]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -101,8 +139,12 @@ fn const_bool_decl_bool_lit() { parse, "const bool b = true;", &expect![[r#" - Stmt [0-20] - StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-10]: BoolType, Ident [11-12] "b", Expr [15-19]: Lit: Bool(true)"#]], + 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)"#]], ); } @@ -112,8 +154,13 @@ fn complex_decl() { parse, "complex c;", &expect![[r#" - Stmt [0-10] - StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-7]: ComplexType [0-7], Ident [8-9] "c""#]], + Stmt [0-10]: + annotations: + kind: ClassicalDeclarationStmt [0-10]: + type: ScalarType [0-7]: ComplexType [0-7]: + base_size: + ident: Ident [8-9] "c" + init_expr: "#]], ); } @@ -123,8 +170,13 @@ fn complex_decl_complex_lit() { parse, "complex c = 1im;", &expect![[r#" - Stmt [0-16] - StmtKind: ClassicalDeclarationStmt [0-16]: ClassicalType [0-7]: ComplexType [0-7], Ident [8-9] "c", ValueExpression Expr [12-15]: Lit: Imaginary(1.0)"#]], + 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)"#]], ); } @@ -134,8 +186,13 @@ fn const_complex_decl_complex_lit() { parse, "const complex c = 1im;", &expect![[r#" - Stmt [0-22] - StmtKind: ConstantDeclaration [0-22]: ClassicalType [6-13]: ComplexType [6-13], Ident [14-15] "c", Expr [18-21]: Lit: Imaginary(1.0)"#]], + 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)"#]], ); } @@ -145,10 +202,16 @@ fn const_complex_decl_complex_binary_lit() { parse, "const complex c = 23.5 + 1.7im;", &expect![[r#" - Stmt [0-31] - StmtKind: ConstantDeclaration [0-31]: ClassicalType [6-13]: ComplexType [6-13], Ident [14-15] "c", Expr [18-30]: BinOp (Add): - Expr [18-22]: Lit: Float(23.5) - Expr [25-30]: Lit: Imaginary(1.7)"#]], + 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)"#]], ); } @@ -158,8 +221,14 @@ fn complex_sized_decl() { parse, "complex[float[32]] c;", &expect![[r#" - Stmt [0-21] - StmtKind: ClassicalDeclarationStmt [0-21]: ClassicalType [0-18]: ComplexType[float[FloatType[Expr [14-16]: Lit: Int(32)]: [8-17]]]: [0-18], Ident [19-20] "c""#]], + 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: "#]], ); } @@ -191,8 +260,14 @@ fn complex_sized_decl_complex_lit() { parse, "complex[float[32]] c = 1im;", &expect![[r#" - Stmt [0-27] - StmtKind: ClassicalDeclarationStmt [0-27]: ClassicalType [0-18]: ComplexType[float[FloatType[Expr [14-16]: Lit: Int(32)]: [8-17]]]: [0-18], Ident [19-20] "c", ValueExpression Expr [23-26]: Lit: Imaginary(1.0)"#]], + 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)"#]], ); } @@ -202,8 +277,14 @@ fn const_complex_sized_decl_complex_lit() { parse, "const complex[float[32]] c = 1im;", &expect![[r#" - Stmt [0-33] - StmtKind: ConstantDeclaration [0-33]: ClassicalType [6-24]: ComplexType[float[FloatType[Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24], Ident [25-26] "c", Expr [29-32]: Lit: Imaginary(1.0)"#]], + 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)"#]], ); } @@ -213,8 +294,13 @@ fn int_decl() { parse, "int i;", &expect![[r#" - Stmt [0-6] - StmtKind: ClassicalDeclarationStmt [0-6]: ClassicalType [0-3]: IntType [0-3], Ident [4-5] "i""#]], + Stmt [0-6]: + annotations: + kind: ClassicalDeclarationStmt [0-6]: + type: ScalarType [0-3]: IntType [0-3]: + size: + ident: Ident [4-5] "i" + init_expr: "#]], ); } @@ -224,8 +310,13 @@ fn int_decl_int_lit() { parse, "int i = 1;", &expect![[r#" - Stmt [0-10] - StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-3]: IntType [0-3], Ident [4-5] "i", ValueExpression Expr [8-9]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -235,8 +326,13 @@ fn const_int_decl_int_lit() { parse, "const int i = 1;", &expect![[r#" - Stmt [0-16] - StmtKind: ConstantDeclaration [0-16]: ClassicalType [6-9]: IntType [6-9], Ident [10-11] "i", Expr [14-15]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -246,8 +342,13 @@ fn int_sized_decl() { parse, "int[32] i;", &expect![[r#" - Stmt [0-10] - StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-7]: IntType[Expr [4-6]: Lit: Int(32)]: [0-7], Ident [8-9] "i""#]], + 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: "#]], ); } @@ -257,8 +358,13 @@ fn int_sized_decl_int_lit() { parse, "int[32] i = 1;", &expect![[r#" - Stmt [0-14] - StmtKind: ClassicalDeclarationStmt [0-14]: ClassicalType [0-7]: IntType[Expr [4-6]: Lit: Int(32)]: [0-7], Ident [8-9] "i", ValueExpression Expr [12-13]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -268,8 +374,13 @@ fn const_int_sized_decl_int_lit() { parse, "const int[32] i = 1;", &expect![[r#" - Stmt [0-20] - StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-13]: IntType[Expr [10-12]: Lit: Int(32)]: [6-13], Ident [14-15] "i", Expr [18-19]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -279,8 +390,13 @@ fn uint_decl() { parse, "uint i;", &expect![[r#" - Stmt [0-7] - StmtKind: ClassicalDeclarationStmt [0-7]: ClassicalType [0-4]: UIntType [0-4], Ident [5-6] "i""#]], + Stmt [0-7]: + annotations: + kind: ClassicalDeclarationStmt [0-7]: + type: ScalarType [0-4]: UIntType [0-4]: + size: + ident: Ident [5-6] "i" + init_expr: "#]], ); } @@ -290,8 +406,13 @@ fn uint_decl_uint_lit() { parse, "uint i = 1;", &expect![[r#" - Stmt [0-11] - StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-4]: UIntType [0-4], Ident [5-6] "i", ValueExpression Expr [9-10]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -301,8 +422,13 @@ fn const_uint_decl_uint_lit() { parse, "const uint i = 1;", &expect![[r#" - Stmt [0-17] - StmtKind: ConstantDeclaration [0-17]: ClassicalType [6-10]: UIntType [6-10], Ident [11-12] "i", Expr [15-16]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -312,8 +438,13 @@ fn uint_sized_decl() { parse, "uint[32] i;", &expect![[r#" - Stmt [0-11] - StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-8]: UIntType[Expr [5-7]: Lit: Int(32)]: [0-8], Ident [9-10] "i""#]], + 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: "#]], ); } @@ -323,8 +454,13 @@ fn uint_sized_decl_uint_lit() { parse, "uint[32] i = 1;", &expect![[r#" - Stmt [0-15] - StmtKind: ClassicalDeclarationStmt [0-15]: ClassicalType [0-8]: UIntType[Expr [5-7]: Lit: Int(32)]: [0-8], Ident [9-10] "i", ValueExpression Expr [13-14]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -334,8 +470,13 @@ fn const_uint_sized_decl_uint_lit() { parse, "const uint[32] i = 1;", &expect![[r#" - Stmt [0-21] - StmtKind: ConstantDeclaration [0-21]: ClassicalType [6-14]: UIntType[Expr [11-13]: Lit: Int(32)]: [6-14], Ident [15-16] "i", Expr [19-20]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -345,8 +486,13 @@ fn float_decl() { parse, "float f;", &expect![[r#" - Stmt [0-8] - StmtKind: ClassicalDeclarationStmt [0-8]: ClassicalType [0-5]: FloatType [0-5], Ident [6-7] "f""#]], + Stmt [0-8]: + annotations: + kind: ClassicalDeclarationStmt [0-8]: + type: ScalarType [0-5]: FloatType [0-5]: + size: + ident: Ident [6-7] "f" + init_expr: "#]], ); } @@ -356,8 +502,13 @@ fn float_decl_float_lit() { parse, "float f = 1;", &expect![[r#" - Stmt [0-12] - StmtKind: ClassicalDeclarationStmt [0-12]: ClassicalType [0-5]: FloatType [0-5], Ident [6-7] "f", ValueExpression Expr [10-11]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -367,8 +518,13 @@ fn const_float_decl_float_lit() { parse, "const float f = 1.0;", &expect![[r#" - Stmt [0-20] - StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-11]: FloatType [6-11], Ident [12-13] "f", Expr [16-19]: Lit: Float(1.0)"#]], + 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)"#]], ); } @@ -378,8 +534,13 @@ fn float_sized_decl() { parse, "float[32] f;", &expect![[r#" - Stmt [0-12] - StmtKind: ClassicalDeclarationStmt [0-12]: ClassicalType [0-9]: FloatType[Expr [6-8]: Lit: Int(32)]: [0-9], Ident [10-11] "f""#]], + 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: "#]], ); } @@ -389,8 +550,13 @@ fn float_sized_decl_float_lit() { parse, "float[32] f = 1.0;", &expect![[r#" - Stmt [0-18] - StmtKind: ClassicalDeclarationStmt [0-18]: ClassicalType [0-9]: FloatType[Expr [6-8]: Lit: Int(32)]: [0-9], Ident [10-11] "f", ValueExpression Expr [14-17]: Lit: Float(1.0)"#]], + 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)"#]], ); } @@ -400,8 +566,13 @@ fn const_float_sized_decl_float_lit() { parse, "const float[32] f = 1;", &expect![[r#" - Stmt [0-22] - StmtKind: ConstantDeclaration [0-22]: ClassicalType [6-15]: FloatType[Expr [12-14]: Lit: Int(32)]: [6-15], Ident [16-17] "f", Expr [20-21]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -411,8 +582,13 @@ fn angle_decl() { parse, "angle a;", &expect![[r#" - Stmt [0-8] - StmtKind: ClassicalDeclarationStmt [0-8]: ClassicalType [0-5]: AngleType [0-5], Ident [6-7] "a""#]], + Stmt [0-8]: + annotations: + kind: ClassicalDeclarationStmt [0-8]: + type: ScalarType [0-5]: AngleType [0-5]: + size: + ident: Ident [6-7] "a" + init_expr: "#]], ); } @@ -422,8 +598,13 @@ fn angle_decl_angle_lit() { parse, "angle a = 1.0;", &expect![[r#" - Stmt [0-14] - StmtKind: ClassicalDeclarationStmt [0-14]: ClassicalType [0-5]: AngleType [0-5], Ident [6-7] "a", ValueExpression Expr [10-13]: Lit: Float(1.0)"#]], + 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)"#]], ); } @@ -453,8 +634,13 @@ fn const_angle_decl_angle_lit() { parse, "const angle a = 1.0;", &expect![[r#" - Stmt [0-20] - StmtKind: ConstantDeclaration [0-20]: ClassicalType [6-11]: AngleType [6-11], Ident [12-13] "a", Expr [16-19]: Lit: Float(1.0)"#]], + 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)"#]], ); } @@ -464,8 +650,13 @@ fn angle_sized_decl() { parse, "angle[32] a;", &expect![[r#" - Stmt [0-12] - StmtKind: ClassicalDeclarationStmt [0-12]: ClassicalType [0-9]: AngleType [0-9]: Expr [6-8]: Lit: Int(32), Ident [10-11] "a""#]], + 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: "#]], ); } @@ -475,8 +666,13 @@ fn angle_sized_decl_angle_lit() { parse, "angle[32] a = 1.0;", &expect![[r#" - Stmt [0-18] - StmtKind: ClassicalDeclarationStmt [0-18]: ClassicalType [0-9]: AngleType [0-9]: Expr [6-8]: Lit: Int(32), Ident [10-11] "a", ValueExpression Expr [14-17]: Lit: Float(1.0)"#]], + 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)"#]], ); } @@ -486,8 +682,13 @@ fn const_angle_sized_decl_angle_lit() { parse, "const angle[32] a = 1.0;", &expect![[r#" - Stmt [0-24] - StmtKind: ConstantDeclaration [0-24]: ClassicalType [6-15]: AngleType [6-15]: Expr [12-14]: Lit: Int(32), Ident [16-17] "a", Expr [20-23]: Lit: Float(1.0)"#]], + 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)"#]], ); } @@ -497,8 +698,12 @@ fn duration_decl() { parse, "duration d;", &expect![[r#" - Stmt [0-11] - StmtKind: ClassicalDeclarationStmt [0-11]: ClassicalType [0-8]: Duration, Ident [9-10] "d""#]], + Stmt [0-11]: + annotations: + kind: ClassicalDeclarationStmt [0-11]: + type: ScalarType [0-8]: Duration + ident: Ident [9-10] "d" + init_expr: "#]], ); } @@ -508,8 +713,12 @@ fn duration_decl_ns_lit() { parse, "duration d = 1000ns;", &expect![[r#" - Stmt [0-20] - StmtKind: ClassicalDeclarationStmt [0-20]: ClassicalType [0-8]: Duration, Ident [9-10] "d", ValueExpression Expr [13-19]: Lit: Duration(1000.0, Ns)"#]], + 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)"#]], ); } @@ -519,8 +728,12 @@ fn duration_decl_us_lit() { parse, "duration d = 1000us;", &expect![[r#" - Stmt [0-20] - StmtKind: ClassicalDeclarationStmt [0-20]: ClassicalType [0-8]: Duration, Ident [9-10] "d", ValueExpression Expr [13-19]: Lit: Duration(1000.0, Us)"#]], + 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)"#]], ); } @@ -532,8 +745,12 @@ fn duration_decl_uus_lit() { parse, "duration d = 1000µs;", &expect![[r#" - Stmt [0-21] - StmtKind: ClassicalDeclarationStmt [0-21]: ClassicalType [0-8]: Duration, Ident [9-10] "d", ValueExpression Expr [13-20]: Lit: Duration(1000.0, Us)"#]], + 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)"#]], ); } @@ -543,8 +760,12 @@ fn duration_decl_ms_lit() { parse, "duration d = 1000ms;", &expect![[r#" - Stmt [0-20] - StmtKind: ClassicalDeclarationStmt [0-20]: ClassicalType [0-8]: Duration, Ident [9-10] "d", ValueExpression Expr [13-19]: Lit: Duration(1000.0, Ms)"#]], + 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)"#]], ); } @@ -554,8 +775,12 @@ fn duration_decl_s_lit() { parse, "duration d = 1000s;", &expect![[r#" - Stmt [0-19] - StmtKind: ClassicalDeclarationStmt [0-19]: ClassicalType [0-8]: Duration, Ident [9-10] "d", ValueExpression Expr [13-18]: Lit: Duration(1000.0, S)"#]], + 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)"#]], ); } @@ -565,8 +790,12 @@ fn duration_decl_dt_lit() { parse, "duration d = 1000dt;", &expect![[r#" - Stmt [0-20] - StmtKind: ClassicalDeclarationStmt [0-20]: ClassicalType [0-8]: Duration, Ident [9-10] "d", ValueExpression Expr [13-19]: Lit: Duration(1000.0, Dt)"#]], + 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)"#]], ); } @@ -576,8 +805,12 @@ fn const_duration_decl_dt_lit() { parse, "const duration d = 10dt;", &expect![[r#" - Stmt [0-24] - StmtKind: ConstantDeclaration [0-24]: ClassicalType [6-14]: Duration, Ident [15-16] "d", Expr [19-23]: Lit: Duration(10.0, Dt)"#]], + 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)"#]], ); } @@ -587,8 +820,12 @@ fn stretch_decl() { parse, "stretch s;", &expect![[r#" - Stmt [0-10] - StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-7]: Stretch, Ident [8-9] "s""#]], + Stmt [0-10]: + annotations: + kind: ClassicalDeclarationStmt [0-10]: + type: ScalarType [0-7]: Stretch + ident: Ident [8-9] "s" + init_expr: "#]], ); } @@ -598,9 +835,16 @@ fn empty_array_decl() { parse, "array[int, 0] arr = {};", &expect![[r#" - Stmt [0-23] - StmtKind: ClassicalDeclarationStmt [0-23]: ArrayType [0-13]: ArrayBaseTypeKind IntType [6-9] - Expr [11-12]: Lit: Int(0), Ident [14-17] "arr", ValueExpression Expr [20-22]: Lit: Array:"#]], + 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: "#]], ); } @@ -610,12 +854,19 @@ fn simple_array_decl() { parse, "array[int[32], 3] arr = {1, 2, 3};", &expect![[r#" - Stmt [0-34] - StmtKind: ClassicalDeclarationStmt [0-34]: ArrayType [0-17]: ArrayBaseTypeKind IntType[Expr [10-12]: Lit: Int(32)]: [6-13] - Expr [15-16]: Lit: Int(3), Ident [18-21] "arr", ValueExpression Expr [24-33]: Lit: Array: - Expr { span: Span { lo: 25, hi: 26 }, kind: Lit(Lit { span: Span { lo: 25, hi: 26 }, kind: Int(1) }) } - Expr { span: Span { lo: 28, hi: 29 }, kind: Lit(Lit { span: Span { lo: 28, hi: 29 }, kind: Int(2) }) } - Expr { span: Span { lo: 31, hi: 32 }, kind: Lit(Lit { span: Span { lo: 31, hi: 32 }, kind: Int(3) }) }"#]], + 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)"#]], ); } @@ -625,13 +876,26 @@ fn nested_array_decl() { parse, "array[int[32], 3, 2] arr = {{1, 2}, {3, 4}, {5, 6}};", &expect![[r#" - Stmt [0-52] - StmtKind: ClassicalDeclarationStmt [0-52]: ArrayType [0-20]: ArrayBaseTypeKind IntType[Expr [10-12]: Lit: Int(32)]: [6-13] - Expr [15-16]: Lit: Int(3) - Expr [18-19]: Lit: Int(2), Ident [21-24] "arr", ValueExpression Expr [27-51]: Lit: Array: - Expr { span: Span { lo: 28, hi: 34 }, kind: Lit(Lit { span: Span { lo: 28, hi: 34 }, kind: Array([Expr { span: Span { lo: 29, hi: 30 }, kind: Lit(Lit { span: Span { lo: 29, hi: 30 }, kind: Int(1) }) }, Expr { span: Span { lo: 32, hi: 33 }, kind: Lit(Lit { span: Span { lo: 32, hi: 33 }, kind: Int(2) }) }]) }) } - Expr { span: Span { lo: 36, hi: 42 }, kind: Lit(Lit { span: Span { lo: 36, hi: 42 }, kind: Array([Expr { span: Span { lo: 37, hi: 38 }, kind: Lit(Lit { span: Span { lo: 37, hi: 38 }, kind: Int(3) }) }, Expr { span: Span { lo: 40, hi: 41 }, kind: Lit(Lit { span: Span { lo: 40, hi: 41 }, kind: Int(4) }) }]) }) } - Expr { span: Span { lo: 44, hi: 50 }, kind: Lit(Lit { span: Span { lo: 44, hi: 50 }, kind: Array([Expr { span: Span { lo: 45, hi: 46 }, kind: Lit(Lit { span: Span { lo: 45, hi: 46 }, kind: Int(5) }) }, Expr { span: Span { lo: 48, hi: 49 }, kind: Lit(Lit { span: Span { lo: 48, hi: 49 }, kind: Int(6) }) }]) }) }"#]], + 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)"#]], ); } @@ -641,8 +905,14 @@ fn measure_hardware_qubit_decl() { parse, "bit res = measure $12;", &expect![[r#" - Stmt [0-22] - StmtKind: ClassicalDeclarationStmt [0-22]: ClassicalType [0-3]: BitType, Ident [4-7] "res", ValueExpression MeasureExpr [10-17]: GateOperand HardwareQubit [18-21]: 12"#]], + 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-17]: + operand: HardwareQubit [18-21]: 12"#]], ); } @@ -652,11 +922,21 @@ fn measure_register_decl() { parse, "bit res = measure qubits[2][3];", &expect![[r#" - Stmt [0-31] - StmtKind: ClassicalDeclarationStmt [0-31]: ClassicalType [0-3]: BitType, Ident [4-7] "res", ValueExpression MeasureExpr [10-17]: GateOperand IndexedIdent [18-30]: Ident [18-24] "qubits"[ - IndexElement: - IndexSetItem Expr [25-26]: Lit: Int(2) - IndexElement: - IndexSetItem Expr [28-29]: Lit: Int(3)]"#]], + 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-17]: + operand: IndexedIdent [18-30]: + name: Ident [18-24] "qubits" + indices: + IndexSet [25-26]: + values: + Expr [25-26]: Lit: Int(2) + IndexSet [28-29]: + values: + Expr [28-29]: Lit: Int(3)"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs index d9f82d4b77..d257821433 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/def.rs @@ -13,8 +13,13 @@ fn minimal() { parse, "def x() { }", &expect![[r#" - Stmt [0-11] - StmtKind: DefStmt [0-11]: Ident [4-5] "x"() "#]], + Stmt [0-11]: + annotations: + kind: DefStmt [0-11]: + ident: Ident [4-5] "x" + parameters: + return_type: + body: Block [8-11]: "#]], ); } @@ -46,8 +51,16 @@ fn missing_args_with_delim_error() { parse, "def x(,) { }", &expect![[r#" - Stmt [0-12] - StmtKind: DefStmt [0-12]: Ident [4-5] "x"([6-6] Ident [0-0] "": ClassicalType [0-0]: Err) + 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( @@ -68,8 +81,24 @@ fn args_with_extra_delim_err_ty() { parse, "def x(int a,,int b) { }", &expect![[r#" - Stmt [0-23] - StmtKind: DefStmt [0-23]: Ident [4-5] "x"([6-11] Ident [10-11] "a": ClassicalType [6-9]: IntType [6-9], [12-12] Ident [0-0] "": ClassicalType [0-0]: Err, [13-18] Ident [17-18] "b": ClassicalType [13-16]: IntType [13-16]) + 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( @@ -90,13 +119,25 @@ fn classical_subroutine() { parse, "def square(int[32] x) -> int { return x ** 2; }", &expect![[r#" - Stmt [0-47] - StmtKind: DefStmt [0-47]: Ident [4-10] "square"([11-20] Ident [19-20] "x": ClassicalType [11-18]: IntType[Expr [15-17]: Lit: Int(32)]: [11-18]) - Stmt [31-45] - StmtKind: ReturnStmt [31-45]: ValueExpression Expr [38-44]: BinOp (Exp): - Expr [38-39]: Ident [38-39] "x" - Expr [43-44]: Lit: Int(2) - ClassicalType [25-28]: IntType [25-28]"#]], + 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)"#]], ); } @@ -106,8 +147,19 @@ fn quantum_args() { parse, "def x(qubit q, qubit[n] qubits) { }", &expect![[r#" - Stmt [0-35] - StmtKind: DefStmt [0-35]: Ident [4-5] "x"([6-13] Ident [12-13] "q": qubit, [15-30] Ident [24-30] "qubits": qubit[Expr [21-22]: Ident [21-22] "n"]) "#]], + 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]: "#]], ); } @@ -117,13 +169,35 @@ fn old_style_args() { parse, "def test(creg c, qreg q, creg c2[2], qreg q4[4]) -> int { return x ** 2; }", &expect![[r#" - Stmt [0-74] - StmtKind: DefStmt [0-74]: Ident [4-8] "test"([9-15] Ident [14-15] "c": ClassicalType [9-15]: BitType, [17-23] Ident [22-23] "q": qubit, [25-35] Ident [30-32] "c2": ClassicalType [25-35]: BitType [25-35]: Expr [33-34]: Lit: Int(2), [37-47] Ident [42-44] "q4": qubit[Expr [45-46]: Lit: Int(4)]) - Stmt [58-72] - StmtKind: ReturnStmt [58-72]: ValueExpression Expr [65-71]: BinOp (Exp): - Expr [65-66]: Ident [65-66] "x" - Expr [70-71]: Lit: Int(2) - ClassicalType [52-55]: IntType [52-55]"#]], + 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)"#]], ); } @@ -133,10 +207,23 @@ fn readonly_array_arg_with_int_dims() { parse, "def specified_sub(readonly array[int[8], 2, 10] arr_arg) {}", &expect![[r#" - Stmt [0-59] - StmtKind: DefStmt [0-59]: Ident [4-17] "specified_sub"([18-55] Ident [48-55] "arr_arg": ArrayReferenceType [18-47]: ArrayBaseTypeKind IntType[Expr [37-38]: Lit: Int(8)]: [33-39] - Expr [41-42]: Lit: Int(2) - Expr [44-46]: Lit: Int(10)) "#]], + 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]: "#]], ); } @@ -146,9 +233,22 @@ fn readonly_array_arg_with_dim() { parse, "def arr_subroutine(readonly array[int[8], #dim = 1] arr_arg) {}", &expect![[r#" - Stmt [0-63] - StmtKind: DefStmt [0-63]: Ident [4-18] "arr_subroutine"([19-59] Ident [52-59] "arr_arg": ArrayReferenceType [19-51]: ArrayBaseTypeKind IntType[Expr [38-39]: Lit: Int(8)]: [34-40] - Expr [49-50]: Lit: Int(1)) "#]], + 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]: "#]], ); } @@ -158,8 +258,21 @@ fn mutable_array_arg() { parse, "def mut_subroutine(mutable array[int[8], #dim = 1] arr_arg) {}", &expect![[r#" - Stmt [0-62] - StmtKind: DefStmt [0-62]: Ident [4-18] "mut_subroutine"([19-58] Ident [51-58] "arr_arg": ArrayReferenceType [19-50]: ArrayBaseTypeKind IntType[Expr [37-38]: Lit: Int(8)]: [33-39] - Expr [48-49]: Lit: Int(1)) "#]], + 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 index 986f833810..b086ea2e68 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/defcal.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/defcal.rs @@ -38,7 +38,8 @@ fn cal_block_accept_any_tokens_inside() { .314 }", &expect![[r#" - Stmt [5-88] - StmtKind: DefCalStmt [5-88]"#]], + 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 index 463ba062f0..2fb62a4e1f 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs @@ -11,13 +11,22 @@ fn delay() { parse, "delay[a] q[0], q[1];", &expect![[r#" - Stmt [0-20] - StmtKind: DelayInstruction [0-20]: Expr [6-7]: Ident [6-7] "a" - GateOperand IndexedIdent [9-13]: Ident [9-10] "q"[ - IndexElement: - IndexSetItem Expr [11-12]: Lit: Int(0)] - GateOperand IndexedIdent [15-19]: Ident [15-16] "q"[ - IndexElement: - IndexSetItem Expr [17-18]: Lit: Int(1)]"#]], + Stmt [0-20]: + annotations: + kind: DelayStmt [0-20]: + duration: Expr [6-7]: Ident [6-7] "a" + qubits: + IndexedIdent [9-13]: + name: Ident [9-10] "q" + indices: + IndexSet [11-12]: + values: + Expr [11-12]: Lit: Int(0) + IndexedIdent [15-19]: + name: Ident [15-16] "q" + 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 index f5a5082ad7..d5f55416e4 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs @@ -11,8 +11,10 @@ fn identifier() { parse, "H;", &expect![[r#" - Stmt [0-2] - StmtKind: ExprStmt [0-2]: Expr [0-1]: Ident [0-1] "H""#]], + Stmt [0-2]: + annotations: + kind: ExprStmt [0-2]: + expr: Expr [0-1]: Ident [0-1] "H""#]], ); } @@ -22,10 +24,13 @@ fn identifier_plus_number() { parse, "H + 2;", &expect![[r#" - Stmt [0-6] - StmtKind: ExprStmt [0-6]: Expr [0-5]: BinOp (Add): - Expr [0-1]: Ident [0-1] "H" - Expr [4-5]: Lit: Int(2)"#]], + 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)"#]], ); } @@ -37,12 +42,17 @@ fn function_call_plus_ident() { parse, "Name(2, 3) + a;", &expect![[r#" - Stmt [0-15] - StmtKind: ExprStmt [0-15]: Expr [0-14]: BinOp (Add): - Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" - Expr [5-6]: Lit: Int(2) - Expr [8-9]: Lit: Int(3) - Expr [13-14]: Ident [13-14] "a""#]], + 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""#]], ); } @@ -52,10 +62,14 @@ fn function_call() { parse, "Name(2, 3);", &expect![[r#" - Stmt [0-11] - StmtKind: ExprStmt [0-11]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" - Expr [5-6]: Lit: Int(2) - Expr [8-9]: Lit: Int(3)"#]], + 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)"#]], ); } @@ -65,11 +79,18 @@ fn indexed_function_call() { parse, "Name(2, 3)[1];", &expect![[r#" - Stmt [0-14] - StmtKind: ExprStmt [0-14]: Expr [0-13]: IndexExpr [10-13]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" - Expr [5-6]: Lit: Int(2) - Expr [8-9]: Lit: Int(3), IndexElement: - IndexSetItem Expr [11-12]: Lit: Int(1)"#]], + Stmt [0-14]: + annotations: + kind: ExprStmt [0-14]: + expr: Expr [0-13]: IndexExpr [10-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)"#]], ); } @@ -79,12 +100,19 @@ fn multi_indexed_function_call() { parse, "Name(2, 3)[1, 0];", &expect![[r#" - Stmt [0-17] - StmtKind: ExprStmt [0-17]: Expr [0-16]: IndexExpr [10-16]: Expr [0-10]: FunctionCall [0-10]: Ident [0-4] "Name" - Expr [5-6]: Lit: Int(2) - Expr [8-9]: Lit: Int(3), IndexElement: - IndexSetItem Expr [11-12]: Lit: Int(1) - IndexSetItem Expr [14-15]: Lit: Int(0)"#]], + Stmt [0-17]: + annotations: + kind: ExprStmt [0-17]: + expr: Expr [0-16]: IndexExpr [10-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)"#]], ); } @@ -94,8 +122,10 @@ fn ident() { parse, "Name;", &expect![[r#" - Stmt [0-5] - StmtKind: ExprStmt [0-5]: Expr [0-4]: Ident [0-4] "Name""#]], + Stmt [0-5]: + annotations: + kind: ExprStmt [0-5]: + expr: Expr [0-4]: Ident [0-4] "Name""#]], ); } @@ -105,9 +135,14 @@ fn index_expr() { parse, "Name[1];", &expect![[r#" - Stmt [0-8] - StmtKind: ExprStmt [0-8]: Expr [0-7]: IndexExpr [4-7]: Expr [0-4]: Ident [0-4] "Name", IndexElement: - IndexSetItem Expr [5-6]: Lit: Int(1)"#]], + Stmt [0-8]: + annotations: + kind: ExprStmt [0-8]: + expr: Expr [0-7]: IndexExpr [4-7]: + collection: Expr [0-4]: Ident [0-4] "Name" + index: IndexSet [5-6]: + values: + Expr [5-6]: Lit: Int(1)"#]], ); } @@ -117,10 +152,13 @@ fn cast_expr() { parse, "bit(0);", &expect![[r#" - Stmt [0-7] - StmtKind: ExprStmt [0-7]: Expr [0-6]: Cast [0-6]: - ClassicalType [0-3]: BitType - Expr [4-5]: Lit: Int(0)"#]], + 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)"#]], ); } @@ -130,9 +168,12 @@ fn cast_expr_with_designator() { parse, "bit[45](0);", &expect![[r#" - Stmt [0-11] - StmtKind: ExprStmt [0-11]: Expr [0-10]: Cast [0-10]: - ClassicalType [0-7]: BitType [0-7]: Expr [4-6]: Lit: Int(45) - Expr [8-9]: Lit: Int(0)"#]], + 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 index 2771da1428..6022cf83f1 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/extern_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/extern_decl.rs @@ -13,8 +13,12 @@ fn missing_semicolon_err() { parse, "extern x()", &expect![[r#" - Stmt [0-10] - StmtKind: ExternDecl [0-10]: Ident [7-8] "x" + Stmt [0-10]: + annotations: + kind: ExternDecl [0-10]: + ident: Ident [7-8] "x" + parameters: + return_type: [ Error( @@ -37,10 +41,15 @@ fn bit_param_bit_ret_decl() { parse, "extern x(bit) -> bit;", &expect![[r#" - Stmt [0-21] - StmtKind: ExternDecl [0-21]: Ident [7-8] "x" - [9-12]: ClassicalType [9-12]: BitType - ClassicalType [17-20]: BitType"#]], + 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: "#]], ); } @@ -50,10 +59,15 @@ fn sized_bit_param_bit_ret_decl() { parse, "extern x(bit[n]) -> bit;", &expect![[r#" - Stmt [0-24] - StmtKind: ExternDecl [0-24]: Ident [7-8] "x" - [9-15]: ClassicalType [9-15]: BitType [9-15]: Expr [13-14]: Ident [13-14] "n" - ClassicalType [20-23]: BitType"#]], + 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: "#]], ); } @@ -63,10 +77,15 @@ fn sized_creg_param_bit_ret_decl() { parse, "extern x(creg[n]) -> bit;", &expect![[r#" - Stmt [0-25] - StmtKind: ExternDecl [0-25]: Ident [7-8] "x" - [9-16]: ClassicalType [9-16]: BitType [9-16]: Expr [14-15]: Ident [14-15] "n" - ClassicalType [21-24]: BitType"#]], + 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: "#]], ); } @@ -76,10 +95,15 @@ fn creg_param_bit_ret_decl() { parse, "extern x(creg) -> bit;", &expect![[r#" - Stmt [0-22] - StmtKind: ExternDecl [0-22]: Ident [7-8] "x" - [9-13]: ClassicalType [9-13]: BitType - ClassicalType [18-21]: BitType"#]], + 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: "#]], ); } @@ -89,11 +113,20 @@ fn readonly_array_arg_with_int_dims() { parse, "extern x(readonly array[int[8], 2, 10]);", &expect![[r#" - Stmt [0-40] - StmtKind: ExternDecl [0-40]: Ident [7-8] "x" - [9-38]: ArrayReferenceType [9-38]: ArrayBaseTypeKind IntType[Expr [28-29]: Lit: Int(8)]: [24-30] - Expr [32-33]: Lit: Int(2) - Expr [35-37]: Lit: Int(10)"#]], + 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: "#]], ); } @@ -103,10 +136,19 @@ fn readonly_array_arg_with_dim() { parse, "extern x(readonly array[int[8], #dim = 1]);", &expect![[r#" - Stmt [0-43] - StmtKind: ExternDecl [0-43]: Ident [7-8] "x" - [9-41]: ArrayReferenceType [9-41]: ArrayBaseTypeKind IntType[Expr [28-29]: Lit: Int(8)]: [24-30] - Expr [39-40]: Lit: Int(1)"#]], + 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: "#]], ); } @@ -116,10 +158,19 @@ fn mutable_array_arg() { parse, "extern x(mutable array[int[8], #dim = 1]);", &expect![[r#" - Stmt [0-42] - StmtKind: ExternDecl [0-42]: Ident [7-8] "x" - [9-40]: ArrayReferenceType [9-40]: ArrayBaseTypeKind IntType[Expr [27-28]: Lit: Int(8)]: [23-29] - Expr [38-39]: Lit: Int(1)"#]], + 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: "#]], ); } @@ -152,11 +203,18 @@ fn annotation() { r#"@test.annotation extern x(creg) -> bit;"#, &expect![[r#" - Stmt [0-47] - Annotation [0-16]: (test.annotation) - StmtKind: ExternDecl [25-47]: Ident [32-33] "x" - [34-38]: ClassicalType [34-38]: BitType - ClassicalType [43-46]: BitType"#]], + 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: "#]], ); } @@ -186,9 +244,13 @@ fn missing_args_with_delim_error() { parse, "extern x(,);", &expect![[r#" - Stmt [0-12] - StmtKind: ExternDecl [0-12]: Ident [7-8] "x" - [9-9]: ClassicalType [0-0]: Err + Stmt [0-12]: + annotations: + kind: ExternDecl [0-12]: + ident: Ident [7-8] "x" + parameters: + [9-9]: ScalarType [0-0]: Err + return_type: [ Error( @@ -209,11 +271,17 @@ fn args_with_extra_delim_err_ty() { parse, "extern x(int,,int);", &expect![[r#" - Stmt [0-19] - StmtKind: ExternDecl [0-19]: Ident [7-8] "x" - [9-12]: ClassicalType [9-12]: IntType [9-12] - [13-13]: ClassicalType [0-0]: Err - [14-17]: ClassicalType [14-17]: IntType [14-17] + 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( diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs index 8ce396ef6b..8d6bf920b7 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs @@ -13,15 +13,42 @@ fn simple_for_loop() { a = 0; }", &expect![[r#" - Stmt [5-50] - StmtKind: ForStmt [5-50]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: - Expr [19-20]: Lit: Int(1) - Expr [22-23]: Lit: Int(2) - Expr [25-26]: Lit: Int(3) - Stmt [38-44] - StmtKind: ExprStmt [38-44]: Expr [38-43]: Assign: - Expr [38-39]: Ident [38-39] "a" - Expr [42-43]: Lit: Int(0)"#]], + 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) + block: + Stmt [38-44]: + annotations: + kind: ExprStmt [38-44]: + expr: Expr [38-43]: AssignExpr: + lhs: Expr [38-39]: Ident [38-39] "a" + rhs: Expr [42-43]: Lit: Int(0)"#]], + ); +} + +#[test] +fn empty_for_loop() { + 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: + block: "#]], ); } @@ -34,15 +61,24 @@ fn simple_for_loop_stmt_body() { a = 0; ", &expect![[r#" - Stmt [5-42] - StmtKind: ForStmt [5-42]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: - Expr [19-20]: Lit: Int(1) - Expr [22-23]: Lit: Int(2) - Expr [25-26]: Lit: Int(3) - Stmt [36-42] - StmtKind: ExprStmt [36-42]: Expr [36-41]: Assign: - Expr [36-37]: Ident [36-37] "a" - Expr [40-41]: Lit: Int(0)"#]], + 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) + block: + Stmt [36-42]: + annotations: + kind: ExprStmt [36-42]: + expr: Expr [36-41]: AssignExpr: + lhs: Expr [36-37]: Ident [36-37] "a" + rhs: Expr [40-41]: Lit: Int(0)"#]], ); } @@ -55,15 +91,23 @@ fn for_loop_range() { a = 0; }", &expect![[r#" - Stmt [5-48] - StmtKind: ForStmt [5-48]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Range: [18-25] - start: Expr [19-20]: Lit: Int(0) - step: Expr [21-22]: Lit: Int(2) - end: Expr [23-24]: Lit: Int(7) - Stmt [36-42] - StmtKind: ExprStmt [36-42]: Expr [36-41]: Assign: - Expr [36-37]: Ident [36-37] "a" - Expr [40-41]: Lit: Int(0)"#]], + 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) + block: + Stmt [36-42]: + annotations: + kind: ExprStmt [36-42]: + expr: Expr [36-41]: AssignExpr: + lhs: Expr [36-37]: Ident [36-37] "a" + rhs: Expr [40-41]: Lit: Int(0)"#]], ); } @@ -76,15 +120,23 @@ fn for_loop_range_no_step() { a = 0; }", &expect![[r#" - Stmt [5-46] - StmtKind: ForStmt [5-46]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Range: [18-23] - start: Expr [19-20]: Lit: Int(0) - - end: Expr [21-22]: Lit: Int(7) - Stmt [34-40] - StmtKind: ExprStmt [34-40]: Expr [34-39]: Assign: - Expr [34-35]: Ident [34-35] "a" - Expr [38-39]: Lit: Int(0)"#]], + 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) + block: + Stmt [34-40]: + annotations: + kind: ExprStmt [34-40]: + expr: Expr [34-39]: AssignExpr: + lhs: Expr [34-35]: Ident [34-35] "a" + rhs: Expr [38-39]: Lit: Int(0)"#]], ); } @@ -97,12 +149,20 @@ fn for_loop_expr() { a = 0; }", &expect![[r#" - Stmt [5-43] - StmtKind: ForStmt [5-43]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", Expr [18-20]: Ident [18-20] "xs" - Stmt [31-37] - StmtKind: ExprStmt [31-37]: Expr [31-36]: Assign: - Expr [31-32]: Ident [31-32] "a" - Expr [35-36]: Lit: Int(0)"#]], + 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" + block: + Stmt [31-37]: + annotations: + kind: ExprStmt [31-37]: + expr: Expr [31-36]: AssignExpr: + lhs: Expr [31-32]: Ident [31-32] "a" + rhs: Expr [35-36]: Lit: Int(0)"#]], ); } @@ -116,17 +176,27 @@ fn for_loop_with_continue_stmt() { continue; }", &expect![[r#" - Stmt [5-68] - StmtKind: ForStmt [5-68]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: - Expr [19-20]: Lit: Int(1) - Expr [22-23]: Lit: Int(2) - Expr [25-26]: Lit: Int(3) - Stmt [38-44] - StmtKind: ExprStmt [38-44]: Expr [38-43]: Assign: - Expr [38-39]: Ident [38-39] "a" - Expr [42-43]: Lit: Int(0) - Stmt [53-62] - StmtKind: Continue [53-62]"#]], + 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) + block: + Stmt [38-44]: + annotations: + kind: ExprStmt [38-44]: + expr: Expr [38-43]: AssignExpr: + lhs: Expr [38-39]: Ident [38-39] "a" + rhs: Expr [42-43]: Lit: Int(0) + Stmt [53-62]: + annotations: + kind: ContinueStmt [53-62]"#]], ); } @@ -140,16 +210,26 @@ fn for_loop_with_break_stmt() { break; }", &expect![[r#" - Stmt [5-65] - StmtKind: ForStmt [5-65]: ClassicalType [9-12]: IntType [9-12], Ident [13-14] "x", DiscreteSet [18-27]: - Expr [19-20]: Lit: Int(1) - Expr [22-23]: Lit: Int(2) - Expr [25-26]: Lit: Int(3) - Stmt [38-44] - StmtKind: ExprStmt [38-44]: Expr [38-43]: Assign: - Expr [38-39]: Ident [38-39] "a" - Expr [42-43]: Lit: Int(0) - Stmt [53-59] - StmtKind: Break [53-59]"#]], + 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) + block: + Stmt [38-44]: + annotations: + kind: ExprStmt [38-44]: + expr: Expr [38-43]: AssignExpr: + lhs: Expr [38-39]: Ident [38-39] "a" + rhs: Expr [42-43]: Lit: Int(0) + Stmt [53-59]: + annotations: + kind: BreakStmt [53-59]"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs index 98af770cd0..03e4a43334 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs @@ -11,9 +11,17 @@ fn gate_call() { parse, "H q0;", &expect![[r#" - Stmt [0-5] - StmtKind: GateCall [0-5]: Ident [0-1] "H" - GateOperand IndexedIdent [2-4]: Ident [2-4] "q0"[]"#]], + Stmt [0-5]: + annotations: + kind: GateCall [0-5]: + modifiers: + name: Ident [0-1] "H" + args: + duration: + qubits: + IndexedIdent [2-4]: + name: Ident [2-4] "q0" + indices: "#]], ); } @@ -23,11 +31,20 @@ fn gate_call_qubit_register() { parse, "H q[2];", &expect![[r#" - Stmt [0-7] - StmtKind: GateCall [0-7]: Ident [0-1] "H" - GateOperand IndexedIdent [2-6]: Ident [2-3] "q"[ - IndexElement: - IndexSetItem Expr [4-5]: Lit: Int(2)]"#]], + Stmt [0-7]: + annotations: + kind: GateCall [0-7]: + modifiers: + name: Ident [0-1] "H" + args: + duration: + qubits: + IndexedIdent [2-6]: + name: Ident [2-3] "q" + indices: + IndexSet [4-5]: + values: + Expr [4-5]: Lit: Int(2)"#]], ); } @@ -37,12 +54,23 @@ fn gate_multiple_qubits() { parse, "CNOT q0, q[4];", &expect![[r#" - Stmt [0-14] - StmtKind: GateCall [0-14]: Ident [0-4] "CNOT" - GateOperand IndexedIdent [5-7]: Ident [5-7] "q0"[] - GateOperand IndexedIdent [9-13]: Ident [9-10] "q"[ - IndexElement: - IndexSetItem Expr [11-12]: Lit: Int(4)]"#]], + Stmt [0-14]: + annotations: + kind: GateCall [0-14]: + modifiers: + name: Ident [0-4] "CNOT" + args: + duration: + qubits: + IndexedIdent [5-7]: + name: Ident [5-7] "q0" + indices: + IndexedIdent [9-13]: + name: Ident [9-10] "q" + indices: + IndexSet [11-12]: + values: + Expr [11-12]: Lit: Int(4)"#]], ); } @@ -52,8 +80,15 @@ fn gate_with_no_qubits() { parse, "inv @ H;", &expect![[r#" - Stmt [0-8] - StmtKind: GateCall [0-8]: Ident [6-7] "H" + Stmt [0-8]: + annotations: + kind: GateCall [0-8]: + modifiers: + QuantumGateModifier [0-5]: Inv + name: Ident [6-7] "H" + args: + duration: + qubits: [ Error( @@ -74,12 +109,21 @@ fn gate_call_with_parameters() { parse, "Rx(pi / 2) q0;", &expect![[r#" - Stmt [0-14] - StmtKind: GateCall [0-14]: Ident [0-2] "Rx" - Expr [3-9]: BinOp (Div): - Expr [3-5]: Ident [3-5] "pi" - Expr [8-9]: Lit: Int(2) - GateOperand IndexedIdent [11-13]: Ident [11-13] "q0"[]"#]], + 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: + IndexedIdent [11-13]: + name: Ident [11-13] "q0" + indices: "#]], ); } @@ -89,9 +133,18 @@ fn gate_call_inv_modifier() { parse, "inv @ H q0;", &expect![[r#" - Stmt [0-11] - StmtKind: GateCall [0-11]: Ident [6-7] "H" - GateOperand IndexedIdent [8-10]: Ident [8-10] "q0"[]"#]], + Stmt [0-11]: + annotations: + kind: GateCall [0-11]: + modifiers: + QuantumGateModifier [0-5]: Inv + name: Ident [6-7] "H" + args: + duration: + qubits: + IndexedIdent [8-10]: + name: Ident [8-10] "q0" + indices: "#]], ); } @@ -101,14 +154,29 @@ fn gate_call_ctrl_inv_modifiers() { parse, "ctrl(2) @ inv @ Rx(pi / 2) c1, c2, q0;", &expect![[r#" - Stmt [0-38] - StmtKind: GateCall [0-38]: Ident [16-18] "Rx" - Expr [19-25]: BinOp (Div): - Expr [19-21]: Ident [19-21] "pi" - Expr [24-25]: Lit: Int(2) - GateOperand IndexedIdent [27-29]: Ident [27-29] "c1"[] - GateOperand IndexedIdent [31-33]: Ident [31-33] "c2"[] - GateOperand IndexedIdent [35-37]: Ident [35-37] "q0"[]"#]], + 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: + IndexedIdent [27-29]: + name: Ident [27-29] "c1" + indices: + IndexedIdent [31-33]: + name: Ident [31-33] "c2" + indices: + IndexedIdent [35-37]: + name: Ident [35-37] "q0" + indices: "#]], ); } @@ -137,11 +205,19 @@ fn parametrized_gate_call() { parse, "Name(2, 3) q;", &expect![[r#" - Stmt [0-13] - StmtKind: GateCall [0-13]: Ident [0-4] "Name" - Expr [5-6]: Lit: Int(2) - Expr [8-9]: Lit: Int(3) - GateOperand IndexedIdent [11-12]: Ident [11-12] "q"[]"#]], + 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: + IndexedIdent [11-12]: + name: Ident [11-12] "q" + indices: "#]], ); } @@ -151,12 +227,19 @@ fn parametrized_gate_call_with_designator() { parse, "Name(2, 3)[1] q;", &expect![[r#" - Stmt [0-16] - StmtKind: GateCall [0-16]: Ident [0-4] "Name" - Expr [5-6]: Lit: Int(2) - Expr [8-9]: Lit: Int(3) - GateOperand IndexedIdent [14-15]: Ident [14-15] "q"[] - Expr [11-12]: Lit: Int(1)"#]], + 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: + IndexedIdent [14-15]: + name: Ident [14-15] "q" + indices: "#]], ); } @@ -184,10 +267,17 @@ fn gate_call_with_designator() { parse, "H[2us] q;", &expect![[r#" - Stmt [0-9] - StmtKind: GateCall [0-9]: Ident [0-1] "H" - GateOperand IndexedIdent [7-8]: Ident [7-8] "q"[] - Expr [2-5]: Lit: Duration(2.0, Us)"#]], + 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: + IndexedIdent [7-8]: + name: Ident [7-8] "q" + indices: "#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_def.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_def.rs index a45c07effa..d54e31d86a 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_def.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_def.rs @@ -13,9 +13,13 @@ fn no_qubits_no_classical() { parse, "gate c0q0 {}", &expect![[r#" - Stmt [0-12] - StmtKind: Gate [0-12]: Ident [5-9] "c0q0"() - "#]], + Stmt [0-12]: + annotations: + kind: Gate [0-12]: + ident: Ident [5-9] "c0q0" + parameters: + qubits: + body: Block [10-12]: "#]], ); } @@ -25,9 +29,13 @@ fn no_qubits_no_classical_with_parens() { parse, "gate c0q0() {}", &expect![[r#" - Stmt [0-14] - StmtKind: Gate [0-14]: Ident [5-9] "c0q0"() - "#]], + Stmt [0-14]: + annotations: + kind: Gate [0-14]: + ident: Ident [5-9] "c0q0" + parameters: + qubits: + body: Block [12-14]: "#]], ); } @@ -37,9 +45,14 @@ fn one_qubit_no_classical() { parse, "gate c0q1 a {}", &expect![[r#" - Stmt [0-14] - StmtKind: Gate [0-14]: Ident [5-9] "c0q1"() Ident [10-11] "a" - "#]], + Stmt [0-14]: + annotations: + kind: Gate [0-14]: + ident: Ident [5-9] "c0q1" + parameters: + qubits: + Ident [10-11] "a" + body: Block [12-14]: "#]], ); } @@ -49,9 +62,15 @@ fn two_qubits_no_classical() { parse, "gate c0q2 a, b {}", &expect![[r#" - Stmt [0-17] - StmtKind: Gate [0-17]: Ident [5-9] "c0q2"() Ident [10-11] "a", Ident [13-14] "b" - "#]], + 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]: "#]], ); } @@ -61,9 +80,16 @@ fn three_qubits_trailing_comma_no_classical() { parse, "gate c0q3 a, b, c, {}", &expect![[r#" - Stmt [0-21] - StmtKind: Gate [0-21]: Ident [5-9] "c0q3"() Ident [10-11] "a", Ident [13-14] "b", Ident [16-17] "c" - "#]], + 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]: "#]], ); } @@ -73,9 +99,14 @@ fn no_qubits_one_classical() { parse, "gate c1q0(a) {}", &expect![[r#" - Stmt [0-15] - StmtKind: Gate [0-15]: Ident [5-9] "c1q0"(Ident [10-11] "a") - "#]], + Stmt [0-15]: + annotations: + kind: Gate [0-15]: + ident: Ident [5-9] "c1q0" + parameters: + Ident [10-11] "a" + qubits: + body: Block [13-15]: "#]], ); } @@ -85,9 +116,15 @@ fn no_qubits_two_classical() { parse, "gate c2q0(a, b) {}", &expect![[r#" - Stmt [0-18] - StmtKind: Gate [0-18]: Ident [5-9] "c2q0"(Ident [10-11] "a", Ident [13-14] "b") - "#]], + 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]: "#]], ); } @@ -97,9 +134,16 @@ fn no_qubits_three_classical() { parse, "gate c3q0(a, b, c) {}", &expect![[r#" - Stmt [0-21] - StmtKind: Gate [0-21]: Ident [5-9] "c3q0"(Ident [10-11] "a", Ident [13-14] "b", Ident [16-17] "c") - "#]], + 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]: "#]], ); } @@ -109,9 +153,15 @@ fn one_qubit_one_classical() { parse, "gate c1q1(a) b {}", &expect![[r#" - Stmt [0-17] - StmtKind: Gate [0-17]: Ident [5-9] "c1q1"(Ident [10-11] "a") Ident [13-14] "b" - "#]], + 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]: "#]], ); } @@ -121,9 +171,17 @@ fn two_qubits_two_classical() { parse, "gate c2q2(a, b) c, d {}", &expect![[r#" - Stmt [0-23] - StmtKind: Gate [0-23]: Ident [5-9] "c2q2"(Ident [10-11] "a", Ident [13-14] "b") Ident [16-17] "c", Ident [19-20] "d" - "#]], + 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]: "#]], ); } @@ -133,12 +191,26 @@ fn two_qubits_two_classical_with_body() { parse, "gate c2q2(a, b) c, d { float[32] x = a - b; }", &expect![[r#" - Stmt [0-45] - StmtKind: Gate [0-45]: Ident [5-9] "c2q2"(Ident [10-11] "a", Ident [13-14] "b") Ident [16-17] "c", Ident [19-20] "d" - - Stmt [23-43] - StmtKind: ClassicalDeclarationStmt [23-43]: ClassicalType [23-32]: FloatType[Expr [29-31]: Lit: Int(32)]: [23-32], Ident [33-34] "x", ValueExpression Expr [37-42]: BinOp (Sub): - Expr [37-38]: Ident [37-38] "a" - Expr [41-42]: Ident [41-42] "b""#]], + 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 index 773611b83c..6b3278918d 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs @@ -11,11 +11,17 @@ fn gphase() { parse, "gphase(pi / 2);", &expect![[r#" - Stmt [0-15] - StmtKind: GPhase [0-15]: - Expr [7-13]: BinOp (Div): - Expr [7-9]: Ident [7-9] "pi" - Expr [12-13]: Lit: Int(2)"#]], + Stmt [0-15]: + annotations: + kind: GPhase [0-15]: + 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: "#]], ); } @@ -25,10 +31,17 @@ fn gphase_qubit_ident() { parse, "gphase(a) q0;", &expect![[r#" - Stmt [0-13] - StmtKind: GPhase [0-13]: - Expr [7-8]: Ident [7-8] "a" - GateOperand IndexedIdent [10-12]: Ident [10-12] "q0"[]"#]], + Stmt [0-13]: + annotations: + kind: GPhase [0-13]: + modifiers: + args: + Expr [7-8]: Ident [7-8] "a" + duration: + qubits: + IndexedIdent [10-12]: + name: Ident [10-12] "q0" + indices: "#]], ); } @@ -38,12 +51,20 @@ fn gphase_qubit_register() { parse, "gphase(a) q[2];", &expect![[r#" - Stmt [0-15] - StmtKind: GPhase [0-15]: - Expr [7-8]: Ident [7-8] "a" - GateOperand IndexedIdent [10-14]: Ident [10-11] "q"[ - IndexElement: - IndexSetItem Expr [12-13]: Lit: Int(2)]"#]], + Stmt [0-15]: + annotations: + kind: GPhase [0-15]: + modifiers: + args: + Expr [7-8]: Ident [7-8] "a" + duration: + qubits: + IndexedIdent [10-14]: + name: Ident [10-11] "q" + indices: + IndexSet [12-13]: + values: + Expr [12-13]: Lit: Int(2)"#]], ); } @@ -53,13 +74,23 @@ fn gphase_multiple_qubits() { parse, "gphase(a) q0, q[4];", &expect![[r#" - Stmt [0-19] - StmtKind: GPhase [0-19]: - Expr [7-8]: Ident [7-8] "a" - GateOperand IndexedIdent [10-12]: Ident [10-12] "q0"[] - GateOperand IndexedIdent [14-18]: Ident [14-15] "q"[ - IndexElement: - IndexSetItem Expr [16-17]: Lit: Int(4)]"#]], + Stmt [0-19]: + annotations: + kind: GPhase [0-19]: + modifiers: + args: + Expr [7-8]: Ident [7-8] "a" + duration: + qubits: + IndexedIdent [10-12]: + name: Ident [10-12] "q0" + indices: + IndexedIdent [14-18]: + name: Ident [14-15] "q" + indices: + IndexSet [16-17]: + values: + Expr [16-17]: Lit: Int(4)"#]], ); } @@ -69,8 +100,13 @@ fn gphase_no_arguments() { parse, "gphase;", &expect![[r#" - Stmt [0-7] - StmtKind: GPhase [0-7]: + Stmt [0-7]: + annotations: + kind: GPhase [0-7]: + modifiers: + args: + duration: + qubits: [ Error( @@ -91,11 +127,17 @@ fn gphase_with_parameters() { parse, "gphase(pi / 2);", &expect![[r#" - Stmt [0-15] - StmtKind: GPhase [0-15]: - Expr [7-13]: BinOp (Div): - Expr [7-9]: Ident [7-9] "pi" - Expr [12-13]: Lit: Int(2)"#]], + Stmt [0-15]: + annotations: + kind: GPhase [0-15]: + 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: "#]], ); } @@ -105,9 +147,15 @@ fn gphase_inv_modifier() { parse, "inv @ gphase(a);", &expect![[r#" - Stmt [0-16] - StmtKind: GPhase [0-16]: - Expr [13-14]: Ident [13-14] "a""#]], + Stmt [0-16]: + annotations: + kind: GPhase [0-16]: + modifiers: + QuantumGateModifier [0-5]: Inv + args: + Expr [13-14]: Ident [13-14] "a" + duration: + qubits: "#]], ); } @@ -117,11 +165,21 @@ fn gphase_ctrl_inv_modifiers() { parse, "ctrl @ inv @ gphase(pi / 2) q0;", &expect![[r#" - Stmt [0-31] - StmtKind: GPhase [0-31]: - Expr [20-26]: BinOp (Div): - Expr [20-22]: Ident [20-22] "pi" - Expr [25-26]: Lit: Int(2) - GateOperand IndexedIdent [28-30]: Ident [28-30] "q0"[]"#]], + Stmt [0-31]: + annotations: + kind: GPhase [0-31]: + 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: + IndexedIdent [28-30]: + name: Ident [28-30] "q0" + 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 index 4d99ccfda8..29e8f0136d 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs @@ -16,19 +16,27 @@ fn simple_if_stmt() { } ", &expect![[r#" - Stmt [5-67] - StmtKind: IfStmt [5-67]: Expr [9-15]: BinOp (Eq): - Expr [9-10]: Ident [9-10] "x" - Expr [14-15]: Ident [14-15] "y" - Stmt [27-33] - StmtKind: ExprStmt [27-33]: Expr [27-32]: Assign: - Expr [27-28]: Ident [27-28] "a" - Expr [31-32]: Lit: Int(0) - Else: - Stmt [55-61] - StmtKind: ExprStmt [55-61]: Expr [55-60]: Assign: - Expr [55-56]: Ident [55-56] "a" - Expr [59-60]: Lit: Int(1)"#]], + 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_block: + Stmt [27-33]: + annotations: + kind: ExprStmt [27-33]: + expr: Expr [27-32]: AssignExpr: + lhs: Expr [27-28]: Ident [27-28] "a" + rhs: Expr [31-32]: Lit: Int(0) + else_block: + Stmt [55-61]: + annotations: + kind: ExprStmt [55-61]: + expr: Expr [55-60]: AssignExpr: + lhs: Expr [55-56]: Ident [55-56] "a" + rhs: Expr [59-60]: Lit: Int(1)"#]], ); } @@ -42,14 +50,21 @@ fn if_stmt_missing_else() { } ", &expect![[r#" - Stmt [5-39] - StmtKind: IfStmt [5-39]: Expr [9-15]: BinOp (Eq): - Expr [9-10]: Ident [9-10] "x" - Expr [14-15]: Ident [14-15] "y" - Stmt [27-33] - StmtKind: ExprStmt [27-33]: Expr [27-32]: Assign: - Expr [27-28]: Ident [27-28] "a" - Expr [31-32]: Lit: Int(0)"#]], + 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_block: + Stmt [27-33]: + annotations: + kind: ExprStmt [27-33]: + expr: Expr [27-32]: AssignExpr: + lhs: Expr [27-28]: Ident [27-28] "a" + rhs: Expr [31-32]: Lit: Int(0) + else_block: "#]], ); } @@ -73,36 +88,56 @@ fn nested_if_stmts() { } ", &expect![[r#" - Stmt [5-215] - StmtKind: IfStmt [5-215]: Expr [9-15]: BinOp (Eq): - Expr [9-10]: Ident [9-10] "x" - Expr [14-15]: Ident [14-15] "y" - Stmt [27-107] - StmtKind: IfStmt [27-107]: Expr [31-39]: BinOp (Eq): - Expr [31-33]: Ident [31-33] "x1" - Expr [37-39]: Ident [37-39] "y1" - Stmt [55-61] - StmtKind: ExprStmt [55-61]: Expr [55-60]: Assign: - Expr [55-56]: Ident [55-56] "a" - Expr [59-60]: Lit: Int(0) - Else: - Stmt [91-97] - StmtKind: ExprStmt [91-97]: Expr [91-96]: Assign: - Expr [91-92]: Ident [91-92] "a" - Expr [95-96]: Lit: Int(1) - Else: - Stmt [129-209] - StmtKind: IfStmt [129-209]: Expr [133-141]: BinOp (Eq): - Expr [133-135]: Ident [133-135] "x2" - Expr [139-141]: Ident [139-141] "y2" - Stmt [157-163] - StmtKind: ExprStmt [157-163]: Expr [157-162]: Assign: - Expr [157-158]: Ident [157-158] "a" - Expr [161-162]: Lit: Int(2) - Else: - Stmt [193-199] - StmtKind: ExprStmt [193-199]: Expr [193-198]: Assign: - Expr [193-194]: Ident [193-194] "a" - Expr [197-198]: Lit: Int(3)"#]], + 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_block: + 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_block: + Stmt [55-61]: + annotations: + kind: ExprStmt [55-61]: + expr: Expr [55-60]: AssignExpr: + lhs: Expr [55-56]: Ident [55-56] "a" + rhs: Expr [59-60]: Lit: Int(0) + else_block: + Stmt [91-97]: + annotations: + kind: ExprStmt [91-97]: + expr: Expr [91-96]: AssignExpr: + lhs: Expr [91-92]: Ident [91-92] "a" + rhs: Expr [95-96]: Lit: Int(1) + else_block: + 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_block: + Stmt [157-163]: + annotations: + kind: ExprStmt [157-163]: + expr: Expr [157-162]: AssignExpr: + lhs: Expr [157-158]: Ident [157-158] "a" + rhs: Expr [161-162]: Lit: Int(2) + else_block: + Stmt [193-199]: + annotations: + kind: ExprStmt [193-199]: + expr: Expr [193-198]: AssignExpr: + lhs: Expr [193-194]: Ident [193-194] "a" + rhs: Expr [197-198]: Lit: Int(3)"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/io_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/io_decl.rs index cfcae5cb3e..aa9c48b864 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/io_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/io_decl.rs @@ -13,8 +13,13 @@ fn input_bit_decl() { parse, "input bit b;", &expect![[r#" - Stmt [0-12] - StmtKind: IODeclaration [0-12]: input, ClassicalType [6-9]: BitType, Ident [10-11] "b""#]], + Stmt [0-12]: + annotations: + kind: IODeclaration [0-12]: + io_keyword: input + type: ScalarType [6-9]: BitType [6-9]: + size: + ident: Ident [10-11] "b""#]], ); } @@ -24,8 +29,13 @@ fn output_bit_decl() { parse, "output bit b;", &expect![[r#" - Stmt [0-13] - StmtKind: IODeclaration [0-13]: output, ClassicalType [7-10]: BitType, Ident [11-12] "b""#]], + Stmt [0-13]: + annotations: + kind: IODeclaration [0-13]: + io_keyword: output + type: ScalarType [7-10]: BitType [7-10]: + size: + ident: Ident [11-12] "b""#]], ); } @@ -35,8 +45,13 @@ fn input_bit_array_decl() { parse, "input bit[2] b;", &expect![[r#" - Stmt [0-15] - StmtKind: IODeclaration [0-15]: input, ClassicalType [6-12]: BitType [6-12]: Expr [10-11]: Lit: Int(2), Ident [13-14] "b""#]], + 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""#]], ); } @@ -46,8 +61,13 @@ fn output_bit_array_decl() { parse, "output bit[2] b;", &expect![[r#" - Stmt [0-16] - StmtKind: IODeclaration [0-16]: output, ClassicalType [7-13]: BitType [7-13]: Expr [11-12]: Lit: Int(2), Ident [14-15] "b""#]], + 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""#]], ); } @@ -57,8 +77,12 @@ fn intput_bool_decl() { parse, "input bool b;", &expect![[r#" - Stmt [0-13] - StmtKind: IODeclaration [0-13]: input, ClassicalType [6-10]: BoolType, Ident [11-12] "b""#]], + Stmt [0-13]: + annotations: + kind: IODeclaration [0-13]: + io_keyword: input + type: ScalarType [6-10]: BoolType + ident: Ident [11-12] "b""#]], ); } @@ -68,8 +92,12 @@ fn output_bool_decl() { parse, "output bool b;", &expect![[r#" - Stmt [0-14] - StmtKind: IODeclaration [0-14]: output, ClassicalType [7-11]: BoolType, Ident [12-13] "b""#]], + Stmt [0-14]: + annotations: + kind: IODeclaration [0-14]: + io_keyword: output + type: ScalarType [7-11]: BoolType + ident: Ident [12-13] "b""#]], ); } @@ -79,8 +107,13 @@ fn input_complex_decl() { parse, "input complex c;", &expect![[r#" - Stmt [0-16] - StmtKind: IODeclaration [0-16]: input, ClassicalType [6-13]: ComplexType [6-13], Ident [14-15] "c""#]], + 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""#]], ); } @@ -90,8 +123,13 @@ fn output_complex_decl() { parse, "output complex c;", &expect![[r#" - Stmt [0-17] - StmtKind: IODeclaration [0-17]: output, ClassicalType [7-14]: ComplexType [7-14], Ident [15-16] "c""#]], + 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""#]], ); } @@ -101,8 +139,14 @@ fn input_complex_sized_decl() { parse, "input complex[float[32]] c;", &expect![[r#" - Stmt [0-27] - StmtKind: IODeclaration [0-27]: input, ClassicalType [6-24]: ComplexType[float[FloatType[Expr [20-22]: Lit: Int(32)]: [14-23]]]: [6-24], Ident [25-26] "c""#]], + 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""#]], ); } @@ -112,8 +156,14 @@ fn output_complex_sized_decl() { parse, "output complex[float[32]] c;", &expect![[r#" - Stmt [0-28] - StmtKind: IODeclaration [0-28]: output, ClassicalType [7-25]: ComplexType[float[FloatType[Expr [21-23]: Lit: Int(32)]: [15-24]]]: [7-25], Ident [26-27] "c""#]], + 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""#]], ); } @@ -123,8 +173,13 @@ fn input_int_decl() { parse, "input int i;", &expect![[r#" - Stmt [0-12] - StmtKind: IODeclaration [0-12]: input, ClassicalType [6-9]: IntType [6-9], Ident [10-11] "i""#]], + Stmt [0-12]: + annotations: + kind: IODeclaration [0-12]: + io_keyword: input + type: ScalarType [6-9]: IntType [6-9]: + size: + ident: Ident [10-11] "i""#]], ); } @@ -134,8 +189,13 @@ fn output_int_decl() { parse, "output int i;", &expect![[r#" - Stmt [0-13] - StmtKind: IODeclaration [0-13]: output, ClassicalType [7-10]: IntType [7-10], Ident [11-12] "i""#]], + Stmt [0-13]: + annotations: + kind: IODeclaration [0-13]: + io_keyword: output + type: ScalarType [7-10]: IntType [7-10]: + size: + ident: Ident [11-12] "i""#]], ); } @@ -145,8 +205,13 @@ fn input_int_sized_decl() { parse, "input int[32] i;", &expect![[r#" - Stmt [0-16] - StmtKind: IODeclaration [0-16]: input, ClassicalType [6-13]: IntType[Expr [10-12]: Lit: Int(32)]: [6-13], Ident [14-15] "i""#]], + 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""#]], ); } @@ -156,8 +221,13 @@ fn output_int_sized_decl() { parse, "output int[32] i;", &expect![[r#" - Stmt [0-17] - StmtKind: IODeclaration [0-17]: output, ClassicalType [7-14]: IntType[Expr [11-13]: Lit: Int(32)]: [7-14], Ident [15-16] "i""#]], + 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""#]], ); } @@ -167,8 +237,13 @@ fn input_uint_decl() { parse, "input uint i;", &expect![[r#" - Stmt [0-13] - StmtKind: IODeclaration [0-13]: input, ClassicalType [6-10]: UIntType [6-10], Ident [11-12] "i""#]], + Stmt [0-13]: + annotations: + kind: IODeclaration [0-13]: + io_keyword: input + type: ScalarType [6-10]: UIntType [6-10]: + size: + ident: Ident [11-12] "i""#]], ); } @@ -178,8 +253,13 @@ fn output_uint_decl() { parse, "output uint i;", &expect![[r#" - Stmt [0-14] - StmtKind: IODeclaration [0-14]: output, ClassicalType [7-11]: UIntType [7-11], Ident [12-13] "i""#]], + Stmt [0-14]: + annotations: + kind: IODeclaration [0-14]: + io_keyword: output + type: ScalarType [7-11]: UIntType [7-11]: + size: + ident: Ident [12-13] "i""#]], ); } @@ -189,8 +269,13 @@ fn input_uint_sized_decl() { parse, "input uint[32] i;", &expect![[r#" - Stmt [0-17] - StmtKind: IODeclaration [0-17]: input, ClassicalType [6-14]: UIntType[Expr [11-13]: Lit: Int(32)]: [6-14], Ident [15-16] "i""#]], + 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""#]], ); } @@ -200,8 +285,13 @@ fn output_uint_sized_decl() { parse, "output uint[32] i;", &expect![[r#" - Stmt [0-18] - StmtKind: IODeclaration [0-18]: output, ClassicalType [7-15]: UIntType[Expr [12-14]: Lit: Int(32)]: [7-15], Ident [16-17] "i""#]], + 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""#]], ); } @@ -211,8 +301,13 @@ fn input_float_decl() { parse, "input float f;", &expect![[r#" - Stmt [0-14] - StmtKind: IODeclaration [0-14]: input, ClassicalType [6-11]: FloatType [6-11], Ident [12-13] "f""#]], + Stmt [0-14]: + annotations: + kind: IODeclaration [0-14]: + io_keyword: input + type: ScalarType [6-11]: FloatType [6-11]: + size: + ident: Ident [12-13] "f""#]], ); } @@ -222,8 +317,13 @@ fn output_float_decl() { parse, "output float f;", &expect![[r#" - Stmt [0-15] - StmtKind: IODeclaration [0-15]: output, ClassicalType [7-12]: FloatType [7-12], Ident [13-14] "f""#]], + Stmt [0-15]: + annotations: + kind: IODeclaration [0-15]: + io_keyword: output + type: ScalarType [7-12]: FloatType [7-12]: + size: + ident: Ident [13-14] "f""#]], ); } @@ -233,8 +333,13 @@ fn input_float_sized_decl() { parse, "input float[32] f;", &expect![[r#" - Stmt [0-18] - StmtKind: IODeclaration [0-18]: input, ClassicalType [6-15]: FloatType[Expr [12-14]: Lit: Int(32)]: [6-15], Ident [16-17] "f""#]], + 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""#]], ); } @@ -244,8 +349,13 @@ fn output_float_sized_decl() { parse, "output float[32] f;", &expect![[r#" - Stmt [0-19] - StmtKind: IODeclaration [0-19]: output, ClassicalType [7-16]: FloatType[Expr [13-15]: Lit: Int(32)]: [7-16], Ident [17-18] "f""#]], + 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""#]], ); } @@ -255,8 +365,13 @@ fn input_angle_decl() { parse, "input angle a;", &expect![[r#" - Stmt [0-14] - StmtKind: IODeclaration [0-14]: input, ClassicalType [6-11]: AngleType [6-11], Ident [12-13] "a""#]], + Stmt [0-14]: + annotations: + kind: IODeclaration [0-14]: + io_keyword: input + type: ScalarType [6-11]: AngleType [6-11]: + size: + ident: Ident [12-13] "a""#]], ); } @@ -266,8 +381,13 @@ fn output_angle_decl() { parse, "output angle a;", &expect![[r#" - Stmt [0-15] - StmtKind: IODeclaration [0-15]: output, ClassicalType [7-12]: AngleType [7-12], Ident [13-14] "a""#]], + Stmt [0-15]: + annotations: + kind: IODeclaration [0-15]: + io_keyword: output + type: ScalarType [7-12]: AngleType [7-12]: + size: + ident: Ident [13-14] "a""#]], ); } @@ -277,8 +397,13 @@ fn input_angle_sized_decl() { parse, "input angle[32] a;", &expect![[r#" - Stmt [0-18] - StmtKind: IODeclaration [0-18]: input, ClassicalType [6-15]: AngleType [6-15]: Expr [12-14]: Lit: Int(32), Ident [16-17] "a""#]], + 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""#]], ); } @@ -288,8 +413,13 @@ fn output_angle_sized_decl() { parse, "output angle[32] a;", &expect![[r#" - Stmt [0-19] - StmtKind: IODeclaration [0-19]: output, ClassicalType [7-16]: AngleType [7-16]: Expr [13-15]: Lit: Int(32), Ident [17-18] "a""#]], + 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""#]], ); } @@ -299,8 +429,12 @@ fn input_duration_decl() { parse, "input duration d;", &expect![[r#" - Stmt [0-17] - StmtKind: IODeclaration [0-17]: input, ClassicalType [6-14]: Duration, Ident [15-16] "d""#]], + Stmt [0-17]: + annotations: + kind: IODeclaration [0-17]: + io_keyword: input + type: ScalarType [6-14]: Duration + ident: Ident [15-16] "d""#]], ); } @@ -310,8 +444,12 @@ fn output_duration_decl() { parse, "output duration d;", &expect![[r#" - Stmt [0-18] - StmtKind: IODeclaration [0-18]: output, ClassicalType [7-15]: Duration, Ident [16-17] "d""#]], + Stmt [0-18]: + annotations: + kind: IODeclaration [0-18]: + io_keyword: output + type: ScalarType [7-15]: Duration + ident: Ident [16-17] "d""#]], ); } @@ -321,8 +459,12 @@ fn input_stretch_decl() { parse, "input stretch s;", &expect![[r#" - Stmt [0-16] - StmtKind: IODeclaration [0-16]: input, ClassicalType [6-13]: Stretch, Ident [14-15] "s""#]], + Stmt [0-16]: + annotations: + kind: IODeclaration [0-16]: + io_keyword: input + type: ScalarType [6-13]: Stretch + ident: Ident [14-15] "s""#]], ); } @@ -332,7 +474,11 @@ fn output_stretch_decl() { parse, "output stretch s;", &expect![[r#" - Stmt [0-17] - StmtKind: IODeclaration [0-17]: output, ClassicalType [7-14]: Stretch, Ident [15-16] "s""#]], + 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 index ffb621efa9..dbf71b9d03 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs @@ -11,8 +11,14 @@ fn measure_identifier() { parse, "measure q;", &expect![[r#" - Stmt [0-10] - StmtKind: MeasureStmt [0-10]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-9]: Ident [8-9] "q"[]"#]], + Stmt [0-10]: + annotations: + kind: MeasureStmt [0-10]: + measurement: MeasureExpr [0-7]: + operand: IndexedIdent [8-9]: + name: Ident [8-9] "q" + indices: + target: "#]], ); } @@ -22,10 +28,17 @@ fn measure_indented_ident() { parse, "measure q[2];", &expect![[r#" - Stmt [0-13] - StmtKind: MeasureStmt [0-13]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-12]: Ident [8-9] "q"[ - IndexElement: - IndexSetItem Expr [10-11]: Lit: Int(2)]"#]], + Stmt [0-13]: + annotations: + kind: MeasureStmt [0-13]: + measurement: MeasureExpr [0-7]: + operand: IndexedIdent [8-12]: + name: Ident [8-9] "q" + indices: + IndexSet [10-11]: + values: + Expr [10-11]: Lit: Int(2) + target: "#]], ); } @@ -35,8 +48,12 @@ fn measure_hardware_qubit() { parse, "measure $42;", &expect![[r#" - Stmt [0-12] - StmtKind: MeasureStmt [0-12]: MeasureExpr [0-7]: GateOperand HardwareQubit [8-11]: 42"#]], + Stmt [0-12]: + annotations: + kind: MeasureStmt [0-12]: + measurement: MeasureExpr [0-7]: + operand: HardwareQubit [8-11]: 42 + target: "#]], ); } @@ -46,8 +63,16 @@ fn measure_arrow_into_ident() { parse, "measure q -> a;", &expect![[r#" - Stmt [0-15] - StmtKind: MeasureStmt [0-15]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-9]: Ident [8-9] "q"[], IndexedIdent [13-14]: Ident [13-14] "a"[]"#]], + Stmt [0-15]: + annotations: + kind: MeasureStmt [0-15]: + measurement: MeasureExpr [0-7]: + operand: IndexedIdent [8-9]: + name: Ident [8-9] "q" + indices: + target: IndexedIdent [13-14]: + name: Ident [13-14] "a" + indices: "#]], ); } @@ -57,9 +82,18 @@ fn measure_arrow_into_indented_ident() { parse, "measure q -> a[1];", &expect![[r#" - Stmt [0-18] - StmtKind: MeasureStmt [0-18]: MeasureExpr [0-7]: GateOperand IndexedIdent [8-9]: Ident [8-9] "q"[], IndexedIdent [13-17]: Ident [13-14] "a"[ - IndexElement: - IndexSetItem Expr [15-16]: Lit: Int(1)]"#]], + Stmt [0-18]: + annotations: + kind: MeasureStmt [0-18]: + measurement: MeasureExpr [0-7]: + operand: IndexedIdent [8-9]: + name: Ident [8-9] "q" + indices: + target: IndexedIdent [13-17]: + name: Ident [13-14] "a" + indices: + IndexSet [15-16]: + values: + Expr [15-16]: Lit: Int(1)"#]], ); } 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 index 69152c07ff..ad0d3b7e1c 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/old_style_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/old_style_decl.rs @@ -13,8 +13,13 @@ fn creg_decl() { parse, "creg c;", &expect![[r#" - Stmt [0-7] - StmtKind: ClassicalDeclarationStmt [0-7]: ClassicalType [0-7]: BitType, Ident [5-6] "c""#]], + Stmt [0-7]: + annotations: + kind: ClassicalDeclarationStmt [0-7]: + type: ScalarType [0-7]: BitType [0-7]: + size: + ident: Ident [5-6] "c" + init_expr: "#]], ); } @@ -24,8 +29,13 @@ fn creg_array_decl() { parse, "creg c[n];", &expect![[r#" - Stmt [0-10] - StmtKind: ClassicalDeclarationStmt [0-10]: ClassicalType [0-10]: BitType [0-10]: Expr [7-8]: Ident [7-8] "n", Ident [5-6] "c""#]], + 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: "#]], ); } @@ -35,8 +45,11 @@ fn qreg_decl() { parse, "qreg q;", &expect![[r#" - Stmt [0-7] - StmtKind: QubitDeclaration [0-7]: Ident [5-6] "q""#]], + Stmt [0-7]: + annotations: + kind: QubitDeclaration [0-7]: + ident: Ident [5-6] "q" + size: "#]], ); } @@ -46,7 +59,10 @@ fn qreg_array_decl() { parse, "qreg q[n];", &expect![[r#" - Stmt [0-10] - StmtKind: QubitDeclaration [0-10]: Ident [5-6] "q", Expr [7-8]: Ident [7-8] "n""#]], + Stmt [0-10]: + annotations: + kind: QubitDeclaration [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 index 83319befae..42abef06cb 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/pragma.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/pragma.rs @@ -13,8 +13,11 @@ fn pragma_decl() { parse, "pragma a.b.d 23", &expect![[r#" - Stmt [0-15] - StmtKind: Pragma [0-15]: (a.b.d, 23)"#]], + Stmt [0-15]: + annotations: + kind: Pragma [0-15]: + identifier: "a.b.d" + value: "23""#]], ); } @@ -24,8 +27,11 @@ fn pragma_decl_ident_only() { parse, "pragma a.b.d", &expect![[r#" - Stmt [0-12] - StmtKind: Pragma [0-12]: (a.b.d)"#]], + Stmt [0-12]: + annotations: + kind: Pragma [0-12]: + identifier: "a.b.d" + value: "#]], ); } @@ -35,8 +41,11 @@ fn pragma_decl_missing_ident() { parse, "pragma ", &expect![[r#" - Stmt [0-7] - StmtKind: Pragma [0-7]: () + Stmt [0-7]: + annotations: + kind: Pragma [0-7]: + identifier: "" + value: [ Error( @@ -59,8 +68,11 @@ fn legacy_pragma_decl() { parse, "#pragma a.b.d 23", &expect![[r#" - Stmt [0-16] - StmtKind: Pragma [0-16]: (a, a.b.d 23)"#]], + Stmt [0-16]: + annotations: + kind: Pragma [0-16]: + identifier: "a" + value: "a.b.d 23""#]], ); } @@ -70,8 +82,11 @@ fn legacy_pragma_decl_ident_only() { parse, "#pragma a.b.d", &expect![[r#" - Stmt [0-13] - StmtKind: Pragma [0-13]: (a, a.b.d)"#]], + Stmt [0-13]: + annotations: + kind: Pragma [0-13]: + identifier: "a" + value: "a.b.d""#]], ); } @@ -81,8 +96,11 @@ fn legacy_pragma_ws_after_hash() { parse, "# pragma a.b.d", &expect![[r#" - Stmt [2-14] - StmtKind: Pragma [2-14]: (a.b.d) + Stmt [2-14]: + annotations: + kind: Pragma [2-14]: + identifier: "a.b.d" + value: [ Error( @@ -108,7 +126,10 @@ fn legacy_pragma_decl_missing_ident() { parse, "#pragma ", &expect![[r#" - Stmt [0-8] - StmtKind: Pragma [0-8]: (a, )"#]], + 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 index 93d18cec15..1bf02de48d 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/quantum_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/quantum_decl.rs @@ -13,8 +13,11 @@ fn quantum_decl() { parse, "qubit q;", &expect![[r#" - Stmt [0-8] - StmtKind: QubitDeclaration [0-8]: Ident [6-7] "q""#]], + Stmt [0-8]: + annotations: + kind: QubitDeclaration [0-8]: + ident: Ident [6-7] "q" + size: "#]], ); } @@ -26,9 +29,14 @@ fn annotated_quantum_decl() { @a.b.c 123 qubit q;"#, &expect![[r#" - Stmt [9-36] - Annotation [9-19]: (a.b.c, 123) - StmtKind: QubitDeclaration [28-36]: Ident [34-35] "q""#]], + Stmt [9-36]: + annotations: + Annotation [9-19]: + identifier: "a.b.c" + value: "123" + kind: QubitDeclaration [28-36]: + ident: Ident [34-35] "q" + size: "#]], ); } @@ -42,11 +50,20 @@ fn multi_annotated_quantum_decl() { @a.b.c 123 qubit q;"#, &expect![[r#" - Stmt [9-108] - Annotation [9-57]: (g.h, dolor sit amet, consectetur adipiscing elit) - Annotation [66-72]: (d.e.f) - Annotation [81-91]: (a.b.c, 123) - StmtKind: QubitDeclaration [100-108]: Ident [106-107] "q""#]], + 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]: + ident: Ident [106-107] "q" + size: "#]], ); } @@ -76,8 +93,11 @@ fn quantum_decl_with_designator() { parse, "qubit[5] qubits;", &expect![[r#" - Stmt [0-16] - StmtKind: QubitDeclaration [0-16]: Ident [9-15] "qubits", Expr [6-7]: Lit: Int(5)"#]], + Stmt [0-16]: + annotations: + kind: QubitDeclaration [0-16]: + ident: Ident [9-15] "qubits" + size: Expr [6-7]: Lit: Int(5)"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs index 863df34196..0b144e90b4 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs @@ -11,8 +11,12 @@ fn reset_ident() { parse, "reset a;", &expect![[r#" - Stmt [0-8] - StmtKind: ResetStmt [0-8]: GateOperand IndexedIdent [6-7]: Ident [6-7] "a"[]"#]], + Stmt [0-8]: + annotations: + kind: ResetStmt [0-8]: + operand: IndexedIdent [6-7]: + name: Ident [6-7] "a" + indices: "#]], ); } @@ -22,10 +26,15 @@ fn reset_indexed_ident() { parse, "reset a[1];", &expect![[r#" - Stmt [0-11] - StmtKind: ResetStmt [0-11]: GateOperand IndexedIdent [6-10]: Ident [6-7] "a"[ - IndexElement: - IndexSetItem Expr [8-9]: Lit: Int(1)]"#]], + Stmt [0-11]: + annotations: + kind: ResetStmt [0-11]: + operand: IndexedIdent [6-10]: + name: Ident [6-7] "a" + indices: + IndexSet [8-9]: + values: + Expr [8-9]: Lit: Int(1)"#]], ); } @@ -35,7 +44,9 @@ fn reset_hardware_qubit() { parse, "reset $42;", &expect![[r#" - Stmt [0-10] - StmtKind: ResetStmt [0-10]: GateOperand HardwareQubit [6-9]: 42"#]], + Stmt [0-10]: + annotations: + kind: ResetStmt [0-10]: + operand: 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 index cc6cfdd706..2ecdea6fd4 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/switch_stmt.rs @@ -16,13 +16,13 @@ fn simple_switch() { ", &expect![[r#" SwitchStmt [9-72]: - Target: Expr [17-18]: Ident [17-18] "x" - Cases: - Labels: - Expr [37-38]: Lit: Int(1) - Block [39-41]: - Default Case: - Block [60-62]: "#]], + 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]: "#]], ); } @@ -35,9 +35,9 @@ fn no_cases_no_default() { ", &expect![[r#" SwitchStmt [9-22]: - Target: Expr [17-18]: Ident [17-18] "x" - - + target: Expr [17-18]: Ident [17-18] "x" + cases: + default_case: [ Error( @@ -63,10 +63,9 @@ fn no_cases() { ", &expect![[r#" SwitchStmt [9-52]: - Target: Expr [17-18]: Ident [17-18] "x" - - Default Case: - Block [40-42]: + target: Expr [17-18]: Ident [17-18] "x" + cases: + default_case: Block [40-42]: [ Error( @@ -92,13 +91,14 @@ fn no_default() { ", &expect![[r#" SwitchStmt [9-54]: - Target: Expr [17-18]: Ident [17-18] "x" - Cases: - Labels: - Expr [37-38]: Lit: Int(0) - Expr [40-41]: Lit: Int(1) - Block [42-44]: - "#]], + 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: "#]], ); } @@ -113,11 +113,12 @@ fn case_with_no_labels() { ", &expect![[r#" SwitchStmt [9-49]: - Target: Expr [17-18]: Ident [17-18] "x" - Cases: - - Block [37-39]: - + target: Expr [17-18]: Ident [17-18] "x" + cases: + SwitchCase [32-39]: + labels: + block: Block [37-39]: + default_case: [ Error( @@ -144,18 +145,30 @@ fn multiple_cases() { ", &expect![[r#" SwitchStmt [9-95]: - Target: Expr [17-18]: Ident [17-18] "x" - Cases: - Labels: - Expr [37-38]: Lit: Int(0) - Block [39-53]: - Stmt [41-51] - StmtKind: ClassicalDeclarationStmt [41-51]: ClassicalType [41-44]: IntType [41-44], Ident [45-46] "x", ValueExpression Expr [49-50]: Lit: Int(0) - Labels: - Expr [69-70]: Lit: Int(1) - Block [71-85]: - Stmt [73-83] - StmtKind: ClassicalDeclarationStmt [73-83]: ClassicalType [73-76]: IntType [73-76], Ident [77-78] "y", ValueExpression Expr [81-82]: Lit: Int(1) - "#]], + 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 index fe7576bbb7..6a7cff7fde 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs @@ -13,14 +13,34 @@ fn simple_while() { a = 0; }", &expect![[r#" - Stmt [5-42] - StmtKind: WhileLoop [5-42]: Expr [12-18]: BinOp (Neq): - Expr [12-13]: Ident [12-13] "x" - Expr [17-18]: Lit: Int(2) - Stmt [30-36] - StmtKind: ExprStmt [30-36]: Expr [30-35]: Assign: - Expr [30-31]: Ident [30-31] "a" - Expr [34-35]: Lit: Int(0)"#]], + 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) + block: + Stmt [30-36]: + annotations: + kind: ExprStmt [30-36]: + expr: Expr [30-35]: AssignExpr: + lhs: Expr [30-31]: Ident [30-31] "a" + 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) + block: "#]], ); } @@ -32,14 +52,20 @@ fn while_stmt_body() { while (x != 2) a = 0;", &expect![[r#" - Stmt [5-34] - StmtKind: WhileLoop [5-34]: Expr [12-18]: BinOp (Neq): - Expr [12-13]: Ident [12-13] "x" - Expr [17-18]: Lit: Int(2) - Stmt [28-34] - StmtKind: ExprStmt [28-34]: Expr [28-33]: Assign: - Expr [28-29]: Ident [28-29] "a" - Expr [32-33]: Lit: Int(0)"#]], + 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) + block: + Stmt [28-34]: + annotations: + kind: ExprStmt [28-34]: + expr: Expr [28-33]: AssignExpr: + lhs: Expr [28-29]: Ident [28-29] "a" + rhs: Expr [32-33]: Lit: Int(0)"#]], ); } @@ -53,16 +79,23 @@ fn while_loop_with_continue_stmt() { continue; }", &expect![[r#" - Stmt [5-60] - StmtKind: WhileLoop [5-60]: Expr [12-18]: BinOp (Neq): - Expr [12-13]: Ident [12-13] "x" - Expr [17-18]: Lit: Int(2) - Stmt [30-36] - StmtKind: ExprStmt [30-36]: Expr [30-35]: Assign: - Expr [30-31]: Ident [30-31] "a" - Expr [34-35]: Lit: Int(0) - Stmt [45-54] - StmtKind: Continue [45-54]"#]], + 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) + block: + Stmt [30-36]: + annotations: + kind: ExprStmt [30-36]: + expr: Expr [30-35]: AssignExpr: + lhs: Expr [30-31]: Ident [30-31] "a" + rhs: Expr [34-35]: Lit: Int(0) + Stmt [45-54]: + annotations: + kind: ContinueStmt [45-54]"#]], ); } @@ -76,15 +109,22 @@ fn while_loop_with_break_stmt() { break; }", &expect![[r#" - Stmt [5-57] - StmtKind: WhileLoop [5-57]: Expr [12-18]: BinOp (Neq): - Expr [12-13]: Ident [12-13] "x" - Expr [17-18]: Lit: Int(2) - Stmt [30-36] - StmtKind: ExprStmt [30-36]: Expr [30-35]: Assign: - Expr [30-31]: Ident [30-31] "a" - Expr [34-35]: Lit: Int(0) - Stmt [45-51] - StmtKind: Break [45-51]"#]], + 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) + block: + Stmt [30-36]: + annotations: + kind: ExprStmt [30-36]: + expr: Expr [30-35]: AssignExpr: + lhs: Expr [30-31]: Ident [30-31] "a" + rhs: Expr [34-35]: Lit: Int(0) + Stmt [45-51]: + annotations: + kind: BreakStmt [45-51]"#]], ); } From c2d4051670305344305a1b351dfa59c6e9536f53 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Fri, 7 Mar 2025 14:34:38 -0800 Subject: [PATCH 54/98] Add initial semantic analysis/transform (#2214) --- compiler/qsc_qasm3/src/ast.rs | 144 +- compiler/qsc_qasm3/src/ast/display_utils.rs | 22 +- compiler/qsc_qasm3/src/ast_builder.rs | 1 + compiler/qsc_qasm3/src/compile.rs | 44 +- compiler/qsc_qasm3/src/lib.rs | 305 +-- compiler/qsc_qasm3/src/oqasm_helpers.rs | 11 +- compiler/qsc_qasm3/src/parser.rs | 36 +- compiler/qsc_qasm3/src/parser/completion.rs | 3 - compiler/qsc_qasm3/src/parser/expr.rs | 23 +- compiler/qsc_qasm3/src/parser/prim.rs | 3 - compiler/qsc_qasm3/src/parser/scan.rs | 4 +- compiler/qsc_qasm3/src/parser/stmt.rs | 20 +- compiler/qsc_qasm3/src/runtime.rs | 70 +- compiler/qsc_qasm3/src/semantic.rs | 131 ++ compiler/qsc_qasm3/src/semantic/ast.rs | 1655 +++++++++++++++ compiler/qsc_qasm3/src/semantic/error.rs | 330 +++ compiler/qsc_qasm3/src/semantic/lowerer.rs | 1788 +++++++++++++++++ compiler/qsc_qasm3/src/semantic/symbols.rs | 451 +++++ compiler/qsc_qasm3/src/semantic/tests.rs | 152 ++ .../qsc_qasm3/src/semantic/tests/decls.rs | 97 + .../qsc_qasm3/src/semantic/tests/decls/bit.rs | 123 ++ .../src/semantic/tests/decls/bool.rs | 123 ++ .../src/semantic/tests/decls/complex.rs | 393 ++++ .../src/semantic/tests/decls/creg.rs | 47 + .../src/semantic/tests/decls/duration.rs | 26 + .../src/semantic/tests/decls/float.rs | 499 +++++ .../qsc_qasm3/src/semantic/tests/decls/int.rs | 423 ++++ .../src/semantic/tests/decls/qreg.rs | 28 + .../src/semantic/tests/decls/stretch.rs | 26 + .../src/semantic/tests/decls/uint.rs | 427 ++++ compiler/qsc_qasm3/src/semantic/types.rs | 675 +++++++ .../src/tests/declaration/integer.rs | 45 - .../src/tests/declaration/unsigned_integer.rs | 45 + compiler/qsc_qasm3/src/types.rs | 27 +- 34 files changed, 7700 insertions(+), 497 deletions(-) create mode 100644 compiler/qsc_qasm3/src/semantic.rs create mode 100644 compiler/qsc_qasm3/src/semantic/ast.rs create mode 100644 compiler/qsc_qasm3/src/semantic/error.rs create mode 100644 compiler/qsc_qasm3/src/semantic/lowerer.rs create mode 100644 compiler/qsc_qasm3/src/semantic/symbols.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/decls.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/decls/bit.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/decls/bool.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/decls/creg.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/decls/duration.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/decls/float.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/decls/int.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/decls/qreg.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/decls/stretch.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs create mode 100644 compiler/qsc_qasm3/src/semantic/types.rs diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index 24ca614a38..d53088a362 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -1,15 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// while we work through the conversion, allow dead code to avoid warnings -#![allow(dead_code)] - -mod display_utils; +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::{ @@ -66,6 +64,7 @@ pub struct Annotation { pub identifier: Rc, pub value: Option>, } + impl Display for Annotation { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let identifier = format!("\"{}\"", self.identifier); @@ -145,21 +144,6 @@ impl WithSpan for Path { } } -#[derive(Clone, Debug)] -pub enum AssignmentExpr { - Expr(Expr), - Measurement(MeasureExpr), -} - -impl Display for AssignmentExpr { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - AssignmentExpr::Expr(expr) => write!(f, "AssignmentExpr {expr}"), - AssignmentExpr::Measurement(measure) => write!(f, "AssignmentExpr {measure}"), - } - } -} - #[derive(Clone, Debug)] pub struct MeasureExpr { pub span: Span, @@ -282,8 +266,12 @@ impl Display for GateOperand { } impl WithSpan for GateOperand { - fn with_span(self, _span: Span) -> Self { - self + fn with_span(self, span: Span) -> Self { + match self { + GateOperand::IndexedIdent(ident) => GateOperand::IndexedIdent(ident.with_span(span)), + GateOperand::HardwareQubit(qubit) => GateOperand::HardwareQubit(qubit.with_span(span)), + GateOperand::Err => GateOperand::Err, + } } } @@ -299,6 +287,12 @@ impl Display for HardwareQubit { } } +impl WithSpan for HardwareQubit { + fn with_span(self, span: Span) -> Self { + Self { span, ..self } + } +} + #[derive(Clone, Debug)] pub struct AliasDeclStmt { pub span: Span, @@ -482,6 +476,15 @@ pub enum Identifier { IndexedIdent(Box), } +impl Identifier { + 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 { @@ -533,6 +536,12 @@ impl Display for IndexedIdent { } } +impl WithSpan for IndexedIdent { + fn with_span(self, span: Span) -> Self { + Self { span, ..self } + } +} + #[derive(Clone, Debug)] pub struct ExprStmt { pub span: Span, @@ -598,6 +607,12 @@ pub struct RangeDefinition { 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)?; @@ -641,7 +656,7 @@ impl Display for GateModifierKind { #[derive(Clone, Debug)] pub struct ClassicalArgument { pub span: Span, - pub r#type: ScalarType, + pub ty: ScalarType, pub name: Identifier, pub access: Option, } @@ -652,13 +667,13 @@ impl Display for ClassicalArgument { write!( f, "ClassicalArgument {}: {}, {}, {}", - self.span, self.r#type, self.name, access + self.span, self.ty, self.name, access ) } else { write!( f, "ClassicalArgument {}: {}, {}", - self.span, self.r#type, self.name + self.span, self.ty, self.name ) } } @@ -726,6 +741,7 @@ pub enum ScalarTypeKind { BoolType, Duration, Stretch, + // Any usage of Err should have pushed a parse error #[default] Err, } @@ -1106,7 +1122,7 @@ impl Display for MeasureStmt { #[derive(Clone, Debug)] pub struct ClassicalDeclarationStmt { pub span: Span, - pub r#type: Box, + pub ty: Box, pub identifier: Ident, pub init_expr: Option>, } @@ -1114,7 +1130,7 @@ pub struct ClassicalDeclarationStmt { impl Display for ClassicalDeclarationStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln_header(f, "ClassicalDeclarationStmt", self.span)?; - writeln_field(f, "type", &self.r#type)?; + writeln_field(f, "type", &self.ty)?; writeln_field(f, "ident", &self.identifier)?; write_opt_field(f, "init_expr", self.init_expr.as_ref()) } @@ -1139,7 +1155,7 @@ impl Display for ValueExpression { pub struct IODeclaration { pub span: Span, pub io_identifier: IOKeyword, - pub r#type: TypeDef, + pub ty: TypeDef, pub ident: Box, } @@ -1147,7 +1163,7 @@ 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.r#type)?; + writeln_field(f, "type", &self.ty)?; write_field(f, "ident", &self.ident) } } @@ -1155,7 +1171,7 @@ impl Display for IODeclaration { #[derive(Clone, Debug)] pub struct ConstantDeclStmt { pub span: Span, - pub r#type: TypeDef, + pub ty: TypeDef, pub identifier: Box, pub init_expr: Expr, } @@ -1163,7 +1179,7 @@ pub struct ConstantDeclStmt { impl Display for ConstantDeclStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln_header(f, "ConstantDeclStmt", self.span)?; - writeln_field(f, "type", &self.r#type)?; + writeln_field(f, "type", &self.ty)?; writeln_field(f, "ident", &self.identifier)?; write_field(f, "init_expr", &self.init_expr) } @@ -1225,7 +1241,7 @@ impl Default for TypedParameter { Self::Scalar(ScalarTypedParameter { span: Span::default(), ident: Ident::default(), - r#type: Box::default(), + ty: Box::default(), }) } } @@ -1233,26 +1249,22 @@ impl Default for TypedParameter { #[derive(Clone, Debug)] pub struct ScalarTypedParameter { pub span: Span, - pub r#type: Box, + 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.r#type)?; + 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 { r#type, ident, .. } = self; - Self { - span, - r#type, - ident, - } + let Self { ty, ident, .. } = self; + Self { span, ty, ident } } } @@ -1281,26 +1293,22 @@ impl WithSpan for QuantumTypedParameter { #[derive(Clone, Debug)] pub struct ArrayTypedParameter { pub span: Span, - pub r#type: Box, + 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.r#type)?; + 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 { r#type, ident, .. } = self; - Self { - span, - r#type, - ident, - } + let Self { ty, ident, .. } = self; + Self { span, ty, ident } } } @@ -1323,21 +1331,6 @@ impl Display for DefStmt { } } -#[derive(Clone, Debug)] -pub enum Operand { - Classical(ClassicalArgument), - Quantum(QuantumArgument), -} - -impl Display for Operand { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Operand::Classical(arg) => write!(f, "{arg}"), - Operand::Quantum(arg) => write!(f, "{arg}"), - } - } -} - #[derive(Clone, Debug)] pub struct ReturnStmt { pub span: Span, @@ -1369,7 +1362,7 @@ impl Display for WhileLoop { #[derive(Clone, Debug)] pub struct ForStmt { pub span: Span, - pub r#type: ScalarType, + pub ty: ScalarType, pub identifier: Identifier, pub set_declaration: Box, pub block: List, @@ -1378,7 +1371,7 @@ pub struct ForStmt { impl Display for ForStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln_header(f, "ForStmt", self.span)?; - writeln_field(f, "variable_type", &self.r#type)?; + writeln_field(f, "variable_type", &self.ty)?; writeln_field(f, "variable_name", &self.identifier)?; writeln_field(f, "iterable", &self.set_declaration)?; write_list_field(f, "block", &self.block) @@ -1550,14 +1543,14 @@ impl Display for FunctionCall { #[derive(Clone, Debug)] pub struct Cast { pub span: Span, - pub r#type: TypeDef, + 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.r#type)?; + writeln_field(f, "type", &self.ty)?; write_field(f, "arg", &self.arg) } } @@ -1592,9 +1585,9 @@ impl Display for Lit { #[derive(Clone, Debug)] pub enum LiteralKind { Array(List), - Bitstring { value: BigInt, width: usize }, + Bitstring(BigInt, u32), Bool(bool), - Duration { value: f64, unit: TimeUnit }, + Duration(f64, TimeUnit), Float(f64), Imaginary(f64), Int(i64), @@ -1606,11 +1599,12 @@ 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 } => { + 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 } => { + LiteralKind::Duration(value, unit) => { write!(f, "Duration({value:?}, {unit:?})") } LiteralKind::Float(value) => write!(f, "Float({value:?})"), @@ -1663,8 +1657,14 @@ pub enum IndexSetItem { /// This is needed to able to use `IndexSetItem` in the `seq` combinator. impl WithSpan for IndexSetItem { - fn with_span(self, _span: Span) -> Self { - self + 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, + } } } diff --git a/compiler/qsc_qasm3/src/ast/display_utils.rs b/compiler/qsc_qasm3/src/ast/display_utils.rs index 9088590bcf..37b29d3198 100644 --- a/compiler/qsc_qasm3/src/ast/display_utils.rs +++ b/compiler/qsc_qasm3/src/ast/display_utils.rs @@ -48,7 +48,7 @@ where /// Writes a list of elements to the given buffer or stream /// with an additional indentation level. -pub(super) fn write_indented_list<'write, 'itemref, 'item, T, I>( +pub(crate) fn write_indented_list<'write, 'itemref, 'item, T, I>( f: &'write mut impl Write, vals: I, ) -> fmt::Result @@ -70,19 +70,19 @@ where } /// Writes the name and span of a structure to the given buffer or stream. -pub(super) fn write_header(f: &mut impl Write, name: &str, span: super::Span) -> fmt::Result { +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(super) fn writeln_header(f: &mut impl Write, name: &str, span: super::Span) -> fmt::Result { +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(super) fn write_field( +pub(crate) fn write_field( f: &mut impl Write, field_name: &str, val: &T, @@ -94,7 +94,7 @@ pub(super) fn write_field( /// Writes a field of a structure to the given buffer /// or stream with an additional indententation level. /// Inserts a newline afterwards. -pub(super) fn writeln_field( +pub(crate) fn writeln_field( f: &mut impl Write, field_name: &str, val: &T, @@ -105,7 +105,7 @@ pub(super) fn writeln_field( /// Writes an optional field of a structure to the given buffer /// or stream with an additional indententation level. -pub(super) fn write_opt_field( +pub(crate) fn write_opt_field( f: &mut impl Write, field_name: &str, opt_val: Option<&T>, @@ -120,7 +120,7 @@ pub(super) fn write_opt_field( /// Writes an optional field of a structure to the given buffer /// or stream with an additional indententation level. /// Inserts a newline afterwards. -pub(super) fn writeln_opt_field( +pub(crate) fn writeln_opt_field( f: &mut impl Write, field_name: &str, opt_val: Option<&T>, @@ -132,7 +132,7 @@ pub(super) fn writeln_opt_field( /// Writes an field of a structure to the given buffer /// or stream with an additional indententation level. /// The field must be an iterable. -pub(super) fn write_list_field<'write, 'itemref, 'item, T, I>( +pub(crate) fn write_list_field<'write, 'itemref, 'item, T, I>( f: &mut impl Write, field_name: &str, vals: I, @@ -152,7 +152,7 @@ where /// or stream with an additional indententation level. /// The field must be an iterable. /// Inserts a newline afterwards. -pub(super) fn writeln_list_field<'write, 'itemref, 'item, T, I>( +pub(crate) fn writeln_list_field<'write, 'itemref, 'item, T, I>( f: &mut impl Write, field_name: &str, vals: I, @@ -169,7 +169,7 @@ where /// 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(super) fn write_opt_list_field<'write, 'itemref, 'item, T, I>( +pub(crate) fn write_opt_list_field<'write, 'itemref, 'item, T, I>( f: &mut impl Write, field_name: &str, opt_vals: Option, @@ -191,7 +191,7 @@ where /// or stream with an additional indententation level. /// The field must be an iterable. /// Inserts a newline afterwards. -pub(super) fn writeln_opt_list_field<'write, 'itemref, 'item, T, I>( +pub(crate) fn writeln_opt_list_field<'write, 'itemref, 'item, T, I>( f: &mut impl Write, field_name: &str, opt_vals: Option, diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm3/src/ast_builder.rs index 4269334548..5f63aef4bb 100644 --- a/compiler/qsc_qasm3/src/ast_builder.rs +++ b/compiler/qsc_qasm3/src/ast_builder.rs @@ -1151,6 +1151,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(), } } diff --git a/compiler/qsc_qasm3/src/compile.rs b/compiler/qsc_qasm3/src/compile.rs index abbc10e70d..f36897d19e 100644 --- a/compiler/qsc_qasm3/src/compile.rs +++ b/compiler/qsc_qasm3/src/compile.rs @@ -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; @@ -2915,11 +2915,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 +3097,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 +3308,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 }); } } @@ -3914,7 +3924,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 +3936,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 +3950,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 +3958,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 +3969,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/lib.rs b/compiler/qsc_qasm3/src/lib.rs index 624bfbaae0..e67f575ec3 100644 --- a/compiler/qsc_qasm3/src/lib.rs +++ b/compiler/qsc_qasm3/src/lib.rs @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +// while we work through the conversion, allow dead code to avoid warnings +#![allow(dead_code)] + mod angle; mod ast; mod ast_builder; @@ -14,6 +17,7 @@ mod oqasm_types; pub mod parse; pub mod parser; mod runtime; +pub mod semantic; mod symbols; mod types; @@ -58,18 +62,16 @@ 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("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("QASM3 Parse Error: Not Found {0}")] NotFound(String), #[error("IO Error: {0}")] @@ -79,300 +81,15 @@ pub enum ErrorKind { impl ErrorKind { fn with_offset(self, offset: u32) -> Self { match self { - ErrorKind::Unimplemented(error, span) => Self::Unimplemented(error, span + offset), - ErrorKind::CalibrationsNotSupported(error, span) => { - Self::CalibrationsNotSupported(error, span + offset) - } - ErrorKind::NotSupported(error, span) => Self::NotSupported(error, span + offset), ErrorKind::Parse(error, span) => Self::Parse(error, span + offset), - ErrorKind::Semantic(error) => ErrorKind::Semantic(error.with_offset(offset)), + ErrorKind::Parser(error) => Self::Parser(error.with_offset(offset)), + ErrorKind::Semantic(error) => Self::Semantic(error.with_offset(offset)), ErrorKind::NotFound(error) => Self::NotFound(error), ErrorKind::IO(error) => Self::IO(error), } } } -#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] -#[error(transparent)] -#[diagnostic(transparent)] -pub struct SemanticError(SemanticErrorKind); - -impl SemanticError { - #[must_use] - pub fn with_offset(self, offset: u32) -> Self { - Self(self.0.with_offset(offset)) - } -} - -/// Represents the kind of semantic error that occurred during compilation of a QASM file(s). -/// For the most part, these errors are fatal and prevent compilation and are -/// safety checks to ensure that the QASM code is valid. -/// -/// We can't use the semantics library for this: -/// - it is unsafe to use (heavy use of panic and unwrap) -/// - it is missing many language features -#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] -enum SemanticErrorKind { - #[error("Annotation missing target statement.")] - #[diagnostic(code("Qsc.Qasm3.Compile.AnnotationWithoutStatement"))] - AnnotationWithoutStatement(#[label] Span), - #[error("Cannot alias type {0}. Only qubit and qubit[] can be aliased.")] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotAliasType"))] - CannotAliasType(String, Span), - #[error("Cannot apply operator {0} to types {1} and {2}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotApplyOperatorToTypes"))] - CannotApplyOperatorToTypes(String, String, String, #[label] Span), - #[error("Cannot assign a value of {0} type to a classical variable of {1} type.")] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotAssignToType"))] - CannotAssignToType(String, String, #[label] Span), - #[error("Cannot call a gate that is not a gate.")] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotCallNonGate"))] - CannotCallNonGate(#[label] Span), - #[error("Cannot cast expression of type {0} to type {1}")] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotCast"))] - CannotCast(String, String, #[label] Span), - #[error("Cannot index variables of type {0}")] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotIndexType"))] - CannotIndexType(String, #[label] Span), - #[error("Cannot update const variable {0}")] - #[diagnostic(help("mutable variables must be declared without the keyword `const`."))] - #[diagnostic(code("Qsc.Qasm3.Compile.CannotUpdateConstVariable"))] - CannotUpdateConstVariable(String, #[label] Span), - #[error("Cannot cast expression of type {0} to type {1} as it would cause truncation.")] - #[diagnostic(code("Qsc.Qasm3.Compile.CastWouldCauseTruncation"))] - CastWouldCauseTruncation(String, String, #[label] Span), - #[error("Complex numbers in assignment binary expressions are not yet supported.")] - #[diagnostic(code("Qsc.Qasm3.Compile.ComplexBinaryAssignment"))] - ComplexBinaryAssignment(#[label] Span), - #[error("Designator must be a literal integer.")] - #[diagnostic(code("Qsc.Qasm3.Compile.DesignatorMustBeIntLiteral"))] - DesignatorMustBeIntLiteral(#[label] Span), - #[error("Failed to compile all expressions in expression list.")] - #[diagnostic(code("Qsc.Qasm3.Compile.FailedToCompileExpressionList"))] - FailedToCompileExpressionList(#[label] Span), - #[error("For iterable must have a set expression, range expression, or iterable expression.")] - #[diagnostic(code("Qsc.Qasm3.Compile.ForIterableInvalidExpression"))] - ForIterableInvalidExpression(#[label] Span), - #[error("For statements must have a body or statement.")] - #[diagnostic(code("Qsc.Qasm3.Compile.ForStatementsMustHaveABodyOrStatement"))] - ForStatementsMustHaveABodyOrStatement(#[label] Span), - #[error("If statement missing {0} expression.")] - #[diagnostic(code("Qsc.Qasm3.Compile.IfStmtMissingExpression"))] - IfStmtMissingExpression(String, #[label] Span), - #[error("include {0} could not be found.")] - #[diagnostic(code("Qsc.Qasm3.Compile.IncludeNotFound"))] - IncludeNotFound(String, #[label] Span), - #[error("include {0} must be declared in global scope.")] - #[diagnostic(code("Qsc.Qasm3.Compile.IncludeNotInGlobalScope"))] - IncludeNotInGlobalScope(String, #[label] Span), - #[error("include {0} must be declared in global scope.")] - #[diagnostic(code("Qsc.Qasm3.Compile.IncludeStatementMissingPath"))] - IncludeStatementMissingPath(#[label] Span), - #[error("Indexed must be a single expression.")] - #[diagnostic(code("Qsc.Qasm3.Compile.IndexMustBeSingleExpr"))] - IndexMustBeSingleExpr(#[label] Span), - #[error("Annotations only valid on gate definitions.")] - #[diagnostic(code("Qsc.Qasm3.Compile.InvalidAnnotationTarget"))] - InvalidAnnotationTarget(Span), - #[error("Assigning {0} values to {1} must be in a range that be converted to {1}.")] - InvalidCastValueRange(String, String, #[label] Span), - #[error("Gate operands other than qubits or qubit arrays are not supported.")] - #[diagnostic(code("Qsc.Qasm3.Compile.InvalidGateOperand"))] - InvalidGateOperand(#[label] Span), - #[error("Control counts must be integer literals.")] - #[diagnostic(code("Qsc.Qasm3.Compile.InvalidControlCount"))] - InvalidControlCount(#[label] Span), - #[error("Gate operands other than qubit arrays are not supported.")] - #[diagnostic(code("Qsc.Qasm3.Compile.InvalidIndexedGateOperand"))] - InvalidIndexedGateOperand(#[label] Span), - #[error("Gate expects {0} classical arguments, but {1} were provided.")] - #[diagnostic(code("Qsc.Qasm3.Compile.InvalidNumberOfClassicalArgs"))] - InvalidNumberOfClassicalArgs(usize, usize, #[label] Span), - #[error("Gate expects {0} qubit arguments, but {1} were provided.")] - #[diagnostic(code("Qsc.Qasm3.Compile.InvalidNumberOfQubitArgs"))] - InvalidNumberOfQubitArgs(usize, usize, #[label] Span), - #[error("Measure statements must have a name.")] - #[diagnostic(code("Qsc.Qasm3.Compile.MeasureExpressionsMustHaveName"))] - MeasureExpressionsMustHaveName(#[label] Span), - #[error("Measure statements must have a gate operand name.")] - #[diagnostic(code("Qsc.Qasm3.Compile.MeasureExpressionsMustHaveGateOperand"))] - MeasureExpressionsMustHaveGateOperand(#[label] Span), - #[error("Control counts must be postitive integers.")] - #[diagnostic(code("Qsc.Qasm3.Compile.NegativeControlCount"))] - NegativeControlCount(#[label] Span), - #[error("The operator {0} is not valid with lhs {1} and rhs {2}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.OperatorNotSupportedForTypes"))] - OperatorNotSupportedForTypes(String, String, String, #[label] Span), - #[error("Pow gate modifiers must have an exponent.")] - #[diagnostic(code("Qsc.Qasm3.Compile.PowModifierMustHaveExponent"))] - PowModifierMustHaveExponent(#[label] Span), - #[error("Qiskit circuits must have output registers.")] - #[diagnostic(code("Qsc.Qasm3.Compile.QiskitEntryPointMissingOutput"))] - QiskitEntryPointMissingOutput(#[label] Span), - #[error("Quantum declarations must be done in global scope.")] - #[diagnostic(code("Qsc.Qasm3.Compile.QuantumDeclarationInNonGlobalScope"))] - QuantumDeclarationInNonGlobalScope(#[label] Span), - #[error("Quantum typed values cannot be used in binary expressions.")] - #[diagnostic(code("Qsc.Qasm3.Compile.QuantumTypesInBinaryExpression"))] - QuantumTypesInBinaryExpression(#[label] Span), - #[error("Range expressions must have a start.")] - #[diagnostic(code("Qsc.Qasm3.Compile.RangeExpressionsMustHaveStart"))] - RangeExpressionsMustHaveStart(#[label] Span), - #[error("Range expressions must have a stop.")] - #[diagnostic(code("Qsc.Qasm3.Compile.RangeExpressionsMustHaveStop"))] - RangeExpressionsMustHaveStop(#[label] Span), - #[error("Redefined symbol: {0}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.RedefinedSymbol"))] - RedefinedSymbol(String, #[label] Span), - #[error("Reset expression must have a gate operand.")] - #[diagnostic(code("Qsc.Qasm3.Compile.ResetExpressionMustHaveGateOperand"))] - ResetExpressionMustHaveGateOperand(#[label] Span), - #[error("Reset expression must have a name.")] - #[diagnostic(code("Qsc.Qasm3.Compile.ResetExpressionMustHaveName"))] - ResetExpressionMustHaveName(#[label] Span), - #[error("Return statements are only allowed within subroutines.")] - #[diagnostic(code("Qsc.Qasm3.Compile.ReturnNotInSubroutine"))] - ReturnNotInSubroutine(#[label] Span), - #[error("Too many controls specified.")] - #[diagnostic(code("Qsc.Qasm3.Compile.TooManyControls"))] - TooManyControls(#[label] Span), - #[error("Types differ by dimensions and are incompatible.")] - #[diagnostic(code("Qsc.Qasm3.Compile.TypeRankError"))] - TypeRankError(#[label] Span), - #[error("Undefined symbol: {0}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.UndefinedSymbol"))] - UndefinedSymbol(String, #[label] Span), - #[error("Unexpected parser error: {0}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.UnexpectedParserError"))] - UnexpectedParserError(String, #[label] Span), - #[error("Unexpected annotation: {0}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.UnknownAnnotation"))] - UnknownAnnotation(String, #[label] Span), - #[error("Undefined symbol: {0}.")] - #[diagnostic(code("Qsc.Qasm3.Compile.UnknownIndexedOperatorKind"))] - UnknownIndexedOperatorKind(#[label] Span), - #[error("While statement missing {0} expression.")] - #[diagnostic(code("Qsc.Qasm3.Compile.WhileStmtMissingExpression"))] - WhileStmtMissingExpression(String, Span), -} - -impl SemanticErrorKind { - /// The semantic errors are reported with the span of the syntax that caused the error. - /// This offset is relative to the start of the file in which the error occurred. - /// This method is used to adjust the span of the error to be relative to where the - /// error was reported in the entire compilation unit as part of the source map. - #[allow(clippy::too_many_lines)] - fn with_offset(self, offset: u32) -> Self { - match self { - Self::AnnotationWithoutStatement(span) => { - Self::AnnotationWithoutStatement(span + offset) - } - Self::CannotCast(lhs, rhs, span) => Self::CannotCast(lhs, rhs, span + offset), - Self::CastWouldCauseTruncation(lhs, rhs, span) => { - Self::CastWouldCauseTruncation(lhs, rhs, span + offset) - } - Self::CannotAliasType(name, span) => Self::CannotAliasType(name, span + offset), - Self::CannotApplyOperatorToTypes(op, lhs, rhs, span) => { - Self::CannotApplyOperatorToTypes(op, lhs, rhs, span + offset) - } - Self::CannotAssignToType(lhs, rhs, span) => { - Self::CannotAssignToType(lhs, rhs, span + offset) - } - Self::CannotCallNonGate(span) => Self::CannotCallNonGate(span + offset), - Self::CannotIndexType(name, span) => Self::CannotIndexType(name, span + offset), - Self::CannotUpdateConstVariable(name, span) => { - Self::CannotUpdateConstVariable(name, span + offset) - } - Self::ComplexBinaryAssignment(span) => Self::ComplexBinaryAssignment(span + offset), - Self::DesignatorMustBeIntLiteral(span) => { - Self::DesignatorMustBeIntLiteral(span + offset) - } - Self::FailedToCompileExpressionList(span) => { - Self::FailedToCompileExpressionList(span + offset) - } - Self::ForIterableInvalidExpression(span) => { - Self::ForIterableInvalidExpression(span + offset) - } - Self::ForStatementsMustHaveABodyOrStatement(span) => { - Self::ForStatementsMustHaveABodyOrStatement(span + offset) - } - Self::IfStmtMissingExpression(name, span) => { - Self::IfStmtMissingExpression(name, span + offset) - } - Self::IncludeNotFound(name, span) => Self::IncludeNotFound(name, span + offset), - Self::IncludeNotInGlobalScope(name, span) => { - Self::IncludeNotInGlobalScope(name, span + offset) - } - Self::IncludeStatementMissingPath(span) => { - Self::IncludeStatementMissingPath(span + offset) - } - Self::IndexMustBeSingleExpr(span) => Self::IndexMustBeSingleExpr(span + offset), - Self::InvalidAnnotationTarget(span) => Self::InvalidAnnotationTarget(span + offset), - Self::InvalidControlCount(span) => Self::InvalidControlCount(span + offset), - Self::InvalidNumberOfClassicalArgs(expected, actual, span) => { - Self::InvalidNumberOfClassicalArgs(expected, actual, span + offset) - } - Self::InvalidNumberOfQubitArgs(expected, actual, span) => { - Self::InvalidNumberOfQubitArgs(expected, actual, span + offset) - } - Self::InvalidCastValueRange(lhs, rhs, span) => { - Self::InvalidCastValueRange(lhs, rhs, span + offset) - } - Self::InvalidGateOperand(span) => Self::InvalidGateOperand(span + offset), - Self::InvalidIndexedGateOperand(span) => Self::InvalidIndexedGateOperand(span + offset), - Self::MeasureExpressionsMustHaveGateOperand(span) => { - Self::MeasureExpressionsMustHaveGateOperand(span + offset) - } - Self::MeasureExpressionsMustHaveName(span) => { - Self::MeasureExpressionsMustHaveName(span + offset) - } - Self::NegativeControlCount(span) => Self::NegativeControlCount(span + offset), - Self::OperatorNotSupportedForTypes(op, lhs, rhs, span) => { - Self::OperatorNotSupportedForTypes(op, lhs, rhs, span + offset) - } - Self::PowModifierMustHaveExponent(span) => { - Self::PowModifierMustHaveExponent(span + offset) - } - Self::QiskitEntryPointMissingOutput(span) => { - Self::QiskitEntryPointMissingOutput(span + offset) - } - Self::QuantumDeclarationInNonGlobalScope(span) => { - Self::QuantumDeclarationInNonGlobalScope(span + offset) - } - Self::QuantumTypesInBinaryExpression(span) => { - Self::QuantumTypesInBinaryExpression(span + offset) - } - Self::RangeExpressionsMustHaveStart(span) => { - Self::RangeExpressionsMustHaveStart(span + offset) - } - Self::RangeExpressionsMustHaveStop(span) => { - Self::RangeExpressionsMustHaveStop(span + offset) - } - Self::RedefinedSymbol(name, span) => Self::RedefinedSymbol(name, span + offset), - Self::ResetExpressionMustHaveGateOperand(span) => { - Self::ResetExpressionMustHaveGateOperand(span + offset) - } - Self::ResetExpressionMustHaveName(span) => { - Self::ResetExpressionMustHaveName(span + offset) - } - Self::ReturnNotInSubroutine(span) => Self::ReturnNotInSubroutine(span + offset), - Self::TooManyControls(span) => Self::TooManyControls(span + offset), - Self::TypeRankError(span) => Self::TypeRankError(span + offset), - Self::UndefinedSymbol(name, span) => Self::UndefinedSymbol(name, span + offset), - Self::UnexpectedParserError(error, span) => { - Self::UnexpectedParserError(error, span + offset) - } - Self::UnknownAnnotation(name, span) => Self::UnknownAnnotation(name, span + offset), - Self::UnknownIndexedOperatorKind(span) => { - Self::UnknownIndexedOperatorKind(span + offset) - } - Self::WhileStmtMissingExpression(name, span) => { - Self::WhileStmtMissingExpression(name, span + offset) - } - } - } -} - /// Qubit semantics differ between Q# and Qiskit. This enum is used to /// specify which semantics to use when compiling QASM to Q#. /// 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/parser.rs b/compiler/qsc_qasm3/src/parser.rs index 9c74e68e40..bf9c2a83aa 100644 --- a/compiler/qsc_qasm3/src/parser.rs +++ b/compiler/qsc_qasm3/src/parser.rs @@ -14,6 +14,7 @@ pub(crate) mod tests; mod completion; mod error; +pub use error::Error; mod expr; mod prgm; mod prim; @@ -37,39 +38,40 @@ impl QasmParseResult { self.source.has_errors() } - pub fn all_errors(&self) -> Vec> { + 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)); + .map(|e| self.map_error(e)) + .collect::>(); self_errors.extend(include_errors); self_errors } #[must_use] - pub fn errors(&self) -> Vec> { + pub fn errors(&self) -> Vec> { self.source .errors() .iter() .map(|e| self.map_error(e.clone())) - .collect() + .collect::>() } - fn map_error( - &self, - error: crate::parser::error::Error, - ) -> WithSource { + fn map_error(&self, error: Error) -> WithSource { let path = self.source.path().display().to_string(); let source = self.source_map.find_by_name(&path); let offset = source.map_or(0, |source| source.offset); let offset_error = error.with_offset(offset); - WithSource::from_map(&self.source_map, offset_error) + WithSource::from_map( + &self.source_map, + crate::Error(crate::ErrorKind::Parser(offset_error)), + ) } } @@ -101,9 +103,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 @@ -128,7 +128,7 @@ pub struct QasmSource { /// The parsed AST of the source file or any parse errors. program: Program, /// Any parse errors that occurred. - errors: Vec, + errors: Vec, /// Any included files that were resolved. /// Note that this is a recursive structure. included: Vec, @@ -139,7 +139,7 @@ impl QasmSource { source: T, file_path: P, program: Program, - errors: Vec, + errors: Vec, included: Vec, ) -> QasmSource { QasmSource { @@ -160,7 +160,7 @@ impl QasmSource { } #[must_use] - pub fn all_errors(&self) -> Vec { + 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); @@ -183,7 +183,7 @@ impl QasmSource { } #[must_use] - pub fn errors(&self) -> Vec { + pub fn errors(&self) -> Vec { self.errors.clone() } @@ -222,7 +222,7 @@ where fn parse_source_and_includes, R>( source: P, resolver: &R, -) -> miette::Result<(Program, Vec, Vec)> +) -> miette::Result<(Program, Vec, Vec)> where R: SourceResolver, { @@ -258,7 +258,7 @@ pub(crate) trait Parser: FnMut(&mut ParserContext) -> Result {} impl Result> Parser for F {} -pub fn parse(input: &str) -> Result<(Program, Vec)> { +pub fn parse(input: &str) -> Result<(Program, Vec)> { let mut scanner = ParserContext::new(input); let program = prgm::parse(&mut scanner)?; Ok((program, scanner.into_errors())) diff --git a/compiler/qsc_qasm3/src/parser/completion.rs b/compiler/qsc_qasm3/src/parser/completion.rs index 17b37d8507..ae8f8e57e9 100644 --- a/compiler/qsc_qasm3/src/parser/completion.rs +++ b/compiler/qsc_qasm3/src/parser/completion.rs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// while we work through the conversion, allow dead code to avoid warnings -#![allow(dead_code)] - pub(crate) mod collector; #[cfg(test)] mod tests; diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 3e19032516..1cc3bc7b4b 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// while we work through the conversion, allow dead code to avoid warnings -#![allow(dead_code)] - //! Expression parsing makes use of Pratt parsing (or “top-down operator-precedence parsing”) to handle //! relative precedence of operators. @@ -280,18 +277,22 @@ fn lit_token(lexeme: &str, token: Token) -> Result> { } Literal::Bitstring => { let lexeme = shorten(1, 1, lexeme); - let width = lexeme - .to_string() - .chars() - .filter(|c| *c == '0' || *c == '1') - .count(); + 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 }, + kind: LiteralKind::Bitstring(value, width), })) } Literal::Imaginary => { @@ -429,7 +430,7 @@ fn timing_literal(lexeme: &str, token: Token, kind: TimingLiteralKind) -> Result Ok(Some(Lit { span: token.span, - kind: LiteralKind::Duration { value, unit }, + kind: LiteralKind::Duration(value, unit), })) } @@ -471,7 +472,7 @@ fn cast_op(s: &mut ParserContext, r#type: TypeDef) -> Result { recovering_token(s, TokenKind::Close(Delim::Paren)); Ok(ExprKind::Cast(Cast { span: s.span(lo), - r#type, + ty: r#type, arg, })) } diff --git a/compiler/qsc_qasm3/src/parser/prim.rs b/compiler/qsc_qasm3/src/parser/prim.rs index c3062c9bf4..44e3ef913b 100644 --- a/compiler/qsc_qasm3/src/parser/prim.rs +++ b/compiler/qsc_qasm3/src/parser/prim.rs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// while we work through the conversion, allow dead code to avoid warnings -#![allow(dead_code)] - #[cfg(test)] pub(crate) mod tests; diff --git a/compiler/qsc_qasm3/src/parser/scan.rs b/compiler/qsc_qasm3/src/parser/scan.rs index 775c20373f..50fbd3b45f 100644 --- a/compiler/qsc_qasm3/src/parser/scan.rs +++ b/compiler/qsc_qasm3/src/parser/scan.rs @@ -7,8 +7,8 @@ use crate::{ }; use qsc_data_structures::span::Span; -use super::error::Error; use super::error::ErrorKind; +use super::Error; #[derive(Debug)] pub(super) struct NoBarrierError; @@ -106,7 +106,7 @@ impl<'a> ParserContext<'a> { } } - pub(super) fn into_errors(self) -> Vec { + pub(super) fn into_errors(self) -> Vec { self.scanner.into_errors() } diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 2dfa716727..680c192d73 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -70,7 +70,7 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { span: s.span(ty.span().lo), kind: Box::new(ExprKind::Cast(Cast { span: s.span(ty.span().lo), - r#type: ty, + ty, arg, })), }; @@ -362,7 +362,7 @@ fn arg_def(s: &mut ParserContext) -> Result { let ident = prim::ident(s)?; TypedParameter::Scalar(ScalarTypedParameter { span: s.span(lo), - r#type: Box::new(ty), + ty: Box::new(ty), ident, }) } else if let Ok(size) = qubit_type(s) { @@ -382,7 +382,7 @@ fn arg_def(s: &mut ParserContext) -> Result { }; TypedParameter::Scalar(ScalarTypedParameter { span: s.span(lo), - r#type: Box::new(ty), + ty: Box::new(ty), ident, }) } else if let Ok((ident, size)) = qreg_type(s) { @@ -395,7 +395,7 @@ fn arg_def(s: &mut ParserContext) -> Result { let ident = prim::ident(s)?; TypedParameter::ArrayReference(ArrayTypedParameter { span: s.span(lo), - r#type: Box::new(ty), + ty: Box::new(ty), ident, }) } else { @@ -527,7 +527,7 @@ fn parse_io_decl(s: &mut ParserContext) -> Result { let decl = IODeclaration { span: s.span(lo), io_identifier: kind, - r#type: ty, + ty, ident, }; Ok(StmtKind::IODeclaration(decl)) @@ -562,7 +562,7 @@ fn parse_non_constant_classical_decl( recovering_semi(s); let decl = ClassicalDeclarationStmt { span: s.span(lo), - r#type: Box::new(ty), + ty: Box::new(ty), identifier, init_expr, }; @@ -580,7 +580,7 @@ fn parse_constant_classical_decl(s: &mut ParserContext) -> Result { recovering_semi(s); let decl = ConstantDeclStmt { span: s.span(lo), - r#type: ty, + ty, identifier, init_expr, }; @@ -675,7 +675,7 @@ fn creg_decl(s: &mut ParserContext) -> Result { recovering_semi(s); Ok(StmtKind::ClassicalDecl(ClassicalDeclarationStmt { span: s.span(lo), - r#type: Box::new(TypeDef::Scalar(ScalarType { + ty: Box::new(TypeDef::Scalar(ScalarType { span: s.span(lo), kind: ScalarTypeKind::Bit(BitType { size, @@ -1039,7 +1039,7 @@ fn for_loop_iterable_expr(s: &mut ParserContext) -> Result { pub fn parse_for_loop(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; token(s, TokenKind::Keyword(Keyword::For))?; - let r#type = scalar_type(s)?; + let ty = scalar_type(s)?; let identifier = Identifier::Ident(Box::new(prim::ident(s)?)); token(s, TokenKind::Keyword(Keyword::In))?; let set_declaration = Box::new(for_loop_iterable_expr(s)?); @@ -1047,7 +1047,7 @@ pub fn parse_for_loop(s: &mut ParserContext) -> Result { Ok(ForStmt { span: s.span(lo), - r#type, + ty, identifier, set_declaration, block, diff --git a/compiler/qsc_qasm3/src/runtime.rs b/compiler/qsc_qasm3/src/runtime.rs index 2b0080d5fd..0f1b0a4541 100644 --- a/compiler/qsc_qasm3/src/runtime.rs +++ b/compiler/qsc_qasm3/src/runtime.rs @@ -1,16 +1,54 @@ // 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. -/// +/// Implement the `gphase` operation for QASM3. +const GPHASE_GATE: &str = " +operation gphase(theta : Double) : Unit is Adj + Ctl { + body ... { + Exp([], theta, []) + } + adjoint auto; + controlled auto; + controlled adjoint auto; +} +"; + +/// Implement the `U` operation for QASM3. +/// We need to apply a global phase, but rather than require gphase to be called, +/// we can use the `R` gate since we have a qubit parameter (though it is ignored). +/// `R(PauliI, 4. * PI() - (lambda + phi + theta), qubit);` +/// Since `U` is periodic to `2pi`, we can use the following: +/// `R(PauliI, -(lambda + phi + theta), qubit);` +const U_GATE: &str = " +operation U(theta : Double, phi : Double, lambda : Double, qubit : Qubit) : Unit is Adj + Ctl { + body ... { + Rz(lambda, qubit); + Ry(theta, qubit); + Rz(phi, qubit); + R(PauliI, -lambda - phi - theta, qubit); + } + adjoint auto; + controlled auto; + controlled adjoint auto; +} +"; + /// 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 { @@ -144,6 +182,8 @@ bitflags! { /// IntAsResultArray requires BoolAsResult to be included. const IntAsResultArrayBE = 0b1_000_000_000 | 0b100; const ResultArrayAsIntBE = 0b10_000_000_000; + const Gphase = 0b100_000_000_000; + const U = 0b1_000_000_000_000; } } @@ -197,6 +237,17 @@ pub(crate) fn get_result_array_as_int_be_decl() -> Stmt { parse_stmt(RESULT_ARRAY_AS_INT_BE) } +pub(crate) fn get_gphase_decl() -> Stmt { + parse_stmt(GPHASE_GATE) +} + +pub(crate) fn get_u_decl() -> Stmt { + parse_stmt(U_GATE) +} + +/// 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:?}"); @@ -213,6 +264,7 @@ fn parse_stmt(name: &str) -> Stmt { } } +/// 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) { @@ -259,5 +311,13 @@ pub(crate) fn get_runtime_function_decls(runtime: RuntimeFunctions) -> Vec let stmt = crate::runtime::get_result_array_as_int_be_decl(); stmts.push(stmt); } + if runtime.contains(RuntimeFunctions::Gphase) { + let stmt = crate::runtime::get_gphase_decl(); + stmts.push(stmt); + } + if runtime.contains(RuntimeFunctions::U) { + let stmt = crate::runtime::get_u_decl(); + stmts.push(stmt); + } stmts } diff --git a/compiler/qsc_qasm3/src/semantic.rs b/compiler/qsc_qasm3/src/semantic.rs new file mode 100644 index 0000000000..1f676049b2 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic.rs @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::io::SourceResolver; +use crate::parser::QasmSource; + +use qsc_frontend::compile::SourceMap; +use qsc_frontend::error::WithSource; +use symbols::SymbolTable; + +use std::path::Path; + +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 parse_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.parse_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 { + let path = self.source.path().display().to_string(); + let source = self.source_map.find_by_name(&path); + let offset = source.map_or(0, |source| source.offset); + + let offset_error = error.with_offset(offset); + + WithSource::from_map( + &self.source_map, + crate::Error(crate::ErrorKind::Parser(offset_error)), + ) + } +} + +/// Parse a QASM file and return the parse result. +/// This function will resolve includes using the provided resolver. +/// If an include file cannot be resolved, an error will be returned. +/// If a file is included recursively, a stack overflow occurs. +pub fn parse_source( + source: S, + path: P, + resolver: &R, +) -> miette::Result +where + S: AsRef, + P: AsRef, + R: SourceResolver, +{ + let res = crate::parser::parse_source(source, path, resolver)?; + let analyzer = crate::semantic::lowerer::Lowerer { + source: res.source, + source_map: res.source_map, + errors: vec![], + file_stack: vec![], + symbols: SymbolTable::default(), + version: None, + stmts: vec![], + }; + let sem_res = analyzer.lower(); + Ok(QasmSemanticParseResult { + source: sem_res.source, + source_map: sem_res.source_map, + symbols: sem_res.symbols, + program: sem_res.program, + errors: sem_res.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..7b5d6bd8d2 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/ast.rs @@ -0,0 +1,1655 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use num_bigint::BigInt; +use qsc_data_structures::span::{Span, WithSpan}; +use std::{ + fmt::{self, Display, Formatter}, + hash::Hash, + rc::Rc, +}; + +use crate::{ + 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, +}; + +#[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 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 enum GateOperand { + IndexedIdent(Box), + HardwareQubit(Box), + #[default] + Err, +} + +impl Display for GateOperand { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + GateOperand::IndexedIdent(ident) => write!(f, "{ident}"), + GateOperand::HardwareQubit(qubit) => write!(f, "{qubit}"), + GateOperand::Err => write!(f, "Error"), + } + } +} + +impl WithSpan for GateOperand { + fn with_span(self, span: Span) -> Self { + match self { + GateOperand::IndexedIdent(ident) => GateOperand::IndexedIdent(ident.with_span(span)), + GateOperand::HardwareQubit(qubit) => GateOperand::HardwareQubit(qubit.with_span(span)), + GateOperand::Err => GateOperand::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), + Barrier(BarrierStmt), + Box(BoxStmt), + Block(Box), + CalibrationGrammar(CalibrationGrammarStmt), + ClassicalDecl(ClassicalDeclarationStmt), + Def(DefStmt), + DefCal(DefCalStmt), + Delay(DelayStmt), + /// An empty statement. + Empty, + End(EndStmt), + ExprStmt(ExprStmt), + ExternDecl(ExternDecl), + For(ForStmt), + If(IfStmt), + GateCall(GateCall), + GPhase(GPhase), + Include(IncludeStmt), + IODeclaration(IODeclaration), + Measure(MeasureStmt), + 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::Alias(alias) => write!(f, "{alias}"), + StmtKind::Barrier(barrier) => write!(f, "{barrier}"), + StmtKind::Box(box_stmt) => write!(f, "{box_stmt}"), + StmtKind::Block(block) => write!(f, "{block}"), + StmtKind::CalibrationGrammar(grammar) => write!(f, "{grammar}"), + StmtKind::ClassicalDecl(decl) => write!(f, "{decl}"), + StmtKind::Def(def) => write!(f, "{def}"), + StmtKind::DefCal(defcal) => write!(f, "{defcal}"), + StmtKind::Delay(delay) => write!(f, "{delay}"), + StmtKind::Empty => write!(f, "Empty"), + 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_block: List, + pub else_block: 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_list_field(f, "if_block", &self.if_block)?; + write_opt_list_field(f, "else_block", self.else_block.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 operand: Box, +} + +impl Display for ResetStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "ResetStmt", self.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 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: 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)?; + 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(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 { + 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 qubit: Box, + pub size: Option, +} + +impl Display for QubitDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "QubitDeclaration", self.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 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_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 MeasureStmt { + pub span: Span, + pub measurement: MeasureExpr, + pub target: Option>, +} + +impl Display for MeasureStmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln_header(f, "MeasureStmt", 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 enum ValueExpression { + Expr(Expr), + Measurement(MeasureExpr), +} + +impl Display for ValueExpression { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + ValueExpression::Expr(expr) => write!(f, "{expr}"), + ValueExpression::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 enum TypedParameter { + Scalar(ScalarTypedParameter), + Quantum(QuantumTypedParameter), + ArrayReference(ArrayTypedParameter), +} + +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: Box, + 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 block: List, +} + +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_list_field(f, "block", &self.block) + } +} + +#[derive(Clone, Debug)] +pub struct ForStmt { + pub span: Span, + pub ty: ScalarType, + pub identifier: Identifier, + pub set_declaration: Box, + pub block: List, +} + +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.identifier)?; + writeln_field(f, "iterable", &self.set_declaration)?; + write_list_field(f, "block", &self.block) + } +} + +#[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 { + Assign(AssignExpr), + AssignOp(AssignOpExpr), + /// An expression with invalid syntax that can't be parsed. + #[default] + Err, + Ident(SymbolId), + UnaryOp(UnaryOpExpr), + BinaryOp(BinaryOpExpr), + Lit(LiteralKind), + 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: {lit}"), + ExprKind::FunctionCall(call) => write!(f, "{call}"), + ExprKind::Cast(expr) => write!(f, "{expr}"), + ExprKind::IndexExpr(expr) => write!(f, "{expr}"), + ExprKind::Assign(expr) => write!(f, "{expr}"), + ExprKind::AssignOp(expr) => write!(f, "{expr}"), + ExprKind::Paren(expr) => write!(f, "Paren {expr}"), + } + } +} + +#[derive(Clone, Debug)] +pub struct AssignExpr { + pub lhs: Expr, + pub rhs: Expr, +} + +impl Display for AssignExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "AssignExpr:")?; + writeln_field(f, "lhs", &self.lhs)?; + write_field(f, "rhs", &self.rhs) + } +} + +#[derive(Clone, Debug)] +pub struct AssignOpExpr { + pub op: BinOp, + pub lhs: Expr, + pub rhs: Expr, +} + +impl Display for AssignOpExpr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "AssignOpExpr:")?; + 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: 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, "type", &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 { + Array(List), + Bitstring(BigInt, u32), + Bool(bool), + Duration(f64, TimeUnit), + Float(f64), + Complex(f64, 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::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, 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}"), + } + } +} + +#[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, 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, 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"), + } + } +} + +#[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/error.rs b/compiler/qsc_qasm3/src/semantic/error.rs new file mode 100644 index 0000000000..c637443765 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -0,0 +1,330 @@ +// 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("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 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 positive literal integer.")] + #[diagnostic(code("Qsc.Qasm3.Compile.DesignatorMustBePositiveIntLiteral"))] + DesignatorMustBePositiveIntLiteral(#[label] Span), + #[error("Designator is too large.")] + #[diagnostic(code("Qsc.Qasm3.Compile.DesignatorTooLarge"))] + DesignatorTooLarge(#[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(#[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("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("{0} are not supported.")] + #[diagnostic(code("Qsc.Qasm3.Compile.NotSupported"))] + NotSupported(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("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("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("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::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::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::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::DesignatorTooLarge(span) => Self::DesignatorTooLarge(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::NotSupported(name, span) => Self::NotSupported(name, 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::TypeDoesNotSupportBitwiseNot(name, span) => { + Self::TypeDoesNotSupportBitwiseNot(name, span + offset) + } + Self::TypeDoesNotSupportedUnaryNegation(name, span) => { + Self::TypeDoesNotSupportedUnaryNegation(name, 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) + } + } + } +} diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs new file mode 100644 index 0000000000..b15e0a7c9d --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -0,0 +1,1788 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::ops::ShlAssign; +use std::path::PathBuf; + +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::ast::list_from_iter; +use crate::oqasm_helpers::safe_i64_to_f64; +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 super::{ + ast::{Stmt, Version}, + SemanticErrorKind, +}; + +pub(super) 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 file_stack: Vec, + pub symbols: SymbolTable, + pub version: Option, + pub stmts: Vec, +} +impl Lowerer { + 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); + + let program = super::ast::Program { + version: self.version, + statements: 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 push the file path to the stack so we can track the current file + // for reporting errors. This saves us from having to pass around + // the current QasmSource value. + self.file_stack.push(source.path()); + + // 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 { + match &*stmt.kind { + crate::ast::StmtKind::Include(include) => { + // 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); + } + _ => { + if let Some(stmt) = self.lower_stmt(stmt) { + self.stmts.push(stmt); + } + } + } + } + + // Finally we pop the file path from the stack so that we + // can return to the previous file for error handling. + self.file_stack.pop(); + } + + #[allow(clippy::too_many_lines)] + fn lower_stmt(&mut self, stmt: &crate::ast::Stmt) -> Option { + let kind = match &*stmt.kind { + crate::ast::StmtKind::Alias(stmt) => { + super::ast::StmtKind::Alias(self.lower_alias(stmt)?) + } + crate::ast::StmtKind::Barrier(stmt) => { + super::ast::StmtKind::Barrier(self.lower_barrier(stmt)?) + } + crate::ast::StmtKind::Box(stmt) => super::ast::StmtKind::Box(self.lower_box(stmt)?), + crate::ast::StmtKind::Break(stmt) => self.lower_break(stmt)?, + crate::ast::StmtKind::Block(stmt) => { + super::ast::StmtKind::Block(Box::new(self.lower_block(stmt)?)) + } + crate::ast::StmtKind::Cal(stmt) => self.lower_calibration(stmt)?, + crate::ast::StmtKind::CalibrationGrammar(stmt) => { + super::ast::StmtKind::CalibrationGrammar(self.lower_calibration_grammar(stmt)?) + } + crate::ast::StmtKind::ClassicalDecl(stmt) => { + super::ast::StmtKind::ClassicalDecl(self.lower_classical_decl(stmt)?) + } + crate::ast::StmtKind::ConstDecl(stmt) => { + super::ast::StmtKind::ClassicalDecl(self.lower_const_decl(stmt)?) + } + crate::ast::StmtKind::Continue(stmt) => self.lower_continue_stmt(stmt)?, + crate::ast::StmtKind::Def(stmt) => super::ast::StmtKind::Def(self.lower_def(stmt)?), + crate::ast::StmtKind::DefCal(stmt) => { + super::ast::StmtKind::DefCal(self.lower_def_cal(stmt)?) + } + crate::ast::StmtKind::Delay(stmt) => { + super::ast::StmtKind::Delay(self.lower_delay(stmt)?) + } + crate::ast::StmtKind::Empty => { + // we ignore empty statements + None? + } + crate::ast::StmtKind::End(stmt) => { + super::ast::StmtKind::End(self.lower_end_stmt(stmt)?) + } + crate::ast::StmtKind::ExprStmt(stmt) => { + super::ast::StmtKind::ExprStmt(self.lower_expr_stmt(stmt)?) + } + crate::ast::StmtKind::ExternDecl(extern_decl) => { + super::ast::StmtKind::ExternDecl(self.lower_extern(extern_decl)?) + } + crate::ast::StmtKind::For(stmt) => { + super::ast::StmtKind::For(self.lower_for_stmt(stmt)?) + } + crate::ast::StmtKind::If(stmt) => super::ast::StmtKind::If(self.lower_if_stmt(stmt)?), + crate::ast::StmtKind::GateCall(stmt) => { + super::ast::StmtKind::GateCall(self.lower_gate_call(stmt)?) + } + crate::ast::StmtKind::GPhase(stmt) => { + super::ast::StmtKind::GPhase(self.lower_gphase(stmt)?) + } + crate::ast::StmtKind::Include(stmt) => { + super::ast::StmtKind::Include(self.lower_include(stmt)?) + } + crate::ast::StmtKind::IODeclaration(stmt) => { + super::ast::StmtKind::IODeclaration(self.lower_io_decl(stmt)?) + } + crate::ast::StmtKind::Measure(stmt) => { + super::ast::StmtKind::Measure(self.lower_measure(stmt)?) + } + crate::ast::StmtKind::Pragma(stmt) => { + super::ast::StmtKind::Pragma(self.lower_pragma(stmt)?) + } + crate::ast::StmtKind::QuantumGateDefinition(stmt) => { + super::ast::StmtKind::QuantumGateDefinition(self.lower_gate_def(stmt)?) + } + crate::ast::StmtKind::QuantumDecl(stmt) => { + super::ast::StmtKind::QuantumDecl(self.lower_quantum_decl(stmt)?) + } + crate::ast::StmtKind::Reset(stmt) => { + super::ast::StmtKind::Reset(self.lower_reset(stmt)?) + } + crate::ast::StmtKind::Return(stmt) => { + super::ast::StmtKind::Return(self.lower_return(stmt)?) + } + crate::ast::StmtKind::Switch(stmt) => { + super::ast::StmtKind::Switch(self.lower_switch(stmt)?) + } + crate::ast::StmtKind::WhileLoop(stmt) => { + super::ast::StmtKind::WhileLoop(self.lower_while_loop(stmt)?) + } + crate::ast::StmtKind::Err => { + self.push_semantic_error(SemanticErrorKind::UnexpectedParserError( + "Unexpected error".to_string(), + stmt.span, + )); + return None; + } + }; + let annotations = self.lower_annotations(&stmt.annotations, &stmt.kind); + Some(super::ast::Stmt { + span: stmt.span, + annotations: 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: &crate::ast::IncludeStmt) { + fn gate_symbol(name: &str, cargs: u32, qargs: u32) -> Symbol { + Symbol { + name: name.to_string(), + ty: Type::Gate(cargs, qargs), + ..Default::default() + } + } + let gates = vec![ + gate_symbol("X", 0, 1), + gate_symbol("Y", 0, 1), + gate_symbol("Z", 0, 1), + gate_symbol("H", 0, 1), + gate_symbol("S", 0, 1), + gate_symbol("T", 0, 1), + gate_symbol("Rx", 1, 1), + gate_symbol("Rxx", 1, 2), + gate_symbol("Ry", 1, 1), + gate_symbol("Ryy", 1, 2), + gate_symbol("Rz", 1, 1), + gate_symbol("Rzz", 1, 2), + gate_symbol("CNOT", 0, 2), + gate_symbol("CY", 0, 2), + gate_symbol("CZ", 0, 2), + gate_symbol("I", 0, 1), + gate_symbol("SWAP", 0, 2), + gate_symbol("CCNOT", 0, 3), + ]; + for gate in gates { + let name = gate.name.clone(); + if self.symbols.insert_symbol(gate).is_err() { + self.push_redefined_symbol_error(name.as_str(), include.span); + } + } + } + + /// 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); + } + + /// 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); + } + + /// Creates an error from the given kind with the current source mapping. + fn create_err(&self, kind: crate::ErrorKind) -> WithSource { + let error = crate::Error(kind); + let path = self.file_stack.last().map_or("", |p| { + p.to_str().expect("expected source mapping to exist.") + }); + let source = self.source_map.find_by_name(path); + let offset = source.map_or(0, |x| x.offset); + let offset_error = error.with_offset(offset); + WithSource::from_map(&self.source_map, offset_error) + } + + fn lower_alias( + &mut self, + alias: &crate::ast::AliasDeclStmt, + ) -> Option { + 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() + .filter_map(|expr| self.lower_expr(expr)) + .collect::>(); + // TODO: handle multiple rhs + // TODO: validate consistency of rhs types + let first = rhs.first().expect("missing rhs"); + let symbol = Symbol { + name: name.to_string(), + ty: first.ty.clone(), + qsharp_ty: self.convert_semantic_type_to_qsharp_type(&first.ty, alias.ident.span())?, + span: alias.ident.span(), + io_kind: IOKind::Default, + }; + let Ok(symbol_id) = self.symbols.insert_symbol(symbol) else { + self.push_redefined_symbol_error(name, alias.span); + return None; + }; + + if rhs.len() != alias.exprs.len() { + // we failed + return None; + } + Some(super::ast::AliasDeclStmt { + span: alias.span, + symbol_id, + exprs: list_from_iter(rhs), + }) + } + + fn lower_expr(&mut self, expr: &crate::ast::Expr) -> Option { + match &*expr.kind { + crate::ast::ExprKind::Assign(_) => { + self.push_unimplemented_error_message("assign expr", expr.span); + None + } + crate::ast::ExprKind::AssignOp(_) => { + self.push_unimplemented_error_message("assignop expr", expr.span); + None + } + crate::ast::ExprKind::BinaryOp(_) => { + self.push_unimplemented_error_message("binary op expr", expr.span); + None + } + crate::ast::ExprKind::Cast(_) => { + self.push_unimplemented_error_message("cast expr", expr.span); + None + } + crate::ast::ExprKind::Err => { + unreachable!("Err expr should not be lowered"); + } + crate::ast::ExprKind::FunctionCall(_) => { + self.push_unimplemented_error_message("function call expr", expr.span); + None + } + crate::ast::ExprKind::Ident(ident) => self.lower_ident_expr(ident), + crate::ast::ExprKind::IndexExpr(_) => { + self.push_unimplemented_error_message("index expr", expr.span); + None + } + + crate::ast::ExprKind::Lit(lit) => self.lower_lit_expr(lit), + + crate::ast::ExprKind::Paren(expr) => self.lower_paren_expr(expr), + crate::ast::ExprKind::UnaryOp(expr) => self.lower_unary_op_expr(expr), + } + } + + fn lower_ident_expr(&mut self, ident: &crate::ast::Ident) -> Option { + let name = ident.name.clone(); + let Some((symbol_id, symbol)) = self.symbols.get_symbol_by_name(&name) else { + self.push_missing_symbol_error(&name, ident.span); + return None; + }; + + let kind = super::ast::ExprKind::Ident(symbol_id); + Some(super::ast::Expr { + span: ident.span, + kind: Box::new(kind), + ty: symbol.ty.clone(), + }) + } + + fn lower_lit_expr(&mut self, expr: &crate::ast::Lit) -> Option { + let (kind, ty) = match &expr.kind { + crate::ast::LiteralKind::BigInt(value) => { + // todo: 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. + (super::ast::LiteralKind::BigInt(value.clone()), Type::Err) + } + crate::ast::LiteralKind::Bitstring(value, size) => ( + super::ast::LiteralKind::Bitstring(value.clone(), *size), + Type::BitArray(super::types::ArrayDimensions::One(*size), true), + ), + crate::ast::LiteralKind::Bool(value) => { + (super::ast::LiteralKind::Bool(*value), Type::Bool(true)) + } + crate::ast::LiteralKind::Int(value) => { + (super::ast::LiteralKind::Int(*value), Type::Int(None, true)) + } + crate::ast::LiteralKind::Float(value) => ( + super::ast::LiteralKind::Float(*value), + Type::Float(None, true), + ), + crate::ast::LiteralKind::Imaginary(value) => ( + super::ast::LiteralKind::Complex(0.0, *value), + Type::Complex(None, true), + ), + crate::ast::LiteralKind::String(_) => { + self.push_unsupported_error_message("String literals", expr.span); + return None; + } + crate::ast::LiteralKind::Duration(_, _) => { + self.push_unsupported_error_message("Duration literals", expr.span); + return None; + } + crate::ast::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() + .filter_map(|expr| self.lower_expr(expr)) + .collect::>(); + if texprs.len() != exprs.len() { + // we failed to lower all the entries and an error was pushed + return None; + } + + ( + super::ast::LiteralKind::Array(list_from_iter(texprs)), + Type::Err, + ) + } + }; + Some(super::ast::Expr { + span: expr.span, + kind: Box::new(super::ast::ExprKind::Lit(kind)), + ty, + }) + } + + fn lower_paren_expr(&mut self, expr: &crate::ast::Expr) -> Option { + let expr = self.lower_expr(expr)?; + let span = expr.span; + let ty = expr.ty.clone(); + let kind = super::ast::ExprKind::Paren(expr); + Some(super::ast::Expr { + span, + kind: Box::new(kind), + ty, + }) + } + + fn lower_unary_op_expr(&mut self, expr: &crate::ast::UnaryOpExpr) -> Option { + match expr.op { + crate::ast::UnaryOp::Neg => { + if let crate::ast::ExprKind::Lit(lit) = expr.expr.kind.as_ref() { + self.lower_negated_literal_as_ty(lit, None, expr.expr.span) + } else { + let expr = self.lower_expr(&expr.expr)?; + let ty = expr.ty.clone(); + if unary_op_can_be_applied_to_type(crate::ast::UnaryOp::Neg, &ty) { + let span = expr.span; + let unary = super::ast::UnaryOpExpr { + op: super::ast::UnaryOp::Neg, + expr, + }; + Some(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::UnaryOp(unary)), + ty, + }) + } else { + let kind = SemanticErrorKind::TypeDoesNotSupportedUnaryNegation( + expr.ty.to_string(), + expr.span, + ); + self.push_semantic_error(kind); + None + } + } + } + crate::ast::UnaryOp::NotB => { + let expr = self.lower_expr(&expr.expr)?; + let ty = expr.ty.clone(); + if unary_op_can_be_applied_to_type(crate::ast::UnaryOp::NotB, &ty) { + let span = expr.span; + let unary = super::ast::UnaryOpExpr { + op: super::ast::UnaryOp::NotB, + expr, + }; + Some(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::UnaryOp(unary)), + ty, + }) + } else { + let kind = SemanticErrorKind::TypeDoesNotSupportedUnaryNegation( + expr.ty.to_string(), + expr.span, + ); + self.push_semantic_error(kind); + None + } + } + crate::ast::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 target_ty = Type::Bool(false); + let expr = + self.lower_expr_with_target_type(Some(&expr.expr), &target_ty, expr.expr.span)?; + + let ty = expr.ty.clone(); + + Some(super::ast::Expr { + span: expr.span, + kind: Box::new(super::ast::ExprKind::UnaryOp(super::ast::UnaryOpExpr { + op: super::ast::UnaryOp::NotL, + expr, + })), + ty, + }) + } + } + } + + fn lower_annotations( + &mut self, + annotations: &[Box], + kind: &crate::ast::StmtKind, + ) -> Vec { + annotations + .iter() + .map(|annotation| self.lower_annotation(annotation, kind)) + .collect::>() + } + + fn lower_annotation( + &mut self, + annotation: &crate::ast::Annotation, + kind: &crate::ast::StmtKind, + ) -> super::ast::Annotation { + if !matches!( + annotation.identifier.to_string().as_str(), + "SimulatableIntrinsic" | "Config" + ) { + self.push_unsupported_error_message( + format!("Annotation {}.", annotation.identifier), + annotation.span, + ); + } + + if let crate::ast::StmtKind::GateCall(_) = &kind { + self.push_unsupported_error_message( + format!( + "Annotation {} is only allowed on gate definitions.", + annotation.identifier + ), + annotation.span, + ); + } + + super::ast::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, + ) -> Option { + let is_const = ty.is_const(); + match ty { + Type::Bit(_) => Some(crate::types::Type::Result(is_const)), + Type::Qubit => Some(crate::types::Type::Qubit), + Type::HardwareQubit => { + let message = "HardwareQubit to Q# type"; + self.push_unsupported_error_message(message, span); + None + } + Type::Int(width, _) | Type::UInt(width, _) => { + if let Some(width) = width { + if *width > 64 { + Some(crate::types::Type::BigInt(is_const)) + } else { + Some(crate::types::Type::Int(is_const)) + } + } else { + Some(crate::types::Type::Int(is_const)) + } + } + Type::Float(_, _) | Type::Angle(_, _) => Some(crate::types::Type::Double(is_const)), + Type::Complex(_, _) => Some(crate::types::Type::Complex(is_const)), + Type::Bool(_) => Some(crate::types::Type::Bool(is_const)), + Type::Duration(_) => { + self.push_unsupported_error_message("Duration type values", span); + None + } + Type::Stretch(_) => { + self.push_unsupported_error_message("Stretch type values", span); + None + } + Type::BitArray(dims, _) => Some(crate::types::Type::ResultArray(dims.into(), is_const)), + Type::QubitArray(dims) => Some(crate::types::Type::QubitArray(dims.into())), + Type::IntArray(size, dims) | Type::UIntArray(size, dims) => { + if let Some(size) = size { + if *size > 64 { + Some(crate::types::Type::BigIntArray(dims.into(), is_const)) + } else { + Some(crate::types::Type::IntArray(dims.into(), is_const)) + } + } else { + Some(crate::types::Type::IntArray(dims.into(), is_const)) + } + } + Type::FloatArray(_, dims) => Some(crate::types::Type::DoubleArray(dims.into())), + Type::AngleArray(_, _) => todo!("AngleArray to Q# type"), + Type::ComplexArray(_, _) => todo!("ComplexArray to Q# type"), + Type::BoolArray(dims) => Some(crate::types::Type::BoolArray(dims.into(), is_const)), + Type::Gate(cargs, qargs) => Some(crate::types::Type::Callable( + crate::types::CallableKind::Operation, + *cargs, + *qargs, + )), + Type::Range => Some(crate::types::Type::Range), + Type::Set => todo!("Set to Q# type"), + Type::Void => Some(crate::types::Type::Tuple(vec![])), + _ => { + let msg = format!("Converting {ty:?} to Q# type"); + self.push_unimplemented_error_message(msg, span); + None + } + } + } + + fn lower_barrier(&mut self, stmt: &crate::ast::BarrierStmt) -> Option { + self.push_unimplemented_error_message("barrier stmt", stmt.span); + None + } + + fn lower_box(&mut self, stmt: &crate::ast::BoxStmt) -> Option { + self.push_unimplemented_error_message("box stmt", stmt.span); + None + } + + fn lower_break(&mut self, stmt: &crate::ast::BreakStmt) -> Option { + self.push_unimplemented_error_message("break stmt", stmt.span); + None + } + + fn lower_block(&mut self, stmt: &crate::ast::Block) -> Option { + self.push_unimplemented_error_message("block stmt", stmt.span); + None + } + + fn lower_calibration( + &mut self, + stmt: &crate::ast::CalibrationStmt, + ) -> Option { + self.push_unimplemented_error_message("calibration stmt", stmt.span); + None + } + + fn lower_calibration_grammar( + &mut self, + stmt: &crate::ast::CalibrationGrammarStmt, + ) -> Option { + self.push_unimplemented_error_message("calibration stmt", stmt.span); + None + } + + fn lower_classical_decl( + &mut self, + stmt: &crate::ast::ClassicalDeclarationStmt, + ) -> Option { + 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 { + name: name.to_string(), + ty: ty.clone(), + qsharp_ty, + span: stmt.identifier.span, + io_kind: IOKind::Default, + }; + + // process the symbol and init_expr gathering any errors + let init_expr = match init_expr { + Some(expr) => match expr { + crate::ast::ValueExpression::Expr(expr) => self + .lower_expr_with_target_type(Some(expr), &ty, stmt_span) + .map(super::ast::ValueExpression::Expr), + crate::ast::ValueExpression::Measurement(measure_expr) => self + .lower_measure_expr_with_target_type(measure_expr, &ty, stmt_span) + .map(super::ast::ValueExpression::Measurement), + }, + None => self + .lower_expr_with_target_type(None, &ty, stmt_span) + .map(super::ast::ValueExpression::Expr), + }; + + let Ok(symbol_id) = self.symbols.insert_symbol(symbol) else { + self.push_redefined_symbol_error(&name, stmt.identifier.span); + return None; + }; + + // even if init_expr was None, Q# semantics require that we have an initial value + // for classical declarations. So if None is returned we hit an error with the expression. + let init_expr = init_expr?; + + Some(super::ast::ClassicalDeclarationStmt { + span: stmt_span, + ty_span, + symbol_id, + init_expr: Box::new(init_expr), + }) + } + + fn lower_const_decl( + &mut self, + stmt: &crate::ast::ConstantDeclStmt, + ) -> Option { + 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 symbol = Symbol { + name: name.to_string(), + ty: ty.clone(), + qsharp_ty, + span: stmt.identifier.span, + io_kind: IOKind::Default, + }; + + // process the symbol and init_expr gathering any errors + let init_expr = self.lower_expr_with_target_type(Some(&stmt.init_expr), &ty, stmt.span); + + let Ok(symbol_id) = self.symbols.insert_symbol(symbol) else { + self.push_redefined_symbol_error(&name, stmt.identifier.span); + return None; + }; + + // even if init_expr was None, Q# semantics require that we have an initial value + // for classical declarations. So if None is returned we hit an error with the expression. + let init_expr = init_expr?; + + Some(super::ast::ClassicalDeclarationStmt { + span: stmt.span, + ty_span, + symbol_id, + init_expr: Box::new(super::ast::ValueExpression::Expr(init_expr)), + }) + } + + fn lower_continue_stmt( + &mut self, + stmt: &crate::ast::ContinueStmt, + ) -> Option { + self.push_unimplemented_error_message("continue stmt", stmt.span); + None + } + + fn lower_def(&mut self, stmt: &crate::ast::DefStmt) -> Option { + self.push_unimplemented_error_message("def stmt", stmt.span); + None + } + + fn lower_def_cal(&mut self, stmt: &crate::ast::DefCalStmt) -> Option { + self.push_unimplemented_error_message("def cal stmt", stmt.span); + None + } + + fn lower_delay(&mut self, stmt: &crate::ast::DelayStmt) -> Option { + self.push_unimplemented_error_message("delay stmt", stmt.span); + None + } + + fn lower_end_stmt(&mut self, stmt: &crate::ast::EndStmt) -> Option { + self.push_unimplemented_error_message("end stmt", stmt.span); + None + } + + fn lower_expr_stmt(&mut self, stmt: &crate::ast::ExprStmt) -> Option { + self.push_unimplemented_error_message("expr stmt", stmt.span); + None + } + + fn lower_extern(&mut self, stmt: &crate::ast::ExternDecl) -> Option { + self.push_unimplemented_error_message("extern stmt", stmt.span); + None + } + + fn lower_for_stmt(&mut self, stmt: &crate::ast::ForStmt) -> Option { + self.push_unimplemented_error_message("for stmt", stmt.span); + None + } + + fn lower_if_stmt(&mut self, stmt: &crate::ast::IfStmt) -> Option { + self.push_unimplemented_error_message("if stmt", stmt.span); + None + } + + fn lower_gate_call(&mut self, stmt: &crate::ast::GateCall) -> Option { + self.push_unimplemented_error_message("gate call stmt", stmt.span); + None + } + + fn lower_gphase(&mut self, stmt: &crate::ast::GPhase) -> Option { + self.push_unimplemented_error_message("gphase stmt", stmt.span); + None + } + + fn lower_include(&mut self, stmt: &crate::ast::IncludeStmt) -> Option { + self.push_unimplemented_error_message("include stmt", stmt.span); + None + } + + fn lower_io_decl( + &mut self, + stmt: &crate::ast::IODeclaration, + ) -> Option { + self.push_unimplemented_error_message("io decl stmt", stmt.span); + None + } + + fn lower_measure(&mut self, stmt: &crate::ast::MeasureStmt) -> Option { + self.push_unimplemented_error_message("measure stmt", stmt.span); + None + } + + fn lower_pragma(&mut self, stmt: &crate::ast::Pragma) -> Option { + self.push_unimplemented_error_message("pragma stmt", stmt.span); + None + } + + fn lower_gate_def( + &mut self, + stmt: &crate::ast::QuantumGateDefinition, + ) -> Option { + self.push_unimplemented_error_message("gate def stmt", stmt.span); + None + } + + fn lower_quantum_decl( + &mut self, + stmt: &crate::ast::QubitDeclaration, + ) -> Option { + self.push_unimplemented_error_message("qubit decl stmt", stmt.span); + None + } + + fn lower_reset(&mut self, stmt: &crate::ast::ResetStmt) -> Option { + self.push_unimplemented_error_message("reset stmt", stmt.span); + None + } + + fn lower_return(&mut self, stmt: &crate::ast::ReturnStmt) -> Option { + self.push_unimplemented_error_message("return stmt", stmt.span); + None + } + + fn lower_switch(&mut self, stmt: &crate::ast::SwitchStmt) -> Option { + self.push_unimplemented_error_message("switch stmt", stmt.span); + None + } + + fn lower_while_loop(&mut self, stmt: &crate::ast::WhileLoop) -> Option { + self.push_unimplemented_error_message("while loop stmt", stmt.span); + None + } + + fn get_semantic_type_from_tydef( + &mut self, + scalar_ty: &crate::ast::TypeDef, + is_const: bool, + ) -> Option { + match scalar_ty { + crate::ast::TypeDef::Scalar(scalar_type) => { + self.get_semantic_type_from_scalar_ty(scalar_type, is_const) + } + crate::ast::TypeDef::Array(array_type) => { + self.get_semantic_type_from_array_ty(array_type, is_const) + } + crate::ast::TypeDef::ArrayReference(array_reference_type) => { + self.get_semantic_type_from_array_reference_ty(array_reference_type, is_const) + } + } + } + + /// designators are positive integer literals when used + /// in the context of a type definition. + fn get_size_designator_from_expr(&mut self, expr: &crate::ast::Expr) -> Option { + if let crate::ast::ExprKind::Lit(lit) = expr.kind.as_ref() { + if let crate::ast::LiteralKind::Int(value) = lit.kind { + if value > 0 { + if let Ok(value) = u32::try_from(value) { + Some(value) + } else { + self.push_semantic_error(SemanticErrorKind::DesignatorTooLarge(lit.span)); + None + } + } else { + self.push_semantic_error( + SemanticErrorKind::DesignatorMustBePositiveIntLiteral(lit.span), + ); + None + } + } else { + self.push_semantic_error(SemanticErrorKind::DesignatorMustBePositiveIntLiteral( + lit.span, + )); + None + } + } else { + self.push_semantic_error(SemanticErrorKind::DesignatorMustBePositiveIntLiteral( + expr.span, + )); + None + } + } + + fn get_semantic_type_from_scalar_ty( + &mut self, + scalar_ty: &crate::ast::ScalarType, + is_const: bool, + ) -> Option { + match &scalar_ty.kind { + crate::ast::ScalarTypeKind::Bit(bit_type) => match &bit_type.size { + Some(size) => Some(crate::semantic::types::Type::BitArray( + super::types::ArrayDimensions::One(self.get_size_designator_from_expr(size)?), + is_const, + )), + None => Some(crate::semantic::types::Type::Bit(is_const)), + }, + crate::ast::ScalarTypeKind::Int(int_type) => match &int_type.size { + Some(size) => Some(crate::semantic::types::Type::Int( + Some(self.get_size_designator_from_expr(size)?), + is_const, + )), + None => Some(crate::semantic::types::Type::Int(None, is_const)), + }, + crate::ast::ScalarTypeKind::UInt(uint_type) => match &uint_type.size { + Some(size) => Some(crate::semantic::types::Type::UInt( + Some(self.get_size_designator_from_expr(size)?), + is_const, + )), + None => Some(crate::semantic::types::Type::UInt(None, is_const)), + }, + crate::ast::ScalarTypeKind::Float(float_type) => match &float_type.size { + Some(size) => Some(crate::semantic::types::Type::Float( + Some(self.get_size_designator_from_expr(size)?), + is_const, + )), + None => Some(crate::semantic::types::Type::Float(None, is_const)), + }, + crate::ast::ScalarTypeKind::Complex(complex_type) => match &complex_type.base_size { + Some(float_type) => match &float_type.size { + Some(size) => Some(crate::semantic::types::Type::Complex( + Some(self.get_size_designator_from_expr(size)?), + is_const, + )), + None => Some(crate::semantic::types::Type::Complex(None, is_const)), + }, + None => Some(crate::semantic::types::Type::Complex(None, is_const)), + }, + crate::ast::ScalarTypeKind::Angle(angle_type) => match &angle_type.size { + Some(size) => Some(crate::semantic::types::Type::Angle( + Some(self.get_size_designator_from_expr(size)?), + is_const, + )), + None => Some(crate::semantic::types::Type::Angle(None, is_const)), + }, + crate::ast::ScalarTypeKind::BoolType => { + Some(crate::semantic::types::Type::Bool(is_const)) + } + crate::ast::ScalarTypeKind::Duration => { + Some(crate::semantic::types::Type::Duration(is_const)) + } + crate::ast::ScalarTypeKind::Stretch => { + Some(crate::semantic::types::Type::Stretch(is_const)) + } + crate::ast::ScalarTypeKind::Err => Some(crate::semantic::types::Type::Err), + } + } + + fn get_semantic_type_from_array_ty( + &mut self, + array_ty: &crate::ast::ArrayType, + _is_const: bool, + ) -> Option { + self.push_unimplemented_error_message("semantic type from array type", array_ty.span); + None + } + + fn get_semantic_type_from_array_reference_ty( + &mut self, + array_ref_ty: &crate::ast::ArrayReferenceType, + _is_const: bool, + ) -> Option { + self.push_unimplemented_error_message( + "semantic type from array refence type", + array_ref_ty.span, + ); + None + } + fn lower_expr_with_target_type( + &mut self, + expr: Option<&crate::ast::Expr>, + ty: &Type, + span: Span, + ) -> Option { + let Some(expr) = expr else { + // In OpenQASM, classical variables may be uninitialized, but in Q#, + // they must be initialized. We will use the default value for the type + // to initialize the variable. + return self.get_default_value(ty, span); + }; + let rhs = self.lower_expr(expr)?; + let rhs_ty = rhs.ty.clone(); + // if we have an exact type match, we can use the rhs as is + if types_equal_except_const(ty, &rhs_ty) { + return Some(rhs); + } + + // 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 super::ast::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 None; + } + // the lhs has a type, but the rhs may be of a different type with + // implicit and explicit conversions. We need to cast the rhs to the + // lhs type, but if that cast fails, we will have already pushed an error + // and we can't proceed + self.cast_expr_to_type(ty, &rhs, span) + } + + fn lower_measure_expr_with_target_type( + &mut self, + _expr: &crate::ast::MeasureExpr, + _ty: &Type, + span: Span, + ) -> Option { + self.push_unimplemented_error_message("measure expr with target type", span); + None + } + + fn get_default_value(&mut self, ty: &Type, span: Span) -> Option { + use super::ast::Expr; + use super::ast::ExprKind; + use super::ast::LiteralKind; + let from_lit_kind = |kind| -> Expr { + Expr { + span: Span::default(), + kind: Box::new(ExprKind::Lit(kind)), + ty: ty.as_const(), + } + }; + match ty { + Type::Bit(_) | Type::Int(_, _) | Type::UInt(_, _) => { + Some(from_lit_kind(LiteralKind::Int(0))) + } + Type::Bool(_) => Some(from_lit_kind(LiteralKind::Bool(false))), + Type::Angle(_, _) | 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(_, _) => { + self.push_unimplemented_error_message("bit array default value", span); + None + } + 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::Duration(_) + | Type::Err + | Type::Gate(_, _) + | Type::Range + | Type::Set + | Type::Void => { + let message = format!("Default values for {ty:?} are unsupported."); + self.push_unsupported_error_message(message, span); + None + } + } + } + + #[allow(clippy::too_many_lines)] + fn coerce_literal_expr_to_type( + &mut self, + ty: &Type, + rhs: &super::ast::Expr, + kind: &super::ast::LiteralKind, + ) -> Option { + if *ty == rhs.ty { + // Base case, we shouldn't have gotten here + // but if we did, we can just return the rhs + return Some(rhs.clone()); + } + if types_equal_except_const(ty, &rhs.ty) { + // literals are always const, so we can safely return + // the const ty + return Some(super::ast::Expr { + span: rhs.span, + kind: rhs.kind.clone(), + ty: ty.as_const(), + }); + } + 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 super::ast::LiteralKind::Int(value) = kind { + // can_cast_literal_with_value_knowledge guarantees that value is 0 or 1 + return Some(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::Lit(super::ast::LiteralKind::Int( + *value, + ))), + ty: lhs_ty.as_const(), + }); + } else if let super::ast::LiteralKind::Bool(value) = kind { + return Some(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::Lit(super::ast::LiteralKind::Int( + i64::from(*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 super::ast::LiteralKind::Int(value) = kind { + if *value < 0 || *value >= (1 << size) { + // todo: error message + 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 { + // todo: error message + return None; + }; + + return Some(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::Lit( + super::ast::LiteralKind::Bitstring(value, size), + )), + ty: lhs_ty.as_const(), + }); + } + } + if matches!(lhs_ty, Type::UInt(..)) { + if let super::ast::LiteralKind::Int(value) = kind { + // this should have been validated by can_cast_literal_with_value_knowledge + return Some(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::Lit(super::ast::LiteralKind::Int( + *value, + ))), + ty: lhs_ty.as_const(), + }); + } + } + let result = match (&lhs_ty, &rhs_ty) { + (Type::Float(..), Type::Int(..) | Type::UInt(..)) => { + if let super::ast::LiteralKind::Int(value) = kind { + if let Some(value) = safe_i64_to_f64(*value) { + return Some(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::Lit( + super::ast::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::Float(..), Type::Float(..)) => { + if let super::ast::LiteralKind::Float(value) = kind { + return Some(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::Lit(super::ast::LiteralKind::Float( + *value, + ))), + ty: lhs_ty.as_const(), + }); + } + None + } + (Type::Complex(..), Type::Complex(..)) => { + if let super::ast::LiteralKind::Complex(real, imag) = kind { + return Some(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::Lit( + super::ast::LiteralKind::Complex(*real, *imag), + )), + ty: lhs_ty.as_const(), + }); + } + None + } + (Type::Complex(..), Type::Float(..)) => { + if let super::ast::LiteralKind::Float(value) = kind { + return Some(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::Lit( + super::ast::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 super::ast::LiteralKind::Int(value) = kind { + if let Some(value) = safe_i64_to_f64(*value) { + return Some(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::Lit( + super::ast::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 super::ast::LiteralKind::Int(value) = kind { + if *value == 0 || *value == 1 { + return Some(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::Lit( + super::ast::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 { + super::ast::LiteralKind::Int(value) => { + return Some(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::Lit( + super::ast::LiteralKind::Int(*value), + )), + ty: lhs_ty.as_const(), + }); + } + super::ast::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(super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::Lit( + super::ast::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 + } + } + + // Rules for negating literals are different than that of expressions + // What those rules are is not clear from the spec, so this is a best guess + // based on other qasm implementations. + fn lower_negated_literal_as_ty( + &mut self, + lit: &crate::ast::Lit, + target_ty: Option, + span: Span, + ) -> Option { + let (kind, ty) = (match &lit.kind { + crate::ast::LiteralKind::Float(value) => Some(( + super::ast::LiteralKind::Float(-value), + Type::Float(None, true), + )), + crate::ast::LiteralKind::Imaginary(value) => Some(( + super::ast::LiteralKind::Complex(0.0, -value), + Type::Complex(None, true), + )), + crate::ast::LiteralKind::Int(value) => { + Some((super::ast::LiteralKind::Int(-value), Type::Int(None, true))) + } + crate::ast::LiteralKind::BigInt(value) => { + let value = BigInt::from(-1) * value; + Some(( + super::ast::LiteralKind::BigInt(value), + Type::Int(None, true), + )) + } + crate::ast::LiteralKind::Duration(value, time_unit) => { + let unit = match time_unit { + crate::ast::TimeUnit::Dt => super::ast::TimeUnit::Dt, + crate::ast::TimeUnit::Ms => super::ast::TimeUnit::Ms, + crate::ast::TimeUnit::Ns => super::ast::TimeUnit::Ns, + crate::ast::TimeUnit::S => super::ast::TimeUnit::S, + crate::ast::TimeUnit::Us => super::ast::TimeUnit::Us, + }; + Some(( + super::ast::LiteralKind::Duration(-value, unit), + Type::Duration(true), + )) + } + crate::ast::LiteralKind::Array(_) => { + self.push_unsupported_error_message("negated array literal expressions", span); + None + } + crate::ast::LiteralKind::Bitstring(_, _) => { + self.push_unsupported_error_message("negated bitstring literal expressions", span); + None + } + crate::ast::LiteralKind::Bool(_) => { + self.push_unsupported_error_message("negated bool literal expressions", span); + None + } + crate::ast::LiteralKind::String(_) => { + self.push_unsupported_error_message("negated string literal expressions", span); + None + } + })?; + + let expr = super::ast::Expr { + span, + kind: Box::new(super::ast::ExprKind::Lit(kind.clone())), + ty, + }; + if let Some(target_ty) = target_ty { + return self.coerce_literal_expr_to_type(&target_ty, &expr, &kind); + } + Some(expr) + } + + fn cast_expr_to_type( + &mut self, + ty: &Type, + rhs: &super::ast::Expr, + span: Span, + ) -> Option { + let cast_expr = self.try_cast_expr_to_type(ty, rhs, span); + if cast_expr.is_none() { + let rhs_ty_name = format!("{:?}", rhs.ty); + let lhs_ty_name = format!("{ty:?}"); + let kind = SemanticErrorKind::CannotCast(rhs_ty_name, lhs_ty_name, span); + self.push_semantic_error(kind); + } + cast_expr + } + + fn try_cast_expr_to_type( + &mut self, + ty: &Type, + rhs: &super::ast::Expr, + span: Span, + ) -> Option { + if *ty == rhs.ty { + // Base case, we shouldn't have gotten here + // but if we did, we can just return the rhs + return Some(rhs.clone()); + } + if types_equal_except_const(ty, &rhs.ty) { + if rhs.ty.is_const() { + // lhs isn't const, but rhs is, we can just return the rhs + return Some(rhs.clone()); + } + // the lsh is supposed to be const but is being initialized + // to a non-const value, we can't allow this + return None; + } + // if the target type is wider, we can try to relax the rhs type + // We only do this for float and complex. Int types + // require extra handling for BigInts + match (ty, &rhs.ty) { + (Type::Float(w1, _), Type::Float(w2, _)) + | (Type::Complex(w1, _), Type::Complex(w2, _)) => { + if w1.is_none() && w2.is_some() { + return Some(super::ast::Expr { + span: rhs.span, + kind: rhs.kind.clone(), + ty: ty.clone(), + }); + } + + if *w1 >= *w2 { + return Some(super::ast::Expr { + span: rhs.span, + kind: rhs.kind.clone(), + ty: ty.clone(), + }); + } + } + _ => {} + } + // Casting of literals is handled elsewhere. This is for casting expressions + // which cannot be bypassed and must be handled by built-in Q# casts from + // the standard library. + match &rhs.ty { + Type::Angle(_, _) => self.cast_angle_expr_to_type(ty, rhs, span), + Type::Bit(_) => self.cast_bit_expr_to_type(ty, rhs, span), + Type::Bool(_) => Self::cast_bool_expr_to_type(ty, rhs), + Type::Complex(_, _) => cast_complex_expr_to_type(ty, rhs), + Type::Float(_, _) => self.cast_float_expr_to_type(ty, rhs, span), + Type::Int(_, _) | Type::UInt(_, _) => Self::cast_int_expr_to_type(ty, rhs), + Type::BitArray(dims, _) => Self::cast_bitarray_expr_to_type(dims, ty, rhs), + _ => None, + } + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | angle | Yes | No | No | No | - | Yes | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + fn cast_angle_expr_to_type( + &mut self, + ty: &Type, + rhs: &super::ast::Expr, + span: Span, + ) -> Option { + assert!(matches!(rhs.ty, Type::Bit(..))); + match ty { + Type::Bit(..) => { + let msg = "Cast angle to bit"; + self.push_unimplemented_error_message(msg, span); + None + } + Type::Bool(..) => { + let msg = "Cast angle to bool"; + self.push_unimplemented_error_message(msg, span); + None + } + + _ => None, + } + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | bit | Yes | Yes | Yes | No | Yes | - | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + fn cast_bit_expr_to_type( + &mut self, + ty: &Type, + rhs: &super::ast::Expr, + span: Span, + ) -> 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::Angle(..) => { + let msg = "Cast bit to angle"; + self.push_unimplemented_error_message(msg, span); + None + } + &Type::Float(..) => { + // The spec says that this cast isn't supported, but it + // casts to other types that case to float. For now, we'll + // say it is invalid like the spec. + None + } + &Type::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( + &mut self, + ty: &Type, + rhs: &super::ast::Expr, + span: Span, + ) -> Option { + assert!(matches!(rhs.ty, Type::Float(..))); + match ty { + &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())) + } + &Type::Angle(..) => { + let msg = "Cast float to angle"; + self.push_unimplemented_error_message(msg, span); + None + } + _ => 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: &super::ast::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: &super::ast::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: &super::ast::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 + } + } +} + +fn wrap_expr_in_implicit_cast_expr(ty: Type, rhs: super::ast::Expr) -> super::ast::Expr { + super::ast::Expr { + span: Span::default(), + kind: Box::new(super::ast::ExprKind::Cast(super::ast::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: &super::ast::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: &crate::ast::Identifier) -> std::rc::Rc { + match identifier { + crate::ast::Identifier::Ident(ident) => ident.name.clone(), + crate::ast::Identifier::IndexedIdent(ident) => ident.name.name.clone(), + } +} diff --git a/compiler/qsc_qasm3/src/semantic/symbols.rs b/compiler/qsc_qasm3/src/semantic/symbols.rs new file mode 100644 index 0000000000..a9ebfe99a2 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/symbols.rs @@ -0,0 +1,451 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use qsc_data_structures::span::Span; +use rustc_hash::FxHashMap; + +use super::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, PartialEq, Eq)] +pub struct Symbol { + pub name: String, + pub span: Span, + pub ty: Type, + pub qsharp_ty: crate::types::Type, + pub io_kind: IOKind, +} + +impl std::fmt::Display for Symbol { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + use crate::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(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SymbolError { + /// The symbol already exists in the symbol table, at the current scope. + AlreadyExists, +} + +/// Symbols have a an I/O kind that determines if they are input or output, or unspecified. +/// The default I/O kind means no explicit kind was part of the decl. +/// There is a specific statement for io decls which sets the I/O kind appropriately. +/// This is used to determine the input and output symbols in the program. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum IOKind { + Default, + Input, + Output, +} + +impl Default for IOKind { + fn default() -> Self { + Self::Default + } +} + +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: Symbol) -> Result<(), SymbolError> { + if self.name_to_id.contains_key(&symbol.name) { + return Err(SymbolError::AlreadyExists); + } + self.name_to_id.insert(symbol.name.clone(), id); + self.id_to_symbol.insert(id, symbol); + self.order.push(id); + Ok(()) + } + + pub fn get_symbol_by_name(&self, name: &str) -> Option<(SymbolId, &Symbol)> { + self.name_to_id + .get(name) + .and_then(|id| self.id_to_symbol.get(id).map(|s| (*id, s))) + } + + fn get_ordered_symbols(&self) -> Vec { + self.order + .iter() + .map(|id| self.id_to_symbol.get(id).expect("ID should exist").clone()) + .collect() + } +} + +/// A symbol table is a collection of scopes and manages the symbol ids. +pub struct SymbolTable { + scopes: Vec, + current_id: SymbolId, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ScopeKind { + /// Global scope, which is the current scope only when no other scopes are active. + /// This is the only scope where gates, qubits, and arrays can be declared. + Global, + Function, + Gate, + Block, +} + +const BUILTIN_SYMBOLS: [&str; 6] = ["pi", "π", "tau", "τ", "euler", "ℇ"]; + +impl Default for SymbolTable { + fn default() -> Self { + let global = Scope::new(ScopeKind::Global); + + let mut slf = Self { + scopes: vec![global], + current_id: SymbolId::default(), + }; + + // Define global constants + for symbol in BUILTIN_SYMBOLS { + slf.insert_symbol(Symbol { + name: symbol.to_string(), + span: Span::default(), + ty: Type::Float(None, true), + qsharp_ty: crate::types::Type::Double(true), + io_kind: IOKind::Default, + }) + .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 id = self.current_id; + self.current_id = self.current_id.successor(); + self.scopes + .last_mut() + .expect("At least one scope should be available") + .insert_symbol(id, symbol)?; + + Ok(id) + } + + #[must_use] + pub fn get_symbol_by_id(&self, id: SymbolId) -> Option<(SymbolId, &Symbol)> { + for scope in self.scopes.iter().rev() { + if let Some(symbol) = scope.id_to_symbol.get(&id) { + return Some((id, symbol)); + } + } + None + } + + #[must_use] + pub fn get_symbol_by_name(&self, name: &str) -> Option<(SymbolId, &Symbol)> { + let scopes = self.scopes.iter().rev(); + let predicate = |x: &Scope| { + x.kind == ScopeKind::Block || x.kind == ScopeKind::Function || x.kind == ScopeKind::Gate + }; + + // Use scan to track the last item that returned false + let mut last_false = None; + let _ = scopes + .scan(&mut last_false, |state, item| { + if !predicate(item) { + **state = Some(item); + } + Some(predicate(item)) + }) + .take_while(|&x| x) + .last(); + let mut scopes = self.scopes.iter().rev(); + while let Some(scope) = scopes + .by_ref() + .take_while(|arg0: &&Scope| predicate(arg0)) + .next() + { + if let Some((id, symbol)) = scope.get_symbol_by_name(name) { + return Some((id, symbol)); + } + } + + if let Some(scope) = last_false { + if let Some((id, symbol)) = scope.get_symbol_by_name(name) { + if symbol.ty.is_const() + || matches!(symbol.ty, Type::Gate(..) | Type::Void) + || self.is_scope_rooted_in_global() + { + return Some((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) { + if symbol.ty.is_const() || matches!(symbol.ty, Type::Gate(..) | Type::Void) { + return Some((id, symbol)); + } + } + } + + None + } + + #[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| scope.kind == ScopeKind::Function) + } + + #[must_use] + pub fn is_scope_rooted_in_global(&self) -> bool { + for scope in self.scopes.iter().rev() { + if scope.kind == ScopeKind::Function { + return false; + } + if scope.kind == ScopeKind::Gate { + return false; + } + } + true + } + + /// Get the input symbols in the program. + pub(crate) fn get_input(&self) -> Option> { + let io_input = self.get_io_input(); + if io_input.is_empty() { + None + } else { + Some(io_input) + } + } + + /// Get the output symbols in the program. + /// Output symbols are either inferred or explicitly declared. + /// If there are no explicitly declared output symbols, then the inferred + /// output symbols are returned. + pub(crate) fn get_output(&self) -> Option> { + let io_ouput = self.get_io_output(); + if io_ouput.is_some() { + io_ouput + } else { + self.get_inferred_output() + } + } + + /// Get all symbols in the global scope that are inferred output symbols. + /// Any global symbol that is not a built-in symbol and has a type that is + /// inferred to be an output type is considered an inferred output symbol. + fn get_inferred_output(&self) -> Option> { + let mut symbols = vec![]; + self.scopes + .iter() + .filter(|scope| scope.kind == ScopeKind::Global) + .for_each(|scope| { + for symbol in scope + .get_ordered_symbols() + .into_iter() + .filter(|symbol| !BUILTIN_SYMBOLS.contains(&symbol.name.as_str())) + .filter(|symbol| symbol.io_kind == IOKind::Default) + { + if 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 + } +} diff --git a/compiler/qsc_qasm3/src/semantic/tests.rs b/compiler/qsc_qasm3/src/semantic/tests.rs new file mode 100644 index 0000000000..8aa9e00e81 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests.rs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +pub mod decls; + +use std::path::Path; +use std::sync::Arc; + +use crate::io::InMemorySourceResolver; +use crate::io::SourceResolver; + +use super::parse_source; + +use super::QasmSemanticParseResult; + +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 source = resolver.resolve(path.as_ref()).map_err(|e| vec![e])?.1; + let res = parse_source(source, path, &resolver).map_err(|e| vec![e])?; + if res.source.has_errors() { + let errors = res + .errors() + .into_iter() + .map(|e| Report::new(e.clone())) + .collect(); + Err(errors) + } else { + Ok(res) + } +} + +pub(crate) fn parse(source: S) -> miette::Result> +where + S: AsRef, +{ + let resolver = InMemorySourceResolver::from_iter([("test".into(), source.as_ref().into())]); + let res = parse_source(source, "test", &resolver).map_err(|e| vec![e])?; + if res.source.has_errors() { + let errors = res + .errors() + .into_iter() + .map(|e| Report::new(e.clone())) + .collect(); + return Err(errors); + } + Ok(res) +} + +pub(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, |p, s| { + let kind = p + .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 = s + .get_symbol_by_id(decl.symbol_id) + .expect("getting symbol by id"); + value.push_str(&format!("[{}] {}", symbol.0, symbol.1)); + value + }); +} + +pub(super) fn check_classical_decls(input: &str, expect: &Expect) { + check_map(input, expect, |p, s| { + let kinds = p + .statements + .iter() + .map(|stmt| stmt.kind.as_ref().clone()) + .collect::>(); + let mut value = String::new(); + for kind in kinds { + let super::ast::StmtKind::ClassicalDecl(decl) = kind else { + panic!("expected classical declaration statement"); + }; + value.push_str(&decl.to_string()); + value.push('\n'); + let symbol = s + .get_symbol_by_id(decl.symbol_id) + .expect("getting symbol by id"); + value.push_str(&format!("[{}] {}", symbol.0, symbol.1)); + 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::SymbolTable) -> String, +) where + S: AsRef, +{ + let resolver = InMemorySourceResolver::from_iter([("test".into(), input.as_ref().into())]); + let res = parse_source(input, "test", &resolver) + .map_err(|e| vec![e]) + .expect("failed to parse"); + let errors = res.all_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::>() + )); + } +} 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..f5f926e956 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/decls.rs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +mod bit; +mod bool; +mod complex; +mod creg; +mod duration; +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: + + [Qsc.Qasm3.Compile.DesignatorMustBePositiveIntLiteral + + x Designator must be a positive literal integer. + ,-[test:1:5] + 1 | int[-5] i; + : ^^ + `---- + ]"#]], + ); +} + +#[test] +fn scalar_ty_designator_must_be_int_literal() { + check( + r#"int[size] i; float[0.0] j;"#, + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.DesignatorMustBePositiveIntLiteral + + x Designator must be a positive literal integer. + ,-[test:1:5] + 1 | int[size] i; float[0.0] j; + : ^^^^ + `---- + , Qsc.Qasm3.Compile.DesignatorMustBePositiveIntLiteral + + x Designator must be a positive literal integer. + ,-[test:1:20] + 1 | int[size] i; float[0.0] j; + : ^^^ + `---- + ]"#]], + ); +} 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..dc29049bf5 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/bit.rs @@ -0,0 +1,123 @@ +// 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: 6 + ty_span: [0-3] + init_expr: Expr [0-0]: + ty: Bit(true) + kind: Lit: Int(0) + [6] Symbol [4-5]: + name: a + type: Bit(false) + qsharp_type: Result + io_kind: Default"#]], + ); +} + +#[test] +#[ignore = "Unimplemented"] +fn array_with_no_init_expr_has_generated_lit_expr() { + check_classical_decl( + "bit[4] a;", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: bit array + | default value + ,-[test:1:1] + 1 | bit[4] a; + : ^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn decl_with_lit_0_init_expr() { + check_classical_decl( + "bit a = 0;", + &expect![[r#" + ClassicalDeclarationStmt [0-10]: + symbol_id: 6 + ty_span: [0-3] + init_expr: Expr [8-9]: + ty: Bit(true) + kind: Lit: Int(0) + [6] 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: 6 + ty_span: [0-3] + init_expr: Expr [8-9]: + ty: Bit(true) + kind: Lit: Int(1) + [6] 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: 6 + ty_span: [6-9] + init_expr: Expr [14-15]: + ty: Bit(true) + kind: Lit: Int(0) + [6] 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: 6 + ty_span: [6-9] + init_expr: Expr [14-15]: + ty: Bit(true) + kind: Lit: Int(1) + [6] 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..d6b7dc7ea3 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/bool.rs @@ -0,0 +1,123 @@ +// 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: 6 + ty_span: [0-4] + init_expr: Expr [0-0]: + ty: Bool(true) + kind: Lit: Bool(false) + [6] 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( + "bool[4] a;", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: bool array + | default value + ,-[test:1:1] + 1 | bool[4] a; + : ^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn decl_with_lit_false_init_expr() { + check_classical_decl( + "bool a = false;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 6 + ty_span: [0-4] + init_expr: Expr [9-14]: + ty: Bool(true) + kind: Lit: Bool(false) + [6] 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: 6 + ty_span: [0-4] + init_expr: Expr [9-13]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] 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: 6 + ty_span: [6-10] + init_expr: Expr [15-20]: + ty: Bool(true) + kind: Lit: Bool(false) + [6] 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: 6 + ty_span: [6-10] + init_expr: Expr [15-19]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] 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..62c3a38ad8 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs @@ -0,0 +1,393 @@ +// 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: 6 + ty_span: [0-14] + init_expr: Expr [0-0]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 0.0) + [6] Symbol [15-16]: + name: x + type: Complex(None, false) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_default() { + check_classical_decl( + "const complex[float] x;", + &expect![[r#" + Program: + version: + statements: + + [Qasm3.Parse.Token + + x expected `=`, found `;` + ,-[test:1:23] + 1 | const complex[float] x; + : ^ + `---- + , Qsc.Qasm3.Compile.UnexpectedParserError + + x Unexpected parser error: Unexpected error. + ,-[test:1:1] + 1 | const complex[float] x; + : ^^^^^^^^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn explicit_bitness_default() { + check_classical_decl( + "complex[float[42]] x;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 6 + ty_span: [0-18] + init_expr: Expr [0-0]: + ty: Complex(Some(42), true) + kind: Lit: Complex(0.0, 0.0) + [6] Symbol [19-20]: + name: x + type: Complex(Some(42), false) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +fn const_explicit_bitness_default() { + check_classical_decl( + "const complex[float[42]] x;", + &expect![[r#" + Program: + version: + statements: + + [Qasm3.Parse.Token + + x expected `=`, found `;` + ,-[test:1:27] + 1 | const complex[float[42]] x; + : ^ + `---- + , Qsc.Qasm3.Compile.UnexpectedParserError + + x Unexpected parser error: Unexpected error. + ,-[test:1:1] + 1 | const complex[float[42]] x; + : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn const_implicit_bitness_double_img_only() { + check_classical_decl( + "const complex[float] x = 1.01im;", + &expect![[r#" + ClassicalDeclarationStmt [0-32]: + symbol_id: 6 + ty_span: [6-20] + init_expr: Expr [25-31]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 1.01) + [6] 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: 6 + ty_span: [6-20] + init_expr: Expr [25-28]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 1.0) + [6] 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: 6 + ty_span: [6-24] + init_expr: Expr [29-35]: + ty: Complex(Some(42), true) + kind: Lit: Complex(0.0, 1.01) + [6] 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: 6 + ty_span: [6-24] + init_expr: Expr [29-32]: + ty: Complex(Some(42), true) + kind: Lit: Complex(0.0, 1.0) + [6] 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: 6 + ty_span: [0-14] + init_expr: Expr [19-25]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 1.01) + [6] 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: 6 + ty_span: [0-14] + init_expr: Expr [19-22]: + ty: Complex(None, true) + kind: Lit: Complex(0.0, 1.0) + [6] 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: 6 + ty_span: [6-20] + init_expr: Expr [25-29]: + ty: Complex(None, true) + kind: Lit: Complex(1.01, 0.0) + [6] 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: 6 + ty_span: [6-20] + init_expr: Expr [25-26]: + ty: Complex(None, true) + kind: Lit: Complex(1.0, 0.0) + [6] 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: 6 + ty_span: [0-14] + init_expr: Expr [19-23]: + ty: Complex(None, true) + kind: Lit: Complex(1.01, 0.0) + [6] 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: 6 + ty_span: [0-14] + init_expr: Expr [19-20]: + ty: Complex(None, true) + kind: Lit: Complex(1.0, 0.0) + [6] Symbol [15-16]: + name: x + type: Complex(None, false) + qsharp_type: Complex + io_kind: Default"#]], + ); +} + +#[test] +#[ignore = "Requires support for binary operators"] +fn implicit_bitness_simple_double_pos_im() { + check_classical_decl( + "complex[float] x = 1.1 + 2.2im;", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: binary op expr + ,-[test:1:20] + 1 | complex[float] x = 1.1 + 2.2im; + : ^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +#[ignore = "Requires support for binary operators"] +fn implicit_bitness_simple_double_neg_im() { + check_classical_decl( + "complex[float] x = 1.1 - 2.2im;", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: binary op expr + ,-[test:1:20] + 1 | complex[float] x = 1.1 - 2.2im; + : ^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +#[ignore = "Requires support for binary operators"] +fn const_implicit_bitness_simple_double_neg_im() { + check_classical_decl( + "const complex[float] x = 1.1 - 2.2im;", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: binary op expr + ,-[test:1:26] + 1 | const complex[float] x = 1.1 - 2.2im; + : ^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +#[ignore = "Requires support for binary operators"] +fn implicit_bitness_simple_double_neg_real() { + check_classical_decl( + "complex[float] x = -1.1 + 2.2im;", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: binary op expr + ,-[test:1:20] + 1 | complex[float] x = -1.1 + 2.2im; + : ^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +#[ignore = "Requires support for binary operators"] +fn const_implicit_bitness_simple_double_neg_real() { + check_classical_decl( + "const complex[float] x = -1.1 + 2.2im;", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: binary op expr + ,-[test:1:26] + 1 | const complex[float] x = -1.1 + 2.2im; + : ^^^^^^^^^^^^ + `---- + ]"#]], + ); +} 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..1f7014b74e --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/creg.rs @@ -0,0 +1,47 @@ +// 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: 6 + ty_span: [0-7] + init_expr: Expr [0-0]: + ty: Bit(true) + kind: Lit: Int(0) + [6] Symbol [5-6]: + name: a + type: Bit(false) + qsharp_type: Result + io_kind: Default"#]], + ); +} + +#[test] +#[ignore = "Unimplemented"] +fn array_with_no_init_expr_has_generated_lit_expr() { + check_classical_decl( + "creg a[4];", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: bit array + | default value + ,-[test:1:1] + 1 | creg a[4]; + : ^^^^^^^^^^ + `---- + ]"#]], + ); +} 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..a28b2a4110 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/duration.rs @@ -0,0 +1,26 @@ +// 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: + + [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/float.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs new file mode 100644 index 0000000000..7975ff3a9e --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs @@ -0,0 +1,499 @@ +// 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: 6 + ty_span: [0-5] + init_expr: Expr [0-0]: + ty: Float(None, true) + kind: Lit: Float(0.0) + [6] Symbol [6-7]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_default() { + check_classical_decl( + "const float x;", + &expect![[r#" + Program: + version: + statements: + + [Qasm3.Parse.Token + + x expected `=`, found `;` + ,-[test:1:14] + 1 | const float x; + : ^ + `---- + , Qsc.Qasm3.Compile.UnexpectedParserError + + x Unexpected parser error: Unexpected error. + ,-[test:1:1] + 1 | const float x; + : ^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn lit() { + check_classical_decl( + "float x = 42.1;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 6 + ty_span: [0-5] + init_expr: Expr [10-14]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [6] 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: 6 + ty_span: [6-11] + init_expr: Expr [16-20]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [6] 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: 6 + ty_span: [0-9] + init_expr: Expr [14-18]: + ty: Float(Some(64), true) + kind: Lit: Float(42.1) + [6] 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: 6 + ty_span: [6-15] + init_expr: Expr [20-24]: + ty: Float(Some(64), true) + kind: Lit: Float(42.1) + [6] 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: 6 + ty_span: [0-5] + init_expr: Expr [10-14]: + ty: Float(None, true) + kind: Lit: Float(0.421) + [6] 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: 6 + ty_span: [6-11] + init_expr: Expr [16-20]: + ty: Float(None, true) + kind: Lit: Float(0.421) + [6] 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: 6 + ty_span: [6-11] + init_expr: Expr [16-22]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [6] 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: 6 + ty_span: [0-5] + init_expr: Expr [10-14]: + ty: Float(None, true) + kind: Lit: Float(421.0) + [6] 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: 6 + ty_span: [6-11] + init_expr: Expr [16-20]: + ty: Float(None, true) + kind: Lit: Float(421.0) + [6] 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: 6 + ty_span: [0-5] + init_expr: Expr [10-16]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [6] 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: 6 + ty_span: [6-11] + init_expr: Expr [16-22]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [6] 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: 6 + ty_span: [0-5] + init_expr: Expr [10-17]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [6] 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: 6 + ty_span: [6-11] + init_expr: Expr [16-23]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [6] 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: 6 + ty_span: [0-5] + init_expr: Expr [10-16]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [6] 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: 6 + ty_span: [6-11] + init_expr: Expr [16-22]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [6] 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: 6 + ty_span: [0-5] + init_expr: Expr [10-18]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [6] 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: 6 + ty_span: [6-11] + init_expr: Expr [16-24]: + ty: Float(None, true) + kind: Lit: Float(42.1) + [6] 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: 6 + ty_span: [6-11] + init_expr: Expr [17-19]: + ty: Float(None, true) + kind: Lit: Float(-7.0) + [6] 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: 6 + ty_span: [6-11] + init_expr: Expr [17-18]: + ty: Float(None, true) + kind: Lit: Float(-7.0) + [6] 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: 6 + ty_span: [0-5] + init_expr: Expr [10-26]: + ty: Float(None, true) + kind: Lit: Float(9007199254740992.0) + [6] 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: + + [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; + : ^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[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: 6 + ty_span: [0-5] + init_expr: Expr [11-27]: + ty: Float(None, true) + kind: Lit: Float(-9007199254740992.0) + [6] Symbol [6-7]: + name: a + type: Float(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn init_float_with_int_value_less_than_safely_representable_values() { + let min_exact_int = -(2i64.pow(f64::MANTISSA_DIGITS)); + let next = min_exact_int - 1; + check_classical_decl( + &format!("float a = {next};"), + &expect![[r#" + Program: + version: + statements: + + [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:12] + 1 | float a = -9007199254740993; + : ^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} 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..7a0f38a21c --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs @@ -0,0 +1,423 @@ +// 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: 6 + ty_span: [0-3] + init_expr: Expr [9-11]: + ty: Int(None, true) + kind: Lit: Int(-42) + [6] 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: 6 + ty_span: [6-9] + init_expr: Expr [15-17]: + ty: Int(None, true) + kind: Lit: Int(-42) + [6] 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: 6 + ty_span: [0-3] + init_expr: Expr [0-0]: + ty: Int(None, true) + kind: Lit: Int(0) + [6] Symbol [4-5]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_default() { + check_classical_decl( + "const int x;", + &expect![[r#" + Program: + version: + statements: + + [Qasm3.Parse.Token + + x expected `=`, found `;` + ,-[test:1:12] + 1 | const int x; + : ^ + `---- + , Qsc.Qasm3.Compile.UnexpectedParserError + + x Unexpected parser error: Unexpected error. + ,-[test:1:1] + 1 | const int x; + : ^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_lit() { + check_classical_decl( + "const int x = 42;", + &expect![[r#" + ClassicalDeclarationStmt [0-17]: + symbol_id: 6 + ty_span: [6-9] + init_expr: Expr [14-16]: + ty: Int(None, true) + kind: Lit: Int(42) + [6] 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: 6 + ty_span: [0-3] + init_expr: Expr [8-15]: + ty: Int(None, true) + kind: Lit: Int(64031) + [6] 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: 6 + ty_span: [6-9] + init_expr: Expr [14-21]: + ty: Int(None, true) + kind: Lit: Int(64031) + [6] 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: 6 + ty_span: [0-3] + init_expr: Expr [8-12]: + ty: Int(None, true) + kind: Lit: Int(34) + [6] 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: 6 + ty_span: [6-9] + init_expr: Expr [14-18]: + ty: Int(None, true) + kind: Lit: Int(34) + [6] 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: 6 + ty_span: [6-9] + init_expr: Expr [14-18]: + ty: Int(None, true) + kind: Lit: Int(34) + [6] 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: 6 + ty_span: [0-3] + init_expr: Expr [8-19]: + ty: Int(None, true) + kind: Lit: Int(153) + [6] 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: 6 + ty_span: [0-3] + init_expr: Expr [8-14]: + ty: Int(None, true) + kind: Lit: Int(10) + [6] 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: 6 + ty_span: [6-9] + init_expr: Expr [14-25]: + ty: Int(None, true) + kind: Lit: Int(153) + [6] 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: 6 + ty_span: [6-9] + init_expr: Expr [14-20]: + ty: Int(None, true) + kind: Lit: Int(10) + [6] 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: 6 + ty_span: [0-3] + init_expr: Expr [8-14]: + ty: Int(None, true) + kind: Lit: Int(2000) + [6] 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: 6 + ty_span: [6-9] + init_expr: Expr [14-20]: + ty: Int(None, true) + kind: Lit: Int(2000) + [6] 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: 6 + ty_span: [0-7] + init_expr: Expr [0-0]: + ty: Int(Some(10), true) + kind: Lit: Int(0) + [6] Symbol [8-9]: + name: x + type: Int(Some(10), false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_explicit_bitness_int_default() { + check_classical_decl( + "const int[10] x;", + &expect![[r#" + Program: + version: + statements: + + [Qasm3.Parse.Token + + x expected `=`, found `;` + ,-[test:1:16] + 1 | const int[10] x; + : ^ + `---- + , Qsc.Qasm3.Compile.UnexpectedParserError + + x Unexpected parser error: Unexpected error. + ,-[test:1:1] + 1 | const int[10] x; + : ^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn explicit_bitness_int() { + check_classical_decl( + "int[10] x = 42;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 6 + ty_span: [0-7] + init_expr: Expr [12-14]: + ty: Int(Some(10), true) + kind: Lit: Int(42) + [6] 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: 6 + ty_span: [6-13] + init_expr: Expr [18-20]: + ty: Int(Some(10), true) + kind: Lit: Int(42) + [6] Symbol [14-15]: + name: x + type: Int(Some(10), true) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn implicit_bitness_int_negative_float_decl_causes_semantic_error() { + check_classical_decl( + "int x = -42.;", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.CannotAssignToType + + x Cannot assign a value of Float(None, true) type to a classical variable of + | Int(None, false) type. + ,-[test:1:1] + 1 | int x = -42.; + : ^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} 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..37fb09e2a4 --- /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] +#[ignore = "unimplemented"] +fn with_no_init_expr_has_generated_lit_expr() { + check_stmt_kind( + "qreg a;", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: qubit decl + | stmt + ,-[test:1:1] + 1 | qreg a; + : ^^^^^^^ + `---- + ]"#]], + ); +} 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..488931a1d5 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/stretch.rs @@ -0,0 +1,26 @@ +// 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: + + [Qsc.Qasm3.Compile.NotSupported + + x Stretch type 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..78ffe2cc58 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs @@ -0,0 +1,427 @@ +// 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: 6 + ty_span: [0-4] + init_expr: Expr [0-0]: + ty: UInt(None, true) + kind: Lit: Int(0) + [6] Symbol [5-6]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_default() { + check_classical_decl( + "const uint x;", + &expect![[r#" + Program: + version: + statements: + + [Qasm3.Parse.Token + + x expected `=`, found `;` + ,-[test:1:13] + 1 | const uint x; + : ^ + `---- + , Qsc.Qasm3.Compile.UnexpectedParserError + + x Unexpected parser error: Unexpected error. + ,-[test:1:1] + 1 | const uint x; + : ^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn const_implicit_bitness_int_lit() { + check_classical_decl( + "const uint x = 42;", + &expect![[r#" + ClassicalDeclarationStmt [0-18]: + symbol_id: 6 + ty_span: [6-10] + init_expr: Expr [15-17]: + ty: UInt(None, true) + kind: Lit: Int(42) + [6] 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: 6 + ty_span: [0-4] + init_expr: Expr [9-16]: + ty: UInt(None, true) + kind: Lit: Int(64031) + [6] 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: 6 + ty_span: [6-10] + init_expr: Expr [15-22]: + ty: UInt(None, true) + kind: Lit: Int(64031) + [6] 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: 6 + ty_span: [6-10] + init_expr: Expr [15-22]: + ty: UInt(None, true) + kind: Lit: Int(64031) + [6] 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: 6 + ty_span: [0-4] + init_expr: Expr [9-13]: + ty: UInt(None, true) + kind: Lit: Int(34) + [6] 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: 6 + ty_span: [0-4] + init_expr: Expr [9-13]: + ty: UInt(None, true) + kind: Lit: Int(34) + [6] 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: 6 + ty_span: [6-10] + init_expr: Expr [15-19]: + ty: UInt(None, true) + kind: Lit: Int(34) + [6] 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: 6 + ty_span: [6-10] + init_expr: Expr [15-19]: + ty: UInt(None, true) + kind: Lit: Int(34) + [6] 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: 6 + ty_span: [0-4] + init_expr: Expr [9-20]: + ty: UInt(None, true) + kind: Lit: Int(153) + [6] 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: 6 + ty_span: [0-4] + init_expr: Expr [9-15]: + ty: UInt(None, true) + kind: Lit: Int(10) + [6] 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: 6 + ty_span: [6-10] + init_expr: Expr [15-26]: + ty: UInt(None, true) + kind: Lit: Int(153) + [6] 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: 6 + ty_span: [6-10] + init_expr: Expr [15-21]: + ty: UInt(None, true) + kind: Lit: Int(10) + [6] 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: 6 + ty_span: [0-4] + init_expr: Expr [9-15]: + ty: UInt(None, true) + kind: Lit: Int(2000) + [6] 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: 6 + ty_span: [6-10] + init_expr: Expr [15-21]: + ty: UInt(None, true) + kind: Lit: Int(2000) + [6] 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: 6 + ty_span: [0-8] + init_expr: Expr [0-0]: + ty: UInt(Some(10), true) + kind: Lit: Int(0) + [6] Symbol [9-10]: + name: x + type: UInt(Some(10), false) + qsharp_type: Int + io_kind: Default"#]], + ); +} + +#[test] +fn explicit_bitness_int() { + check_classical_decl( + "const uint[10] x;", + &expect![[r#" + Program: + version: + statements: + + [Qasm3.Parse.Token + + x expected `=`, found `;` + ,-[test:1:17] + 1 | const uint[10] x; + : ^ + `---- + , Qsc.Qasm3.Compile.UnexpectedParserError + + x Unexpected parser error: Unexpected error. + ,-[test:1:1] + 1 | const uint[10] x; + : ^^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn assigning_uint_to_negative_lit_results_in_semantic_error() { + check_classical_decl( + "const uint[10] x = -42;", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.CannotAssignToType + + x Cannot assign a value of Int(None, true) type to a classical variable of + | UInt(Some(10), true) type. + ,-[test:1:1] + 1 | const uint[10] x = -42; + : ^^^^^^^^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn implicit_bitness_uint_const_negative_decl_raises_semantic_error() { + check_classical_decl( + "const uint x = -42;", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.CannotAssignToType + + x Cannot assign a value of Int(None, true) type to a classical variable of + | UInt(None, true) type. + ,-[test:1:1] + 1 | const uint x = -42; + : ^^^^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn explicit_bitness_uint_const_negative_decl_raises_semantic_error() { + check_classical_decl( + "const uint[32] x = -42;", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.CannotAssignToType + + x Cannot assign a value of Int(None, true) type to a classical variable of + | UInt(Some(32), true) type. + ,-[test:1:1] + 1 | const uint[32] x = -42; + : ^^^^^^^^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/semantic/types.rs b/compiler/qsc_qasm3/src/semantic/types.rs new file mode 100644 index 0000000000..f111bef11d --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/types.rs @@ -0,0 +1,675 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::cmp::max; + +use core::fmt; +use std::fmt::{Display, Formatter}; + +use crate::ast::UnaryOp::NotL; + +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), + 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::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::BitArray(..) + | Type::QubitArray(..) + | Type::AngleArray(..) + | Type::BoolArray(..) + | Type::ComplexArray(..) + | Type::DurationArray(..) + | Type::FloatArray(..) + | Type::IntArray(..) + | 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 + ) + } + + /// 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) => match dims { + ArrayDimensions::One(_) => Type::Bit(*is_const), + ArrayDimensions::Two(d1, _) => Type::BitArray(ArrayDimensions::One(*d1), *is_const), + ArrayDimensions::Three(d1, d2, _) => { + Type::BitArray(ArrayDimensions::Two(*d1, *d2), *is_const) + } + ArrayDimensions::Four(d1, d2, d3, _) => { + Type::BitArray(ArrayDimensions::Three(*d1, *d2, *d3), *is_const) + } + ArrayDimensions::Five(d1, d2, d3, d4, _) => { + Type::BitArray(ArrayDimensions::Four(*d1, *d2, *d3, *d4), *is_const) + } + ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { + Type::BitArray(ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5), *is_const) + } + ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => Type::BitArray( + ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6), + *is_const, + ), + ArrayDimensions::Err => Type::Err, + }, + Type::QubitArray(dims) => match dims { + ArrayDimensions::One(_) => Type::Qubit, + ArrayDimensions::Two(d1, _) => Type::QubitArray(ArrayDimensions::One(*d1)), + ArrayDimensions::Three(d1, d2, _) => { + Type::QubitArray(ArrayDimensions::Two(*d1, *d2)) + } + ArrayDimensions::Four(d1, d2, d3, _) => { + Type::QubitArray(ArrayDimensions::Three(*d1, *d2, *d3)) + } + ArrayDimensions::Five(d1, d2, d3, d4, _) => { + Type::QubitArray(ArrayDimensions::Four(*d1, *d2, *d3, *d4)) + } + ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { + Type::QubitArray(ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) + } + ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { + Type::QubitArray(ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) + } + ArrayDimensions::Err => Type::Err, + }, + Type::BoolArray(dims) => match dims { + ArrayDimensions::One(_) => Type::Bool(false), + ArrayDimensions::Two(d1, _) => Type::BoolArray(ArrayDimensions::One(*d1)), + ArrayDimensions::Three(d1, d2, _) => { + Type::BoolArray(ArrayDimensions::Two(*d1, *d2)) + } + ArrayDimensions::Four(d1, d2, d3, _) => { + Type::BoolArray(ArrayDimensions::Three(*d1, *d2, *d3)) + } + ArrayDimensions::Five(d1, d2, d3, d4, _) => { + Type::BoolArray(ArrayDimensions::Four(*d1, *d2, *d3, *d4)) + } + ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { + Type::BoolArray(ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) + } + ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { + Type::BoolArray(ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) + } + ArrayDimensions::Err => Type::Err, + }, + Type::AngleArray(size, dims) => match dims { + ArrayDimensions::One(_) => Type::Angle(*size, false), + ArrayDimensions::Two(d1, _) => Type::AngleArray(*size, ArrayDimensions::One(*d1)), + ArrayDimensions::Three(d1, d2, _) => { + Type::AngleArray(*size, ArrayDimensions::Two(*d1, *d2)) + } + ArrayDimensions::Four(d1, d2, d3, _) => { + Type::AngleArray(*size, ArrayDimensions::Three(*d1, *d2, *d3)) + } + ArrayDimensions::Five(d1, d2, d3, d4, _) => { + Type::AngleArray(*size, ArrayDimensions::Four(*d1, *d2, *d3, *d4)) + } + ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { + Type::AngleArray(*size, ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) + } + ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { + Type::AngleArray(*size, ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) + } + ArrayDimensions::Err => Type::Err, + }, + Type::ComplexArray(size, dims) => match dims { + ArrayDimensions::One(_) => Type::Complex(*size, false), + ArrayDimensions::Two(d1, _) => Type::ComplexArray(*size, ArrayDimensions::One(*d1)), + ArrayDimensions::Three(d1, d2, _) => { + Type::ComplexArray(*size, ArrayDimensions::Two(*d1, *d2)) + } + ArrayDimensions::Four(d1, d2, d3, _) => { + Type::ComplexArray(*size, ArrayDimensions::Three(*d1, *d2, *d3)) + } + ArrayDimensions::Five(d1, d2, d3, d4, _) => { + Type::ComplexArray(*size, ArrayDimensions::Four(*d1, *d2, *d3, *d4)) + } + ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { + Type::ComplexArray(*size, ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) + } + ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { + Type::ComplexArray(*size, ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) + } + ArrayDimensions::Err => Type::Err, + }, + Type::DurationArray(dims) => match dims { + ArrayDimensions::One(_) => Type::Duration(false), + ArrayDimensions::Two(d1, _) => Type::DurationArray(ArrayDimensions::One(*d1)), + ArrayDimensions::Three(d1, d2, _) => { + Type::DurationArray(ArrayDimensions::Two(*d1, *d2)) + } + ArrayDimensions::Four(d1, d2, d3, _) => { + Type::DurationArray(ArrayDimensions::Three(*d1, *d2, *d3)) + } + ArrayDimensions::Five(d1, d2, d3, d4, _) => { + Type::DurationArray(ArrayDimensions::Four(*d1, *d2, *d3, *d4)) + } + ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { + Type::DurationArray(ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) + } + ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { + Type::DurationArray(ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) + } + ArrayDimensions::Err => Type::Err, + }, + Type::FloatArray(size, dims) => match dims { + ArrayDimensions::One(_) => Type::Float(*size, false), + ArrayDimensions::Two(d1, _) => Type::FloatArray(*size, ArrayDimensions::One(*d1)), + ArrayDimensions::Three(d1, d2, _) => { + Type::FloatArray(*size, ArrayDimensions::Two(*d1, *d2)) + } + ArrayDimensions::Four(d1, d2, d3, _) => { + Type::FloatArray(*size, ArrayDimensions::Three(*d1, *d2, *d3)) + } + ArrayDimensions::Five(d1, d2, d3, d4, _) => { + Type::FloatArray(*size, ArrayDimensions::Four(*d1, *d2, *d3, *d4)) + } + ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { + Type::FloatArray(*size, ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) + } + ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { + Type::FloatArray(*size, ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) + } + ArrayDimensions::Err => Type::Err, + }, + Type::IntArray(size, dims) => match dims { + ArrayDimensions::One(_) => Type::Int(*size, false), + ArrayDimensions::Two(d1, _) => Type::IntArray(*size, ArrayDimensions::One(*d1)), + ArrayDimensions::Three(d1, d2, _) => { + Type::IntArray(*size, ArrayDimensions::Two(*d1, *d2)) + } + ArrayDimensions::Four(d1, d2, d3, _) => { + Type::IntArray(*size, ArrayDimensions::Three(*d1, *d2, *d3)) + } + ArrayDimensions::Five(d1, d2, d3, d4, _) => { + Type::IntArray(*size, ArrayDimensions::Four(*d1, *d2, *d3, *d4)) + } + ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { + Type::IntArray(*size, ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) + } + ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { + Type::IntArray(*size, ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) + } + ArrayDimensions::Err => Type::Err, + }, + Type::UIntArray(size, dims) => match dims { + ArrayDimensions::One(_) => Type::UInt(*size, false), + ArrayDimensions::Two(d1, _) => Type::UIntArray(*size, ArrayDimensions::One(*d1)), + ArrayDimensions::Three(d1, d2, _) => { + Type::UIntArray(*size, ArrayDimensions::Two(*d1, *d2)) + } + ArrayDimensions::Four(d1, d2, d3, _) => { + Type::UIntArray(*size, ArrayDimensions::Three(*d1, *d2, *d3)) + } + ArrayDimensions::Five(d1, d2, d3, d4, _) => { + Type::UIntArray(*size, ArrayDimensions::Four(*d1, *d2, *d3, *d4)) + } + ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { + Type::UIntArray(*size, ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) + } + ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { + Type::UIntArray(*size, ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) + } + ArrayDimensions::Err => Type::Err, + }, + _ => 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(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +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 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"), + } + } +} + +/// 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::UInt(..) | Type::Angle(..)) { + Some(Type::UInt(ty.width(), 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::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: crate::ast::UnaryOp, ty: &Type) -> bool { + match op { + crate::ast::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, + }, + NotL => matches!(ty, Type::Bool(_)), + crate::ast::UnaryOp::Neg => { + matches!(ty, Type::Int(_, _) | Type::Float(_, _) | Type::Angle(_, _)) + } + } +} diff --git a/compiler/qsc_qasm3/src/tests/declaration/integer.rs b/compiler/qsc_qasm3/src/tests/declaration/integer.rs index e4d9e1f9a0..529b9b1231 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/integer.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/integer.rs @@ -330,51 +330,6 @@ fn const_explicit_bitness_int_decl() -> miette::Result<(), Vec> { Ok(()) } -#[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() { let source = " diff --git a/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs b/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs index f5e1695c35..736ccb6bda 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs @@ -265,3 +265,48 @@ fn const_explicit_bitness_int_decl() -> miette::Result<(), Vec> { .assert_eq(&qsharp); Ok(()) } + +#[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()); +} diff --git a/compiler/qsc_qasm3/src/types.rs b/compiler/qsc_qasm3/src/types.rs index 6b24bfd83a..046ba921d0 100644 --- a/compiler/qsc_qasm3/src/types.rs +++ b/compiler/qsc_qasm3/src/types.rs @@ -97,9 +97,8 @@ impl Complex { } } -#[allow(dead_code)] -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) enum Type { +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub enum Type { Bool(bool), BigInt(bool), Complex(bool), @@ -117,7 +116,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,6 +157,23 @@ 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 { @@ -196,6 +214,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"), } } } From ea70d7cee1724b16881b560f6231a86078a3ae21 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Sat, 8 Mar 2025 09:24:06 -0800 Subject: [PATCH 55/98] Make Assign and AssignOp valid only in expression statements (#2215) Previously assignments and binary assignments were valid in any part of the expression tree. This PR makes Assign and AssignOp valid only in expression statements. --- compiler/qsc_qasm3/src/parser/expr.rs | 24 ++----- compiler/qsc_qasm3/src/parser/expr/tests.rs | 32 --------- compiler/qsc_qasm3/src/parser/stmt.rs | 56 ++++++++++----- compiler/qsc_qasm3/src/parser/stmt/tests.rs | 1 + .../src/parser/stmt/tests/expr_stmt.rs | 71 ++++++++++++++++++ .../src/parser/stmt/tests/invalid_stmts.rs | 72 +++++++++++++++++++ 6 files changed, 188 insertions(+), 68 deletions(-) create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts.rs diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 1cc3bc7b4b..902bad0eaa 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -13,10 +13,10 @@ use qsc_data_structures::span::Span; use crate::{ ast::{ - self, list_from_iter, AssignExpr, AssignOpExpr, BinOp, BinaryOpExpr, Cast, DiscreteSet, - Expr, ExprKind, FunctionCall, GateOperand, HardwareQubit, Ident, IndexElement, IndexExpr, - IndexSet, IndexSetItem, IndexedIdent, List, Lit, LiteralKind, MeasureExpr, RangeDefinition, - TimeUnit, TypeDef, UnaryOp, ValueExpression, Version, + self, list_from_iter, BinOp, BinaryOpExpr, Cast, DiscreteSet, Expr, ExprKind, FunctionCall, + GateOperand, HardwareQubit, Ident, IndexElement, IndexExpr, IndexSet, IndexSetItem, + IndexedIdent, List, Lit, LiteralKind, MeasureExpr, RangeDefinition, TimeUnit, TypeDef, + UnaryOp, ValueExpression, Version, }, keyword::Keyword, lex::{ @@ -49,8 +49,6 @@ struct InfixOp { } enum OpKind { - Assign, - AssignBinary(BinOp), Binary(BinOp, Assoc), Funcall, Index, @@ -120,14 +118,6 @@ fn expr_op_with_lhs(s: &mut ParserContext, context: OpContext, mut lhs: Expr) -> s.advance(); let kind = match op.kind { - OpKind::Assign => { - let rhs = expr_op(s, OpContext::Precedence(op.precedence))?; - Box::new(ExprKind::Assign(AssignExpr { lhs, rhs })) - } - OpKind::AssignBinary(kind) => { - let rhs = expr_op(s, OpContext::Precedence(op.precedence))?; - Box::new(ExprKind::AssignOp(AssignOpExpr { op: kind, lhs, rhs })) - } OpKind::Binary(kind, assoc) => { let precedence = next_precedence(op.precedence, assoc); let rhs = expr_op(s, OpContext::Precedence(precedence))?; @@ -645,15 +635,11 @@ fn infix_op(name: OpName) -> Option { kind: OpKind::Index, precedence: 13, }), - TokenKind::Eq => Some(InfixOp { - kind: OpKind::Assign, - precedence: 0, - }), _ => None, } } -fn closed_bin_op(op: ClosedBinOp) -> BinOp { +pub(crate) fn closed_bin_op(op: ClosedBinOp) -> BinOp { match op { ClosedBinOp::Amp => BinOp::AndB, ClosedBinOp::AmpAmp => BinOp::AndL, diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index 1e8cec4ac5..64c050a3b1 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -1112,38 +1112,6 @@ fn lit_array() { ); } -#[test] -fn assignment_and_unop() { - check_expr( - "c = a && !b", - &expect![[r#" - Expr [0-11]: AssignExpr: - lhs: Expr [0-1]: Ident [0-1] "c" - 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_expr( - "d = !a && b", - &expect![[r#" - Expr [0-11]: AssignExpr: - lhs: Expr [0-1]: Ident [0-1] "d" - 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""#]], - ); -} - #[test] fn hardware_qubit() { check( diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 680c192d73..6a79e964c7 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -17,16 +17,16 @@ use super::{ use crate::{ ast::{ list_from_iter, AccessControl, AliasDeclStmt, AngleType, Annotation, ArrayBaseTypeKind, - ArrayReferenceType, ArrayType, ArrayTypedParameter, 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, IntType, - List, LiteralKind, MeasureStmt, Pragma, QuantumGateDefinition, QuantumGateModifier, - QuantumTypedParameter, QubitDeclaration, RangeDefinition, ResetStmt, ReturnStmt, - ScalarType, ScalarTypeKind, ScalarTypedParameter, Stmt, StmtKind, SwitchCase, SwitchStmt, - TypeDef, TypedParameter, UIntType, WhileLoop, + ArrayReferenceType, ArrayType, ArrayTypedParameter, AssignExpr, 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, IntType, List, LiteralKind, MeasureStmt, Pragma, QuantumGateDefinition, + QuantumGateModifier, QuantumTypedParameter, QubitDeclaration, RangeDefinition, ResetStmt, + ReturnStmt, ScalarType, ScalarTypeKind, ScalarTypedParameter, Stmt, StmtKind, SwitchCase, + SwitchStmt, TypeDef, TypedParameter, UIntType, WhileLoop, }, keyword::Keyword, lex::{cooked::Type, Delim, TokenKind}, @@ -1095,10 +1095,30 @@ fn parse_end_stmt(s: &mut ParserContext) -> Result { Ok(EndStmt { span: s.span(lo) }) } -/// Grammar: `expression SEMICOLON`. +/// Grammar: `(expression | assignExpr | AssignOpExpr) SEMICOLON`. fn parse_expression_stmt(s: &mut ParserContext, lhs: Option) -> Result { let expr = if let Some(lhs) = lhs { - expr::expr_with_lhs(s, lhs)? + if opt(s, |s| token(s, TokenKind::Eq))?.is_some() { + let rhs = expr::expr(s)?; + Expr { + span: s.span(lhs.span.lo), + kind: Box::new(ExprKind::Assign(AssignExpr { lhs, rhs })), + } + } else if let TokenKind::BinOpEq(op) = s.peek().kind { + s.advance(); + let op = expr::closed_bin_op(op); + let rhs = expr::expr(s)?; + Expr { + span: s.span(lhs.span.lo), + kind: Box::new(ExprKind::AssignOp(crate::ast::AssignOpExpr { + op, + lhs, + rhs, + })), + } + } else { + expr::expr_with_lhs(s, lhs)? + } } else { expr::expr(s)? }; @@ -1237,16 +1257,18 @@ fn parse_gate_call_stmt(s: &mut ParserContext) -> Result { let mut duration = opt(s, designator)?; let qubits = gate_operand_list(s)?; - recovering_semi(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(ExprStmt { - span: s.span(lo), - expr: gate_or_expr, - })); + 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), diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests.rs b/compiler/qsc_qasm3/src/parser/stmt/tests.rs index 0e3a2f74a9..5b6e25c336 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests.rs @@ -19,6 +19,7 @@ mod gate_def; mod gphase; mod if_stmt; mod include; +mod invalid_stmts; mod io_decl; mod measure; mod old_style_decl; diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs index d5f55416e4..a039a335b7 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs @@ -34,6 +34,77 @@ fn identifier_plus_number() { ); } +#[test] +fn assignment() { + check( + parse, + "a = 1;", + &expect![[r#" + Stmt [0-6]: + annotations: + kind: ExprStmt [0-6]: + expr: Expr [0-5]: AssignExpr: + lhs: Expr [0-1]: Ident [0-1] "a" + rhs: Expr [4-5]: Lit: Int(1)"#]], + ); +} + +#[test] +fn binary_assignment() { + check( + parse, + "a += 1;", + &expect![[r#" + Stmt [0-7]: + annotations: + kind: ExprStmt [0-7]: + expr: Expr [0-6]: AssignOpExpr: + op: Add + lhs: Expr [0-1]: Ident [0-1] "a" + rhs: Expr [5-6]: Lit: Int(1)"#]], + ); +} + +#[test] +fn assignment_and_unop() { + check( + parse, + "c = a && !b;", + &expect![[r#" + Stmt [0-12]: + annotations: + kind: ExprStmt [0-12]: + expr: Expr [0-11]: AssignExpr: + lhs: Expr [0-1]: Ident [0-1] "c" + 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: ExprStmt [0-12]: + expr: Expr [0-11]: AssignExpr: + lhs: Expr [0-1]: Ident [0-1] "d" + 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] 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..db14c8a438 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts.rs @@ -0,0 +1,72 @@ +use crate::parser::{stmt::parse, tests::check}; +use expect_test::expect; + +#[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_block: + Stmt [13-15]: + annotations: + kind: ExprStmt [13-15]: + expr: Expr [13-14]: Lit: Int(3) + else_block: + + [ + 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_block: + Stmt [14-16]: + annotations: + kind: ExprStmt [14-16]: + expr: Expr [14-15]: Lit: Int(3) + else_block: + + [ + Error( + Token( + Close( + Paren, + ), + BinOpEq( + Plus, + ), + Span { + lo: 6, + hi: 8, + }, + ), + ), + ]"#]], + ); +} From 5480691259df48f6b5ecb46392af57dece97ec94 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Mon, 10 Mar 2025 22:01:24 -0700 Subject: [PATCH 56/98] Make assignment a StmtKind (#2216) This PR turns assignment into a StmtKind (it was a ExprKind before). --- compiler/qsc_qasm3/src/ast.rs | 26 +-- compiler/qsc_qasm3/src/parser/error.rs | 4 + compiler/qsc_qasm3/src/parser/expr.rs | 2 +- compiler/qsc_qasm3/src/parser/expr/tests.rs | 16 +- compiler/qsc_qasm3/src/parser/stmt.rs | 146 +++++++++++--- .../qsc_qasm3/src/parser/stmt/tests/alias.rs | 4 +- .../src/parser/stmt/tests/expr_stmt.rs | 180 ++++++++++++++---- .../src/parser/stmt/tests/for_loops.rs | 63 +++--- .../src/parser/stmt/tests/gate_call.rs | 31 ++- .../src/parser/stmt/tests/if_stmt.rs | 63 +++--- .../src/parser/stmt/tests/while_loops.rs | 36 ++-- compiler/qsc_qasm3/src/semantic/ast.rs | 22 ++- compiler/qsc_qasm3/src/semantic/lowerer.rs | 27 ++- 13 files changed, 443 insertions(+), 177 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/ast.rs index d53088a362..c45bc40bae 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/ast.rs @@ -312,6 +312,8 @@ impl Display for AliasDeclStmt { #[derive(Clone, Debug, Default)] pub enum StmtKind { Alias(AliasDeclStmt), + Assign(AssignStmt), + AssignOp(AssignOpStmt), Barrier(BarrierStmt), Box(BoxStmt), Break(BreakStmt), @@ -351,6 +353,8 @@ pub enum StmtKind { 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}"), @@ -1432,8 +1436,6 @@ impl Display for SwitchCase { #[derive(Clone, Debug, Default)] pub enum ExprKind { - Assign(AssignExpr), - AssignOp(AssignOpExpr), /// An expression with invalid syntax that can't be parsed. #[default] Err, @@ -1458,37 +1460,37 @@ impl Display for ExprKind { ExprKind::FunctionCall(call) => write!(f, "{call}"), ExprKind::Cast(expr) => write!(f, "{expr}"), ExprKind::IndexExpr(expr) => write!(f, "{expr}"), - ExprKind::Assign(expr) => write!(f, "{expr}"), - ExprKind::AssignOp(expr) => write!(f, "{expr}"), ExprKind::Paren(expr) => write!(f, "Paren {expr}"), } } } #[derive(Clone, Debug)] -pub struct AssignExpr { - pub lhs: Expr, +pub struct AssignStmt { + pub span: Span, + pub lhs: IndexedIdent, pub rhs: Expr, } -impl Display for AssignExpr { +impl Display for AssignStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - writeln!(f, "AssignExpr:")?; + writeln_header(f, "AssignStmt", self.span)?; writeln_field(f, "lhs", &self.lhs)?; write_field(f, "rhs", &self.rhs) } } #[derive(Clone, Debug)] -pub struct AssignOpExpr { +pub struct AssignOpStmt { + pub span: Span, pub op: BinOp, - pub lhs: Expr, + pub lhs: IndexedIdent, pub rhs: Expr, } -impl Display for AssignOpExpr { +impl Display for AssignOpStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - writeln!(f, "AssignOpExpr:")?; + writeln_header(f, "AssignOpStmt", self.span)?; writeln_field(f, "op", &self.op)?; writeln_field(f, "lhs", &self.lhs)?; write_field(f, "rhs", &self.rhs) diff --git a/compiler/qsc_qasm3/src/parser/error.rs b/compiler/qsc_qasm3/src/parser/error.rs index ce4d35dfe6..fbba90d248 100644 --- a/compiler/qsc_qasm3/src/parser/error.rs +++ b/compiler/qsc_qasm3/src/parser/error.rs @@ -134,6 +134,9 @@ pub enum ErrorKind { #[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), } impl ErrorKind { @@ -156,6 +159,7 @@ impl ErrorKind { 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), } } } diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 902bad0eaa..b9b082310f 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -468,7 +468,7 @@ fn cast_op(s: &mut ParserContext, r#type: TypeDef) -> Result { } fn index_expr(s: &mut ParserContext, lhs: Expr) -> Result { - let lo = s.span(0).hi - 1; + let lo = lhs.span.lo; let index = index_element(s)?; recovering_token(s, TokenKind::Close(Delim::Bracket)); Ok(ExprKind::IndexExpr(IndexExpr { diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index 64c050a3b1..a14716f10c 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -953,7 +953,7 @@ fn index_expr() { check_expr( "foo[1]", &expect![[r#" - Expr [0-6]: IndexExpr [3-6]: + Expr [0-6]: IndexExpr [0-6]: collection: Expr [0-3]: Ident [0-3] "foo" index: IndexSet [4-5]: values: @@ -966,7 +966,7 @@ fn index_set() { check_expr( "foo[{1, 4, 5}]", &expect![[r#" - Expr [0-14]: IndexExpr [3-14]: + Expr [0-14]: IndexExpr [0-14]: collection: Expr [0-3]: Ident [0-3] "foo" index: DiscreteSet [4-13]: values: @@ -981,7 +981,7 @@ fn index_multiple_ranges() { check_expr( "foo[1:5, 3:7, 4:8]", &expect![[r#" - Expr [0-18]: IndexExpr [3-18]: + Expr [0-18]: IndexExpr [0-18]: collection: Expr [0-3]: Ident [0-3] "foo" index: IndexSet [4-17]: values: @@ -1005,7 +1005,7 @@ fn index_range() { check_expr( "foo[1:5:2]", &expect![[r#" - Expr [0-10]: IndexExpr [3-10]: + Expr [0-10]: IndexExpr [0-10]: collection: Expr [0-3]: Ident [0-3] "foo" index: IndexSet [4-9]: values: @@ -1021,7 +1021,7 @@ fn index_full_range() { check_expr( "foo[:]", &expect![[r#" - Expr [0-6]: IndexExpr [3-6]: + Expr [0-6]: IndexExpr [0-6]: collection: Expr [0-3]: Ident [0-3] "foo" index: IndexSet [4-5]: values: @@ -1037,7 +1037,7 @@ fn index_range_start() { check_expr( "foo[1:]", &expect![[r#" - Expr [0-7]: IndexExpr [3-7]: + Expr [0-7]: IndexExpr [0-7]: collection: Expr [0-3]: Ident [0-3] "foo" index: IndexSet [4-6]: values: @@ -1053,7 +1053,7 @@ fn index_range_end() { check_expr( "foo[:5]", &expect![[r#" - Expr [0-7]: IndexExpr [3-7]: + Expr [0-7]: IndexExpr [0-7]: collection: Expr [0-3]: Ident [0-3] "foo" index: IndexSet [4-6]: values: @@ -1069,7 +1069,7 @@ fn index_range_step() { check_expr( "foo[:2:]", &expect![[r#" - Expr [0-8]: IndexExpr [3-8]: + Expr [0-8]: IndexExpr [0-8]: collection: Expr [0-3]: Ident [0-3] "foo" index: IndexSet [4-7]: values: diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 6a79e964c7..5cd51becaa 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -17,8 +17,8 @@ use super::{ use crate::{ ast::{ list_from_iter, AccessControl, AliasDeclStmt, AngleType, Annotation, ArrayBaseTypeKind, - ArrayReferenceType, ArrayType, ArrayTypedParameter, AssignExpr, BarrierStmt, BitType, - Block, BoxStmt, BreakStmt, CalibrationGrammarStmt, CalibrationStmt, Cast, + 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, @@ -107,6 +107,81 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { Box::new(StmtKind::Break(stmt)) } else if let Some(stmt) = opt(s, parse_end_stmt)? { Box::new(StmtKind::End(stmt)) + } else if let Some(indexed_ident) = opt(s, indexed_identifier)? { + if s.peek().kind == TokenKind::Eq { + s.advance(); + let expr = expr::expr(s)?; + recovering_semi(s); + Box::new(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(s)?; + recovering_semi(s); + Box::new(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)?; + + Box::new(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), + }; + + Box::new(parse_gate_call_with_expr(s, expr)?) + } } else if let Some(stmt_kind) = opt(s, parse_gate_call_stmt)? { Box::new(stmt_kind) } else if let Some(stmt) = opt(s, |s| parse_expression_stmt(s, None))? { @@ -1098,27 +1173,7 @@ fn parse_end_stmt(s: &mut ParserContext) -> Result { /// Grammar: `(expression | assignExpr | AssignOpExpr) SEMICOLON`. fn parse_expression_stmt(s: &mut ParserContext, lhs: Option) -> Result { let expr = if let Some(lhs) = lhs { - if opt(s, |s| token(s, TokenKind::Eq))?.is_some() { - let rhs = expr::expr(s)?; - Expr { - span: s.span(lhs.span.lo), - kind: Box::new(ExprKind::Assign(AssignExpr { lhs, rhs })), - } - } else if let TokenKind::BinOpEq(op) = s.peek().kind { - s.advance(); - let op = expr::closed_bin_op(op); - let rhs = expr::expr(s)?; - Expr { - span: s.span(lhs.span.lo), - kind: Box::new(ExprKind::AssignOp(crate::ast::AssignOpExpr { - op, - lhs, - rhs, - })), - } - } else { - expr::expr_with_lhs(s, lhs)? - } + expr::expr_with_lhs(s, lhs)? } else { expr::expr(s)? }; @@ -1296,6 +1351,51 @@ fn parse_gate_call_stmt(s: &mut ParserContext) -> Result { })) } +/// 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] diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs index 9a918a3296..3c6d47ec8a 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/alias.rs @@ -31,7 +31,7 @@ fn concatenation_alias() { kind: AliasDeclStmt [0-32]: ident: Ident [4-5] "x" exprs: - Expr [8-14]: IndexExpr [9-14]: + Expr [8-14]: IndexExpr [8-14]: collection: Expr [8-9]: Ident [8-9] "a" index: IndexSet [10-13]: values: @@ -40,7 +40,7 @@ fn concatenation_alias() { step: end: Expr [12-13]: Lit: Int(2) Expr [18-19]: Ident [18-19] "b" - Expr [23-31]: IndexExpr [24-31]: + Expr [23-31]: IndexExpr [23-31]: collection: Expr [23-24]: Ident [23-24] "c" index: IndexSet [25-30]: values: diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs index a039a335b7..5fd49612b6 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs @@ -40,31 +40,117 @@ fn assignment() { parse, "a = 1;", &expect![[r#" - Stmt [0-6]: - annotations: - kind: ExprStmt [0-6]: - expr: Expr [0-5]: AssignExpr: - lhs: Expr [0-1]: Ident [0-1] "a" + Stmt [0-6]: + annotations: + kind: AssignStmt [0-6]: + lhs: IndexedIdent [0-1]: + name: Ident [0-1] "a" + indices: rhs: Expr [4-5]: Lit: Int(1)"#]], ); } #[test] -fn binary_assignment() { +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" + 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" + 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: ExprStmt [0-7]: - expr: Expr [0-6]: AssignOpExpr: + Stmt [0-7]: + annotations: + kind: AssignOpStmt [0-7]: op: Add - lhs: Expr [0-1]: Ident [0-1] "a" + lhs: IndexedIdent [0-1]: + name: Ident [0-1] "a" + 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" + 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" + 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( @@ -73,15 +159,16 @@ fn assignment_and_unop() { &expect![[r#" Stmt [0-12]: annotations: - kind: ExprStmt [0-12]: - expr: Expr [0-11]: AssignExpr: - lhs: Expr [0-1]: Ident [0-1] "c" - 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""#]], + kind: AssignStmt [0-12]: + lhs: IndexedIdent [0-1]: + name: Ident [0-1] "c" + 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""#]], ); } @@ -93,15 +180,16 @@ fn assignment_unop_and() { &expect![[r#" Stmt [0-12]: annotations: - kind: ExprStmt [0-12]: - expr: Expr [0-11]: AssignExpr: - lhs: Expr [0-1]: Ident [0-1] "d" - 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""#]], + kind: AssignStmt [0-12]: + lhs: IndexedIdent [0-1]: + name: Ident [0-1] "d" + 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""#]], ); } @@ -153,7 +241,7 @@ fn indexed_function_call() { Stmt [0-14]: annotations: kind: ExprStmt [0-14]: - expr: Expr [0-13]: IndexExpr [10-13]: + expr: Expr [0-13]: IndexExpr [0-13]: collection: Expr [0-10]: FunctionCall [0-10]: name: Ident [0-4] "Name" args: @@ -174,7 +262,7 @@ fn multi_indexed_function_call() { Stmt [0-17]: annotations: kind: ExprStmt [0-17]: - expr: Expr [0-16]: IndexExpr [10-16]: + expr: Expr [0-16]: IndexExpr [0-16]: collection: Expr [0-10]: FunctionCall [0-10]: name: Ident [0-4] "Name" args: @@ -209,7 +297,7 @@ fn index_expr() { Stmt [0-8]: annotations: kind: ExprStmt [0-8]: - expr: Expr [0-7]: IndexExpr [4-7]: + expr: Expr [0-7]: IndexExpr [0-7]: collection: Expr [0-4]: Ident [0-4] "Name" index: IndexSet [5-6]: values: @@ -217,6 +305,34 @@ fn index_expr() { ); } +#[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( diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs index 8d6bf920b7..30239971b5 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs @@ -27,10 +27,11 @@ fn simple_for_loop() { block: Stmt [38-44]: annotations: - kind: ExprStmt [38-44]: - expr: Expr [38-43]: AssignExpr: - lhs: Expr [38-39]: Ident [38-39] "a" - rhs: Expr [42-43]: Lit: Int(0)"#]], + kind: AssignStmt [38-44]: + lhs: IndexedIdent [38-39]: + name: Ident [38-39] "a" + indices: + rhs: Expr [42-43]: Lit: Int(0)"#]], ); } @@ -75,10 +76,11 @@ fn simple_for_loop_stmt_body() { block: Stmt [36-42]: annotations: - kind: ExprStmt [36-42]: - expr: Expr [36-41]: AssignExpr: - lhs: Expr [36-37]: Ident [36-37] "a" - rhs: Expr [40-41]: Lit: Int(0)"#]], + kind: AssignStmt [36-42]: + lhs: IndexedIdent [36-37]: + name: Ident [36-37] "a" + indices: + rhs: Expr [40-41]: Lit: Int(0)"#]], ); } @@ -104,10 +106,11 @@ fn for_loop_range() { block: Stmt [36-42]: annotations: - kind: ExprStmt [36-42]: - expr: Expr [36-41]: AssignExpr: - lhs: Expr [36-37]: Ident [36-37] "a" - rhs: Expr [40-41]: Lit: Int(0)"#]], + kind: AssignStmt [36-42]: + lhs: IndexedIdent [36-37]: + name: Ident [36-37] "a" + indices: + rhs: Expr [40-41]: Lit: Int(0)"#]], ); } @@ -133,10 +136,11 @@ fn for_loop_range_no_step() { block: Stmt [34-40]: annotations: - kind: ExprStmt [34-40]: - expr: Expr [34-39]: AssignExpr: - lhs: Expr [34-35]: Ident [34-35] "a" - rhs: Expr [38-39]: Lit: Int(0)"#]], + kind: AssignStmt [34-40]: + lhs: IndexedIdent [34-35]: + name: Ident [34-35] "a" + indices: + rhs: Expr [38-39]: Lit: Int(0)"#]], ); } @@ -159,10 +163,11 @@ fn for_loop_expr() { block: Stmt [31-37]: annotations: - kind: ExprStmt [31-37]: - expr: Expr [31-36]: AssignExpr: - lhs: Expr [31-32]: Ident [31-32] "a" - rhs: Expr [35-36]: Lit: Int(0)"#]], + kind: AssignStmt [31-37]: + lhs: IndexedIdent [31-32]: + name: Ident [31-32] "a" + indices: + rhs: Expr [35-36]: Lit: Int(0)"#]], ); } @@ -190,10 +195,11 @@ fn for_loop_with_continue_stmt() { block: Stmt [38-44]: annotations: - kind: ExprStmt [38-44]: - expr: Expr [38-43]: AssignExpr: - lhs: Expr [38-39]: Ident [38-39] "a" - rhs: Expr [42-43]: Lit: Int(0) + kind: AssignStmt [38-44]: + lhs: IndexedIdent [38-39]: + name: Ident [38-39] "a" + indices: + rhs: Expr [42-43]: Lit: Int(0) Stmt [53-62]: annotations: kind: ContinueStmt [53-62]"#]], @@ -224,10 +230,11 @@ fn for_loop_with_break_stmt() { block: Stmt [38-44]: annotations: - kind: ExprStmt [38-44]: - expr: Expr [38-43]: AssignExpr: - lhs: Expr [38-39]: Ident [38-39] "a" - rhs: Expr [42-43]: Lit: Int(0) + kind: AssignStmt [38-44]: + lhs: IndexedIdent [38-39]: + name: Ident [38-39] "a" + indices: + rhs: Expr [42-43]: Lit: Int(0) Stmt [53-59]: annotations: kind: BreakStmt [53-59]"#]], diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs index 03e4a43334..78de9c02c0 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs @@ -252,7 +252,7 @@ fn multi_indexed_gate_call() { Error( InvalidGateCallDesignator( Span { - lo: 10, + lo: 0, hi: 16, }, ), @@ -287,14 +287,27 @@ fn gate_call_with_invalid_designator() { parse, "H[2us][3] q;", &expect![[r#" - Error( - InvalidGateCallDesignator( - Span { - lo: 6, - hi: 9, - }, + 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: + IndexedIdent [10-11]: + name: Ident [10-11] "q" + indices: + + [ + Error( + MultipleIndexOperators( + Span { + lo: 0, + hi: 9, + }, + ), ), - ) - "#]], + ]"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs index 29e8f0136d..e2c242942a 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs @@ -26,17 +26,19 @@ fn simple_if_stmt() { if_block: Stmt [27-33]: annotations: - kind: ExprStmt [27-33]: - expr: Expr [27-32]: AssignExpr: - lhs: Expr [27-28]: Ident [27-28] "a" - rhs: Expr [31-32]: Lit: Int(0) + kind: AssignStmt [27-33]: + lhs: IndexedIdent [27-28]: + name: Ident [27-28] "a" + indices: + rhs: Expr [31-32]: Lit: Int(0) else_block: Stmt [55-61]: annotations: - kind: ExprStmt [55-61]: - expr: Expr [55-60]: AssignExpr: - lhs: Expr [55-56]: Ident [55-56] "a" - rhs: Expr [59-60]: Lit: Int(1)"#]], + kind: AssignStmt [55-61]: + lhs: IndexedIdent [55-56]: + name: Ident [55-56] "a" + indices: + rhs: Expr [59-60]: Lit: Int(1)"#]], ); } @@ -60,10 +62,11 @@ fn if_stmt_missing_else() { if_block: Stmt [27-33]: annotations: - kind: ExprStmt [27-33]: - expr: Expr [27-32]: AssignExpr: - lhs: Expr [27-28]: Ident [27-28] "a" - rhs: Expr [31-32]: Lit: Int(0) + kind: AssignStmt [27-33]: + lhs: IndexedIdent [27-28]: + name: Ident [27-28] "a" + indices: + rhs: Expr [31-32]: Lit: Int(0) else_block: "#]], ); } @@ -106,17 +109,19 @@ fn nested_if_stmts() { if_block: Stmt [55-61]: annotations: - kind: ExprStmt [55-61]: - expr: Expr [55-60]: AssignExpr: - lhs: Expr [55-56]: Ident [55-56] "a" - rhs: Expr [59-60]: Lit: Int(0) + kind: AssignStmt [55-61]: + lhs: IndexedIdent [55-56]: + name: Ident [55-56] "a" + indices: + rhs: Expr [59-60]: Lit: Int(0) else_block: Stmt [91-97]: annotations: - kind: ExprStmt [91-97]: - expr: Expr [91-96]: AssignExpr: - lhs: Expr [91-92]: Ident [91-92] "a" - rhs: Expr [95-96]: Lit: Int(1) + kind: AssignStmt [91-97]: + lhs: IndexedIdent [91-92]: + name: Ident [91-92] "a" + indices: + rhs: Expr [95-96]: Lit: Int(1) else_block: Stmt [129-209]: annotations: @@ -128,16 +133,18 @@ fn nested_if_stmts() { if_block: Stmt [157-163]: annotations: - kind: ExprStmt [157-163]: - expr: Expr [157-162]: AssignExpr: - lhs: Expr [157-158]: Ident [157-158] "a" - rhs: Expr [161-162]: Lit: Int(2) + kind: AssignStmt [157-163]: + lhs: IndexedIdent [157-158]: + name: Ident [157-158] "a" + indices: + rhs: Expr [161-162]: Lit: Int(2) else_block: Stmt [193-199]: annotations: - kind: ExprStmt [193-199]: - expr: Expr [193-198]: AssignExpr: - lhs: Expr [193-194]: Ident [193-194] "a" - rhs: Expr [197-198]: Lit: Int(3)"#]], + kind: AssignStmt [193-199]: + lhs: IndexedIdent [193-194]: + name: Ident [193-194] "a" + indices: + rhs: Expr [197-198]: Lit: Int(3)"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs index 6a7cff7fde..4c5a1e0af1 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs @@ -23,10 +23,11 @@ fn simple_while() { block: Stmt [30-36]: annotations: - kind: ExprStmt [30-36]: - expr: Expr [30-35]: AssignExpr: - lhs: Expr [30-31]: Ident [30-31] "a" - rhs: Expr [34-35]: Lit: Int(0)"#]], + kind: AssignStmt [30-36]: + lhs: IndexedIdent [30-31]: + name: Ident [30-31] "a" + indices: + rhs: Expr [34-35]: Lit: Int(0)"#]], ); } @@ -62,10 +63,11 @@ fn while_stmt_body() { block: Stmt [28-34]: annotations: - kind: ExprStmt [28-34]: - expr: Expr [28-33]: AssignExpr: - lhs: Expr [28-29]: Ident [28-29] "a" - rhs: Expr [32-33]: Lit: Int(0)"#]], + kind: AssignStmt [28-34]: + lhs: IndexedIdent [28-29]: + name: Ident [28-29] "a" + indices: + rhs: Expr [32-33]: Lit: Int(0)"#]], ); } @@ -89,10 +91,11 @@ fn while_loop_with_continue_stmt() { block: Stmt [30-36]: annotations: - kind: ExprStmt [30-36]: - expr: Expr [30-35]: AssignExpr: - lhs: Expr [30-31]: Ident [30-31] "a" - rhs: Expr [34-35]: Lit: Int(0) + kind: AssignStmt [30-36]: + lhs: IndexedIdent [30-31]: + name: Ident [30-31] "a" + indices: + rhs: Expr [34-35]: Lit: Int(0) Stmt [45-54]: annotations: kind: ContinueStmt [45-54]"#]], @@ -119,10 +122,11 @@ fn while_loop_with_break_stmt() { block: Stmt [30-36]: annotations: - kind: ExprStmt [30-36]: - expr: Expr [30-35]: AssignExpr: - lhs: Expr [30-31]: Ident [30-31] "a" - rhs: Expr [34-35]: Lit: Int(0) + kind: AssignStmt [30-36]: + lhs: IndexedIdent [30-31]: + name: Ident [30-31] "a" + indices: + rhs: Expr [34-35]: Lit: Int(0) Stmt [45-51]: annotations: kind: BreakStmt [45-51]"#]], diff --git a/compiler/qsc_qasm3/src/semantic/ast.rs b/compiler/qsc_qasm3/src/semantic/ast.rs index 7b5d6bd8d2..dcbebbaee9 100644 --- a/compiler/qsc_qasm3/src/semantic/ast.rs +++ b/compiler/qsc_qasm3/src/semantic/ast.rs @@ -304,6 +304,8 @@ impl Display for AliasDeclStmt { #[derive(Clone, Debug, Default)] pub enum StmtKind { Alias(AliasDeclStmt), + Assign(AssignStmt), + AssignOp(AssignOpStmt), Barrier(BarrierStmt), Box(BoxStmt), Block(Box), @@ -340,6 +342,8 @@ 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}"), @@ -1368,8 +1372,6 @@ impl Display for SwitchCase { #[derive(Clone, Debug, Default)] pub enum ExprKind { - Assign(AssignExpr), - AssignOp(AssignOpExpr), /// An expression with invalid syntax that can't be parsed. #[default] Err, @@ -1394,37 +1396,37 @@ impl Display for ExprKind { ExprKind::FunctionCall(call) => write!(f, "{call}"), ExprKind::Cast(expr) => write!(f, "{expr}"), ExprKind::IndexExpr(expr) => write!(f, "{expr}"), - ExprKind::Assign(expr) => write!(f, "{expr}"), - ExprKind::AssignOp(expr) => write!(f, "{expr}"), ExprKind::Paren(expr) => write!(f, "Paren {expr}"), } } } #[derive(Clone, Debug)] -pub struct AssignExpr { +pub struct AssignStmt { + pub span: Span, pub lhs: Expr, pub rhs: Expr, } -impl Display for AssignExpr { +impl Display for AssignStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - writeln!(f, "AssignExpr:")?; + writeln_header(f, "AssignStmt", self.span)?; writeln_field(f, "lhs", &self.lhs)?; write_field(f, "rhs", &self.rhs) } } #[derive(Clone, Debug)] -pub struct AssignOpExpr { +pub struct AssignOpStmt { + pub span: Span, pub op: BinOp, pub lhs: Expr, pub rhs: Expr, } -impl Display for AssignOpExpr { +impl Display for AssignOpStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - writeln!(f, "AssignOpExpr:")?; + writeln_header(f, "AssignOpStmt", self.span)?; writeln_field(f, "op", &self.op)?; writeln_field(f, "lhs", &self.lhs)?; write_field(f, "rhs", &self.rhs) diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index b15e0a7c9d..e0976acccc 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -144,6 +144,12 @@ impl Lowerer { crate::ast::StmtKind::Alias(stmt) => { super::ast::StmtKind::Alias(self.lower_alias(stmt)?) } + crate::ast::StmtKind::Assign(stmt) => { + super::ast::StmtKind::Assign(self.lower_assign(stmt)?) + } + crate::ast::StmtKind::AssignOp(stmt) => { + super::ast::StmtKind::AssignOp(self.lower_assign_op(stmt)?) + } crate::ast::StmtKind::Barrier(stmt) => { super::ast::StmtKind::Barrier(self.lower_barrier(stmt)?) } @@ -363,16 +369,21 @@ impl Lowerer { }) } + fn lower_assign(&mut self, assign: &crate::ast::AssignStmt) -> Option { + self.push_unimplemented_error_message("assign stmt", assign.span); + None + } + + fn lower_assign_op( + &mut self, + assign_op: &crate::ast::AssignOpStmt, + ) -> Option { + self.push_unimplemented_error_message("assign op stmt", assign_op.span); + None + } + fn lower_expr(&mut self, expr: &crate::ast::Expr) -> Option { match &*expr.kind { - crate::ast::ExprKind::Assign(_) => { - self.push_unimplemented_error_message("assign expr", expr.span); - None - } - crate::ast::ExprKind::AssignOp(_) => { - self.push_unimplemented_error_message("assignop expr", expr.span); - None - } crate::ast::ExprKind::BinaryOp(_) => { self.push_unimplemented_error_message("binary op expr", expr.span); None From e68781706248bfd8a96708f5655335dbd3f263df Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Wed, 12 Mar 2025 07:30:32 -0700 Subject: [PATCH 57/98] Add grammar docstrings to QASM3 parser (#2222) This PR: 1. Fixes a bug with block statements 2. Adds grammar docstrings to the QASM3 parser. 3. Adds the invalid tests in the reference parser to make sure they are invalid for our parser as well. --- compiler/qsc_qasm3/src/parser/expr.rs | 11 + compiler/qsc_qasm3/src/parser/prgm.rs | 2 + compiler/qsc_qasm3/src/parser/stmt.rs | 342 ++++-- compiler/qsc_qasm3/src/parser/stmt/tests.rs | 1 + .../qsc_qasm3/src/parser/stmt/tests/block.rs | 44 + .../src/parser/stmt/tests/invalid_stmts.rs | 83 +- .../parser/stmt/tests/invalid_stmts/branch.rs | 205 ++++ .../parser/stmt/tests/invalid_stmts/cal.rs | 69 ++ .../stmt/tests/invalid_stmts/constant.rs | 153 +++ .../parser/stmt/tests/invalid_stmts/decl.rs | 994 ++++++++++++++++++ .../stmt/tests/invalid_stmts/gate_calls.rs | 213 ++++ .../stmt/tests/invalid_stmts/headers.rs | 231 ++++ .../src/parser/stmt/tests/invalid_stmts/io.rs | 187 ++++ .../parser/stmt/tests/invalid_stmts/loops.rs | 289 +++++ .../stmt/tests/invalid_stmts/measure.rs | 180 ++++ .../parser/stmt/tests/invalid_stmts/switch.rs | 193 ++++ .../parser/stmt/tests/invalid_stmts/tokens.rs | 298 ++++++ 17 files changed, 3312 insertions(+), 183 deletions(-) create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/block.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/branch.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/cal.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/constant.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/decl.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/gate_calls.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/headers.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/io.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/loops.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/measure.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/switch.rs create mode 100644 compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/tokens.rs diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index b9b082310f..5f679cd385 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -680,6 +680,7 @@ fn unescape(s: &str) -> std::result::Result { Ok(buf) } +/// Grammar: `LBRACKET expression RBRACKET`. pub(super) fn designator(s: &mut ParserContext) -> Result { token(s, TokenKind::Open(Delim::Bracket))?; let expr = expr(s)?; @@ -756,6 +757,7 @@ fn hardware_qubit(s: &mut ParserContext) -> Result { }) } +/// Grammar: `Identifier indexOperator*`. pub(crate) fn indexed_identifier(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; let name: Ident = ident(s)?; @@ -768,6 +770,15 @@ pub(crate) fn indexed_identifier(s: &mut ParserContext) -> Result }) } +/// 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)?; diff --git a/compiler/qsc_qasm3/src/parser/prgm.rs b/compiler/qsc_qasm3/src/parser/prgm.rs index 5de4907612..928b033c8e 100644 --- a/compiler/qsc_qasm3/src/parser/prgm.rs +++ b/compiler/qsc_qasm3/src/parser/prgm.rs @@ -13,6 +13,7 @@ use crate::{ use super::ParserContext; +/// Grammar: `version? statementOrScope* EOF`. pub(super) fn parse(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; let version = opt(s, parse_version)?; @@ -29,6 +30,7 @@ pub(super) fn parse(s: &mut ParserContext) -> Result { }) } +/// Grammar: `OPENQASM VersionSpecifier SEMICOLON`. fn parse_version(s: &mut ParserContext<'_>) -> Result { s.expect(WordKinds::OpenQASM); token(s, TokenKind::Keyword(crate::keyword::Keyword::OpenQASM))?; diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 5cd51becaa..a72c0eac9b 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -23,10 +23,10 @@ use crate::{ DelayStmt, EndStmt, EnumerableSet, Expr, ExprKind, ExprStmt, ExternDecl, ExternParameter, FloatType, ForStmt, FunctionCall, GPhase, GateCall, GateModifierKind, GateOperand, IODeclaration, IOKeyword, Ident, Identifier, IfStmt, IncludeStmt, IndexElement, IndexExpr, - IndexSetItem, IntType, List, LiteralKind, MeasureStmt, Pragma, QuantumGateDefinition, - QuantumGateModifier, QuantumTypedParameter, QubitDeclaration, RangeDefinition, ResetStmt, - ReturnStmt, ScalarType, ScalarTypeKind, ScalarTypedParameter, Stmt, StmtKind, SwitchCase, - SwitchStmt, TypeDef, TypedParameter, UIntType, WhileLoop, + IndexSetItem, IndexedIdent, IntType, List, LiteralKind, MeasureStmt, Pragma, + QuantumGateDefinition, QuantumGateModifier, QuantumTypedParameter, QubitDeclaration, + RangeDefinition, ResetStmt, ReturnStmt, ScalarType, ScalarTypeKind, ScalarTypedParameter, + Stmt, StmtKind, SwitchCase, SwitchStmt, TypeDef, TypedParameter, UIntType, WhileLoop, }, keyword::Keyword, lex::{cooked::Type, Delim, TokenKind}, @@ -34,7 +34,45 @@ use crate::{ use super::{prim::token, ParserContext}; -#[allow(clippy::too_many_lines)] +/// 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)? { @@ -45,6 +83,7 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { })); } let attrs = many(s, parse_annotation)?; + let kind = if token(s, TokenKind::Semicolon).is_ok() { if attrs.is_empty() { Box::new(StmtKind::Empty) @@ -60,25 +99,7 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { } else if let Some(include) = opt(s, parse_include)? { Box::new(include) } else if let Some(ty) = opt(s, scalar_or_array_type)? { - if matches!(s.peek().kind, TokenKind::Identifier) { - Box::new(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, - })), - }; - Box::new(StmtKind::ExprStmt(parse_expression_stmt( - s, - Some(cast_expr), - )?)) - } + Box::new(disambiguate_type(s, ty)?) } else if let Some(decl) = opt(s, parse_constant_classical_decl)? { Box::new(decl) } else if let Some(decl) = opt(s, parse_quantum_decl)? { @@ -108,80 +129,7 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { } else if let Some(stmt) = opt(s, parse_end_stmt)? { Box::new(StmtKind::End(stmt)) } else if let Some(indexed_ident) = opt(s, indexed_identifier)? { - if s.peek().kind == TokenKind::Eq { - s.advance(); - let expr = expr::expr(s)?; - recovering_semi(s); - Box::new(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(s)?; - recovering_semi(s); - Box::new(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)?; - - Box::new(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), - }; - - Box::new(parse_gate_call_with_expr(s, expr)?) - } + Box::new(disambiguate_ident(s, indexed_ident)?) } else if let Some(stmt_kind) = opt(s, parse_gate_call_stmt)? { Box::new(stmt_kind) } else if let Some(stmt) = opt(s, |s| parse_expression_stmt(s, None))? { @@ -219,13 +167,122 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { })) } +/// 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(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(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], parse) + recovering(s, default, &[TokenKind::Semicolon], parse_block_or_stmt) }) } +/// 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))?; @@ -246,6 +303,7 @@ fn default(span: Span) -> Box { }) } +/// Grammar: `AnnotationKeyword RemainingLineContent?`. pub fn parse_annotation(s: &mut ParserContext) -> Result> { let lo = s.peek().span.lo; s.expect(WordKinds::Annotation); @@ -297,6 +355,7 @@ pub fn parse_annotation(s: &mut ParserContext) -> Result> { })) } +/// Grammar: `INCLUDE StringLiteral SEMICOLON`. fn parse_include(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; token(s, TokenKind::Keyword(Keyword::Include))?; @@ -320,6 +379,7 @@ fn parse_include(s: &mut ParserContext) -> Result { ))) } +/// Grammar: `PRAGMA RemainingLineContent`. fn parse_pragma(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; s.expect(WordKinds::Pragma); @@ -367,6 +427,7 @@ fn parse_pragma(s: &mut ParserContext) -> Result { }) } +/// 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))?; @@ -385,6 +446,7 @@ fn parse_extern(s: &mut ParserContext) -> Result { 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))?; @@ -404,6 +466,7 @@ fn parse_def(s: &mut ParserContext) -> Result { Ok(kind) } +/// Grammar: `scalarType | arrayReferenceType | CREG designator?`. fn extern_arg_def(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; @@ -430,6 +493,13 @@ fn extern_arg_def(s: &mut ParserContext) -> Result { 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; @@ -483,6 +553,8 @@ fn arg_def(s: &mut ParserContext) -> Result { 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; @@ -519,6 +591,7 @@ fn array_reference_ty(s: &mut ParserContext) -> Result { }) } +/// Grammar: `ARROW scalarType`. fn return_sig(s: &mut ParserContext) -> Result { token(s, TokenKind::Arrow)?; scalar_type(s) @@ -549,6 +622,7 @@ fn gate_params(s: &mut ParserContext<'_>) -> Result> { 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))?; @@ -560,6 +634,7 @@ fn parse_return(s: &mut ParserContext) -> Result { })) } +/// Grammar: `qubitType Identifier SEMICOLON`. fn parse_quantum_decl(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; let size = qubit_type(s)?; @@ -573,12 +648,14 @@ fn parse_quantum_decl(s: &mut ParserContext) -> Result { })) } +/// 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; @@ -608,6 +685,7 @@ fn parse_io_decl(s: &mut ParserContext) -> Result { 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)); @@ -622,6 +700,7 @@ pub fn scalar_or_array_type(s: &mut ParserContext) -> Result { ))) } +/// Grammar: `(scalarType | arrayType) Identifier (EQUALS declarationExpression)? SEMICOLON`. fn parse_non_constant_classical_decl( s: &mut ParserContext, ty: TypeDef, @@ -645,6 +724,7 @@ fn parse_non_constant_classical_decl( 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))?; @@ -663,6 +743,8 @@ fn parse_constant_classical_decl(s: &mut ParserContext) -> Result { 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))?; @@ -679,6 +761,17 @@ pub(super) fn array_type(s: &mut ParserContext) -> Result { }) } +/// 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); @@ -709,6 +802,18 @@ pub(super) fn array_base_type(s: &mut ParserContext) -> Result Result { if let Ok(v) = scalar_bit_type(s) { return Ok(v); @@ -1007,6 +1112,7 @@ pub fn parse_switch_stmt(s: &mut ParserContext) -> Result { }) } +/// Grammar: `CASE expressionList scope`. fn case_stmt(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; token(s, TokenKind::Keyword(Keyword::Case))?; @@ -1025,19 +1131,30 @@ fn case_stmt(s: &mut ParserContext) -> Result { }) } +/// Grammar: `DEFAULT scope`. fn default_case_stmt(s: &mut ParserContext) -> Result { token(s, TokenKind::Keyword(Keyword::Default))?; parse_block(s).map(|block| *block) } -/// Parses a block or a statement. This is a helper function -/// to be used in loops and if stmts, in which their bodies -/// can be a block expr or a single statement. -fn parse_block_or_stmt(s: &mut ParserContext) -> Result> { +/// Grammar: `statement | scope`. +fn parse_block_or_stmt(s: &mut ParserContext) -> Result> { if let Some(block) = opt(s, parse_block)? { - Ok(block.stmts) + Ok(Box::new(Stmt { + span: block.span, + annotations: Default::default(), + kind: Box::new(StmtKind::Block(block)), + })) } else { - Ok(Box::new([parse(s)?])) + Ok(parse(s)?) + } +} + +fn into_stmt_list(stmt: Stmt) -> List { + if let StmtKind::Block(block) = *stmt.kind { + block.stmts + } else { + Box::new([Box::new(stmt)]) } } @@ -1050,9 +1167,9 @@ pub fn parse_if_stmt(s: &mut ParserContext) -> Result { let condition = expr::expr(s)?; recovering_token(s, TokenKind::Close(Delim::Paren)); - let if_block = parse_block_or_stmt(s)?; + let if_block = into_stmt_list(*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)?) + Some(into_stmt_list(*parse_block_or_stmt(s)?)) } else { None }; @@ -1109,7 +1226,8 @@ fn for_loop_iterable_expr(s: &mut ParserContext) -> Result { } } -/// Grammar: `FOR scalarType Identifier IN (setExpression | LBRACKET rangeExpression RBRACKET | expression) body=statementOrScope`. +/// Grammar: +/// `FOR scalarType Identifier IN (setExpression | LBRACKET rangeExpression RBRACKET | expression) body=statementOrScope`. /// Reference: . pub fn parse_for_loop(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; @@ -1118,7 +1236,7 @@ pub fn parse_for_loop(s: &mut ParserContext) -> Result { let identifier = Identifier::Ident(Box::new(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)?; + let block = into_stmt_list(*parse_block_or_stmt(s)?); Ok(ForStmt { span: s.span(lo), @@ -1137,7 +1255,7 @@ pub fn parse_while_loop(s: &mut ParserContext) -> Result { 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)?; + let block = into_stmt_list(*parse_block_or_stmt(s)?); Ok(WhileLoop { span: s.span(lo), @@ -1170,7 +1288,7 @@ fn parse_end_stmt(s: &mut ParserContext) -> Result { Ok(EndStmt { span: s.span(lo) }) } -/// Grammar: `(expression | assignExpr | AssignOpExpr) SEMICOLON`. +/// 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)? @@ -1439,6 +1557,8 @@ fn reinterpret_index_expr( ))) } +/// Grammar: +/// `gateModifier* GPHASE (LPAREN expressionList? RPAREN)? designator? gateOperandList? SEMICOLON` fn parse_gphase( s: &mut ParserContext, lo: u32, diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests.rs b/compiler/qsc_qasm3/src/parser/stmt/tests.rs index 5b6e25c336..1db174f83a 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests.rs @@ -4,6 +4,7 @@ mod alias; mod annotation; mod barrier; +mod block; mod box_stmt; mod cal; mod cal_grammar; 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..de70376f6b --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/block.rs @@ -0,0 +1,44 @@ +// 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" + indices: + rhs: Expr [74-75]: Lit: Int(2)"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts.rs index db14c8a438..deff9b0e90 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts.rs @@ -1,72 +1,11 @@ -use crate::parser::{stmt::parse, tests::check}; -use expect_test::expect; - -#[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_block: - Stmt [13-15]: - annotations: - kind: ExprStmt [13-15]: - expr: Expr [13-14]: Lit: Int(3) - else_block: - - [ - 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_block: - Stmt [14-16]: - annotations: - kind: ExprStmt [14-16]: - expr: Expr [14-15]: Lit: Int(3) - else_block: - - [ - Error( - Token( - Close( - Paren, - ), - BinOpEq( - Plus, - ), - Span { - lo: 6, - hi: 8, - }, - ), - ), - ]"#]], - ); -} +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..0229f5bbb4 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/branch.rs @@ -0,0 +1,205 @@ +// 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_block: + Stmt [13-15]: + annotations: + kind: ExprStmt [13-15]: + expr: Expr [13-14]: Lit: Int(3) + else_block: + + [ + 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_block: + Stmt [14-16]: + annotations: + kind: ExprStmt [14-16]: + expr: Expr [14-15]: Lit: Int(3) + else_block: + + [ + Error( + Token( + Close( + Paren, + ), + BinOpEq( + Plus, + ), + Span { + lo: 6, + hi: 8, + }, + ), + ), + ]"#]], + ); +} + +#[test] +fn empty_if_block() { + check( + parse, + "if (true);", + &expect![[r#" + Stmt [0-10]: + annotations: + kind: IfStmt [0-10]: + condition: Expr [4-8]: Lit: Bool(true) + if_block: + Stmt [9-10]: + annotations: + kind: Empty + else_block: "#]], + ); +} + +#[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..3268d1ec75 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/gate_calls.rs @@ -0,0 +1,213 @@ +// 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: + 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: + HardwareQubit [15-17]: 0 + 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: + HardwareQubit [18-20]: 0 + 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: + HardwareQubit [18-20]: 0 + 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..5258d77869 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/loops.rs @@ -0,0 +1,289 @@ +// 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) + block: + 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() { + check( + parse, + "while (true);", + &expect![[r#" + Stmt [0-13]: + annotations: + kind: WhileLoop [0-13]: + condition: Expr [7-11]: Lit: Bool(true) + block: + Stmt [12-13]: + annotations: + kind: Empty"#]], + ); +} 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..1a236c2ecb --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/measure.rs @@ -0,0 +1,180 @@ +// 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: MeasureStmt [0-10]: + measurement: MeasureExpr [0-7]: + operand: 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#" + Error( + Rule( + "expression", + Measure, + Span { + lo: 9, + hi: 16, + }, + ), + ) + "#]], + ); +} + +#[test] +fn assign_arrow() { + check( + parse, + "a = measure $0 -> b;", + &expect![[r#" + Error( + Rule( + "expression", + Measure, + Span { + lo: 4, + hi: 11, + }, + ), + ) + "#]], + ); +} + +#[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..79c685326c --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/tokens.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 bad_tokens() { + check( + parse, + "#;", + &expect![[r#" + Stmt [1-2]: + annotations: + kind: Empty + + [ + Error( + Lex( + Incomplete( + Ident, + Identifier, + Single( + Semi, + ), + 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: Empty + + [ + Error( + Lex( + Unknown( + '3', + Span { + lo: 0, + hi: 4, + }, + ), + ), + ), + ]"#]], + ); + + 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, + }, + ), + ) + "#]], + ); +} From 1c16710ec0b0953bc904e2ec1345d35821c1e3c5 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Wed, 12 Mar 2025 14:07:26 -0700 Subject: [PATCH 58/98] Implement base declarations and binary expression lowering (#2221) --- compiler/qsc_qasm3/src/lib.rs | 1 - compiler/qsc_qasm3/src/parser.rs | 5 +- compiler/qsc_qasm3/src/{ => parser}/ast.rs | 23 +- .../src/{ => parser}/ast/display_utils.rs | 0 compiler/qsc_qasm3/src/parser/expr.rs | 25 +- compiler/qsc_qasm3/src/parser/expr/tests.rs | 2 +- compiler/qsc_qasm3/src/parser/prgm.rs | 3 +- compiler/qsc_qasm3/src/parser/prim.rs | 8 +- compiler/qsc_qasm3/src/parser/prim/tests.rs | 2 +- compiler/qsc_qasm3/src/parser/stmt.rs | 26 +- compiler/qsc_qasm3/src/semantic/ast.rs | 98 +- compiler/qsc_qasm3/src/semantic/error.rs | 10 + compiler/qsc_qasm3/src/semantic/lowerer.rs | 1034 +++++++++++++---- compiler/qsc_qasm3/src/semantic/symbols.rs | 11 +- compiler/qsc_qasm3/src/semantic/tests.rs | 20 +- .../src/semantic/tests/assignment.rs | 18 + .../qsc_qasm3/src/semantic/tests/decls.rs | 1 + .../src/semantic/tests/decls/angle.rs | 415 +++++++ .../src/semantic/tests/expression.rs | 57 + .../src/semantic/tests/expression/binary.rs | 7 + .../binary/arithmetic_conversions.rs | 264 +++++ .../tests/expression/binary/comparison.rs | 53 + .../binary/comparison/bit_to_bit.rs | 542 +++++++++ .../binary/comparison/bool_to_bool.rs | 478 ++++++++ .../binary/comparison/float_to_float.rs | 336 ++++++ .../binary/comparison/int_to_int.rs | 336 ++++++ .../binary/comparison/uint_to_uint.rs | 336 ++++++ .../tests/expression/binary/complex.rs | 66 ++ .../semantic/tests/expression/binary/ident.rs | 161 +++ .../expression/implicit_cast_from_angle.rs | 556 +++++++++ .../expression/implicit_cast_from_bit.rs | 303 +++++ .../expression/implicit_cast_from_bitarray.rs | 97 ++ .../expression/implicit_cast_from_bool.rs | 326 ++++++ .../expression/implicit_cast_from_float.rs | 601 ++++++++++ .../expression/implicit_cast_from_int.rs | 442 +++++++ compiler/qsc_qasm3/src/semantic/types.rs | 199 +++- 36 files changed, 6521 insertions(+), 341 deletions(-) rename compiler/qsc_qasm3/src/{ => parser}/ast.rs (98%) rename compiler/qsc_qasm3/src/{ => parser}/ast/display_utils.rs (100%) create mode 100644 compiler/qsc_qasm3/src/semantic/tests/assignment.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/binary.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/binary/arithmetic_conversions.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison/bit_to_bit.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison/bool_to_bool.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison/float_to_float.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison/int_to_int.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison/uint_to_uint.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/binary/ident.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_angle.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_bit.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_bitarray.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_bool.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_float.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_int.rs diff --git a/compiler/qsc_qasm3/src/lib.rs b/compiler/qsc_qasm3/src/lib.rs index e67f575ec3..f046839fb2 100644 --- a/compiler/qsc_qasm3/src/lib.rs +++ b/compiler/qsc_qasm3/src/lib.rs @@ -5,7 +5,6 @@ #![allow(dead_code)] mod angle; -mod ast; mod ast_builder; mod compile; pub use compile::qasm_to_program; diff --git a/compiler/qsc_qasm3/src/parser.rs b/compiler/qsc_qasm3/src/parser.rs index bf9c2a83aa..cdd177cc0f 100644 --- a/compiler/qsc_qasm3/src/parser.rs +++ b/compiler/qsc_qasm3/src/parser.rs @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::ast::{Program, StmtKind}; +pub mod ast; use crate::io::SourceResolver; +use ast::{Program, StmtKind}; use qsc_frontend::compile::SourceMap; use qsc_frontend::error::WithSource; use scan::ParserContext; @@ -231,7 +232,7 @@ where Ok((program, errors, included)) } -fn parse_includes(program: &crate::ast::Program, resolver: &R) -> miette::Result> +fn parse_includes(program: &Program, resolver: &R) -> miette::Result> where R: SourceResolver, { diff --git a/compiler/qsc_qasm3/src/ast.rs b/compiler/qsc_qasm3/src/parser/ast.rs similarity index 98% rename from compiler/qsc_qasm3/src/ast.rs rename to compiler/qsc_qasm3/src/parser/ast.rs index c45bc40bae..03193a2d0c 100644 --- a/compiler/qsc_qasm3/src/ast.rs +++ b/compiler/qsc_qasm3/src/parser/ast.rs @@ -481,6 +481,7 @@ pub enum Identifier { } impl Identifier { + #[must_use] pub fn span(&self) -> Span { match self { Identifier::Ident(ident) => ident.span, @@ -878,6 +879,7 @@ pub enum TypeDef { } impl TypeDef { + #[must_use] pub fn span(&self) -> Span { match self { TypeDef::Scalar(ident) => ident.span, @@ -1649,6 +1651,16 @@ impl Display for IndexElement { } } +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), @@ -1680,7 +1692,7 @@ impl Display for IndexSetItem { } } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum IOKeyword { Input, Output, @@ -1695,6 +1707,15 @@ impl Display for IOKeyword { } } +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)] pub enum TimeUnit { Dt, diff --git a/compiler/qsc_qasm3/src/ast/display_utils.rs b/compiler/qsc_qasm3/src/parser/ast/display_utils.rs similarity index 100% rename from compiler/qsc_qasm3/src/ast/display_utils.rs rename to compiler/qsc_qasm3/src/parser/ast/display_utils.rs diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 5f679cd385..6e775e807a 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -12,29 +12,26 @@ use num_traits::Num; use qsc_data_structures::span::Span; use crate::{ - ast::{ - self, list_from_iter, BinOp, BinaryOpExpr, Cast, DiscreteSet, Expr, ExprKind, FunctionCall, - GateOperand, HardwareQubit, Ident, IndexElement, IndexExpr, IndexSet, IndexSetItem, - IndexedIdent, List, Lit, LiteralKind, MeasureExpr, RangeDefinition, TimeUnit, TypeDef, - UnaryOp, ValueExpression, Version, - }, keyword::Keyword, lex::{ cooked::{ComparisonOp, Literal, TimingLiteralKind}, ClosedBinOp, Delim, Radix, Token, TokenKind, }, - parser::{ - completion::WordKinds, - prim::{shorten, token}, - scan::ParserContext, - }, }; use crate::parser::Result; use super::{ + ast::{ + list_from_iter, BinOp, BinaryOpExpr, Cast, DiscreteSet, Expr, ExprKind, FunctionCall, + GateOperand, HardwareQubit, Ident, IndexElement, IndexExpr, IndexSet, IndexSetItem, + IndexedIdent, List, Lit, LiteralKind, MeasureExpr, RangeDefinition, TimeUnit, TypeDef, + UnaryOp, UnaryOpExpr, ValueExpression, Version, + }, + completion::WordKinds, error::{Error, ErrorKind}, - prim::{ident, many, opt, recovering_token, seq, FinalSep}, + prim::{ident, many, opt, recovering_token, seq, shorten, token, FinalSep}, + scan::ParserContext, stmt::scalar_or_array_type, }; @@ -94,7 +91,7 @@ fn expr_op(s: &mut ParserContext, context: OpContext) -> Result { let rhs = expr_op(s, OpContext::Precedence(op.precedence))?; Expr { span: s.span(lo), - kind: Box::new(ExprKind::UnaryOp(ast::UnaryOpExpr { + kind: Box::new(ExprKind::UnaryOp(UnaryOpExpr { op: op.kind, expr: rhs, })), @@ -444,7 +441,7 @@ pub(crate) fn paren_expr(s: &mut ParserContext, lo: u32) -> Result { }) } -fn funcall(s: &mut ParserContext, ident: ast::Ident) -> Result { +fn funcall(s: &mut ParserContext, ident: Ident) -> Result { let lo = ident.span.lo; let (args, _) = seq(s, expr)?; token(s, TokenKind::Close(Delim::Paren))?; diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index a14716f10c..893f9db6fa 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -3,7 +3,7 @@ use super::expr; use crate::{ - ast::StmtKind, + parser::ast::StmtKind, parser::{scan::ParserContext, stmt, tests::check}, }; use expect_test::{expect, Expect}; diff --git a/compiler/qsc_qasm3/src/parser/prgm.rs b/compiler/qsc_qasm3/src/parser/prgm.rs index 928b033c8e..52f836ba20 100644 --- a/compiler/qsc_qasm3/src/parser/prgm.rs +++ b/compiler/qsc_qasm3/src/parser/prgm.rs @@ -6,11 +6,12 @@ use super::{ stmt, Result, }; use crate::{ - ast::{Program, Stmt, StmtKind, Version}, lex::{Delim, TokenKind}, parser::{completion::WordKinds, expr}, }; +use super::ast::{Program, Stmt, StmtKind, Version}; + use super::ParserContext; /// Grammar: `version? statementOrScope* EOF`. diff --git a/compiler/qsc_qasm3/src/parser/prim.rs b/compiler/qsc_qasm3/src/parser/prim.rs index 44e3ef913b..7f8d189a10 100644 --- a/compiler/qsc_qasm3/src/parser/prim.rs +++ b/compiler/qsc_qasm3/src/parser/prim.rs @@ -9,11 +9,9 @@ use super::{ scan::ParserContext, Parser, Result, }; -use crate::{ - ast::{Ident, IncompletePath, Path, PathKind}, - lex::TokenKind, - parser::completion::WordKinds, -}; +use crate::{lex::TokenKind, parser::completion::WordKinds}; + +use super::ast::{Ident, IncompletePath, Path, PathKind}; use qsc_data_structures::span::{Span, WithSpan}; diff --git a/compiler/qsc_qasm3/src/parser/prim/tests.rs b/compiler/qsc_qasm3/src/parser/prim/tests.rs index a86993fa83..7b2814b717 100644 --- a/compiler/qsc_qasm3/src/parser/prim/tests.rs +++ b/compiler/qsc_qasm3/src/parser/prim/tests.rs @@ -3,9 +3,9 @@ use super::{ident, opt, seq}; use crate::{ - ast::PathKind, keyword::Keyword, lex::TokenKind, + parser::ast::PathKind, parser::{ completion::WordKinds, error::{Error, ErrorKind}, diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index a72c0eac9b..c8f27caedd 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -15,23 +15,23 @@ use super::{ Result, }; use crate::{ - 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, MeasureStmt, Pragma, - QuantumGateDefinition, QuantumGateModifier, QuantumTypedParameter, QubitDeclaration, - RangeDefinition, ResetStmt, ReturnStmt, ScalarType, ScalarTypeKind, ScalarTypedParameter, - Stmt, StmtKind, SwitchCase, SwitchStmt, TypeDef, TypedParameter, UIntType, WhileLoop, - }, 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, MeasureStmt, 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 diff --git a/compiler/qsc_qasm3/src/semantic/ast.rs b/compiler/qsc_qasm3/src/semantic/ast.rs index dcbebbaee9..ecb4d15e2f 100644 --- a/compiler/qsc_qasm3/src/semantic/ast.rs +++ b/compiler/qsc_qasm3/src/semantic/ast.rs @@ -10,7 +10,7 @@ use std::{ }; use crate::{ - ast::{ + 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, @@ -21,6 +21,8 @@ use crate::{ semantic::symbols::SymbolId, }; +use crate::parser::ast as syntax; + #[derive(Clone, Debug)] pub struct Program { pub statements: List, @@ -218,6 +220,32 @@ impl Display for BinOp { } } +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 { @@ -305,6 +333,7 @@ impl Display for AliasDeclStmt { pub enum StmtKind { Alias(AliasDeclStmt), Assign(AssignStmt), + IndexedAssign(IndexedAssignStmt), AssignOp(AssignOpStmt), Barrier(BarrierStmt), Box(BoxStmt), @@ -361,6 +390,7 @@ impl Display for StmtKind { StmtKind::GPhase(gphase) => write!(f, "{gphase}"), StmtKind::If(if_stmt) => write!(f, "{if_stmt}"), StmtKind::Include(include) => write!(f, "{include}"), + StmtKind::IndexedAssign(assign) => write!(f, "{assign}"), StmtKind::IODeclaration(io) => write!(f, "{io}"), StmtKind::Measure(measure) => write!(f, "{measure}"), StmtKind::Pragma(pragma) => write!(f, "{pragma}"), @@ -503,14 +533,14 @@ impl WithSpan for Ident { #[derive(Clone, Debug)] pub struct IndexedIdent { pub span: Span, - pub name: Ident, + 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, "name", &self.name)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; write_list_field(f, "indices", &self.indices) } } @@ -1136,17 +1166,15 @@ impl Display for ValueExpression { #[derive(Clone, Debug)] pub struct IODeclaration { pub span: Span, - pub io_identifier: IOKeyword, - pub ty: TypeDef, - pub ident: Box, + pub symbol_id: SymbolId, + pub init_expr: 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) + writeln_field(f, "symbol_id", &self.symbol_id)?; + write_field(f, "init_expr", &self.init_expr) } } @@ -1196,7 +1224,7 @@ pub struct ScalarTypedParameter { impl Display for ScalarTypedParameter { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln_header(f, "ScalarTypedParameter", self.span)?; - writeln_field(f, "type", &self.ty)?; + writeln_field(f, "ty", &self.ty)?; write_field(f, "ident", &self.ident) } } @@ -1240,7 +1268,7 @@ pub struct ArrayTypedParameter { impl Display for ArrayTypedParameter { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln_header(f, "ArrayTypedParameter", self.span)?; - writeln_field(f, "type", &self.ty)?; + writeln_field(f, "ty", &self.ty)?; write_field(f, "ident", &self.ident) } } @@ -1376,6 +1404,7 @@ pub enum ExprKind { #[default] Err, Ident(SymbolId), + IndexedIdentifier(IndexedIdent), UnaryOp(UnaryOpExpr), BinaryOp(BinaryOpExpr), Lit(LiteralKind), @@ -1389,7 +1418,8 @@ 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::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}"), @@ -1404,14 +1434,31 @@ impl Display for ExprKind { #[derive(Clone, Debug)] pub struct AssignStmt { pub span: Span, - pub lhs: Expr, + pub symbold_id: SymbolId, pub rhs: Expr, } impl Display for AssignStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln_header(f, "AssignStmt", self.span)?; - writeln_field(f, "lhs", &self.lhs)?; + writeln_field(f, "symbol_id", &self.symbold_id)?; + write_field(f, "rhs", &self.rhs) + } +} + +#[derive(Clone, Debug)] +pub struct IndexedAssignStmt { + pub span: Span, + pub symbold_id: SymbolId, + pub lhs: Expr, + 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.symbold_id)?; + writeln_field(f, "lhs", &self.rhs)?; write_field(f, "rhs", &self.rhs) } } @@ -1419,7 +1466,7 @@ impl Display for AssignStmt { #[derive(Clone, Debug)] pub struct AssignOpStmt { pub span: Span, - pub op: BinOp, + pub symbold_id: SymbolId, pub lhs: Expr, pub rhs: Expr, } @@ -1427,8 +1474,8 @@ pub struct AssignOpStmt { 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)?; + writeln_field(f, "symbol_id", &self.symbold_id)?; + writeln_field(f, "lhs", &self.rhs)?; write_field(f, "rhs", &self.rhs) } } @@ -1454,6 +1501,15 @@ pub struct BinaryOpExpr { 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:")?; @@ -1488,7 +1544,7 @@ pub struct Cast { impl Display for Cast { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln_header(f, "Cast", self.span)?; - writeln_field(f, "type", &self.ty)?; + writeln_field(f, "ty", &self.ty)?; write_field(f, "expr", &self.expr) } } @@ -1574,12 +1630,10 @@ impl Display for IndexElement { } } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub enum IndexSetItem { RangeDefinition(RangeDefinition), Expr(Expr), - #[default] - Err, } /// This is needed to able to use `IndexSetItem` in the `seq` combinator. @@ -1590,7 +1644,6 @@ impl WithSpan for IndexSetItem { IndexSetItem::RangeDefinition(range.with_span(span)) } IndexSetItem::Expr(expr) => IndexSetItem::Expr(expr.with_span(span)), - IndexSetItem::Err => IndexSetItem::Err, } } } @@ -1600,7 +1653,6 @@ impl Display for IndexSetItem { match self { IndexSetItem::RangeDefinition(range) => write!(f, "{range}"), IndexSetItem::Expr(expr) => write!(f, "{expr}"), - IndexSetItem::Err => write!(f, "Err"), } } } diff --git a/compiler/qsc_qasm3/src/semantic/error.rs b/compiler/qsc_qasm3/src/semantic/error.rs index c637443765..48b7334ff6 100644 --- a/compiler/qsc_qasm3/src/semantic/error.rs +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -78,6 +78,9 @@ pub enum SemanticErrorKind { #[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), @@ -162,6 +165,9 @@ pub enum SemanticErrorKind { #[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), @@ -241,6 +247,9 @@ impl SemanticErrorKind { Self::ForStatementsMustHaveABodyOrStatement(span) => { Self::ForStatementsMustHaveABodyOrStatement(span + offset) } + Self::InconsistentTypesInAlias(name, span) => { + Self::InconsistentTypesInAlias(name, span + offset) + } Self::IfStmtMissingExpression(name, span) => { Self::IfStmtMissingExpression(name, span + offset) } @@ -303,6 +312,7 @@ impl SemanticErrorKind { } Self::ReturnNotInSubroutine(span) => Self::ReturnNotInSubroutine(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) } diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index e0976acccc..4656c9a6b3 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -4,6 +4,12 @@ use std::ops::ShlAssign; use std::path::PathBuf; +use super::types::binop_requires_int_conversion_for_type; +use super::types::binop_requires_symmetric_int_conversion; +use super::types::is_complex_binop_supported; +use super::types::promote_to_uint_ty; +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; @@ -15,13 +21,14 @@ use qsc_frontend::{compile::SourceMap, error::WithSource}; use super::symbols::{IOKind, Symbol, SymbolTable}; -use crate::ast::list_from_iter; use crate::oqasm_helpers::safe_i64_to_f64; 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::parser::ast as syntax; + use super::{ ast::{Stmt, Version}, SemanticErrorKind, @@ -52,7 +59,7 @@ impl Lowerer { let program = super::ast::Program { version: self.version, - statements: list_from_iter(self.stmts), + statements: syntax::list_from_iter(self.stmts), }; super::QasmSemanticParseResult { @@ -64,7 +71,7 @@ impl Lowerer { } } - fn lower_version(&mut self, version: Option) -> Option { + fn lower_version(&mut self, version: Option) -> Option { if let Some(version) = version { if version.major != 3 { self.push_semantic_error(SemanticErrorKind::UnsupportedVersion( @@ -103,7 +110,7 @@ impl Lowerer { for stmt in &source.program().statements { match &*stmt.kind { - crate::ast::StmtKind::Include(include) => { + syntax::StmtKind::Include(include) => { // 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() { @@ -139,97 +146,85 @@ impl Lowerer { } #[allow(clippy::too_many_lines)] - fn lower_stmt(&mut self, stmt: &crate::ast::Stmt) -> Option { + fn lower_stmt(&mut self, stmt: &syntax::Stmt) -> Option { let kind = match &*stmt.kind { - crate::ast::StmtKind::Alias(stmt) => { - super::ast::StmtKind::Alias(self.lower_alias(stmt)?) - } - crate::ast::StmtKind::Assign(stmt) => { - super::ast::StmtKind::Assign(self.lower_assign(stmt)?) - } - crate::ast::StmtKind::AssignOp(stmt) => { + syntax::StmtKind::Alias(stmt) => super::ast::StmtKind::Alias(self.lower_alias(stmt)?), + syntax::StmtKind::Assign(stmt) => self.lower_assign(stmt)?, + syntax::StmtKind::AssignOp(stmt) => { super::ast::StmtKind::AssignOp(self.lower_assign_op(stmt)?) } - crate::ast::StmtKind::Barrier(stmt) => { + syntax::StmtKind::Barrier(stmt) => { super::ast::StmtKind::Barrier(self.lower_barrier(stmt)?) } - crate::ast::StmtKind::Box(stmt) => super::ast::StmtKind::Box(self.lower_box(stmt)?), - crate::ast::StmtKind::Break(stmt) => self.lower_break(stmt)?, - crate::ast::StmtKind::Block(stmt) => { + syntax::StmtKind::Box(stmt) => super::ast::StmtKind::Box(self.lower_box(stmt)?), + syntax::StmtKind::Break(stmt) => self.lower_break(stmt)?, + syntax::StmtKind::Block(stmt) => { super::ast::StmtKind::Block(Box::new(self.lower_block(stmt)?)) } - crate::ast::StmtKind::Cal(stmt) => self.lower_calibration(stmt)?, - crate::ast::StmtKind::CalibrationGrammar(stmt) => { + syntax::StmtKind::Cal(stmt) => self.lower_calibration(stmt)?, + syntax::StmtKind::CalibrationGrammar(stmt) => { super::ast::StmtKind::CalibrationGrammar(self.lower_calibration_grammar(stmt)?) } - crate::ast::StmtKind::ClassicalDecl(stmt) => { + syntax::StmtKind::ClassicalDecl(stmt) => { super::ast::StmtKind::ClassicalDecl(self.lower_classical_decl(stmt)?) } - crate::ast::StmtKind::ConstDecl(stmt) => { + syntax::StmtKind::ConstDecl(stmt) => { super::ast::StmtKind::ClassicalDecl(self.lower_const_decl(stmt)?) } - crate::ast::StmtKind::Continue(stmt) => self.lower_continue_stmt(stmt)?, - crate::ast::StmtKind::Def(stmt) => super::ast::StmtKind::Def(self.lower_def(stmt)?), - crate::ast::StmtKind::DefCal(stmt) => { + syntax::StmtKind::Continue(stmt) => self.lower_continue_stmt(stmt)?, + syntax::StmtKind::Def(stmt) => super::ast::StmtKind::Def(self.lower_def(stmt)?), + syntax::StmtKind::DefCal(stmt) => { super::ast::StmtKind::DefCal(self.lower_def_cal(stmt)?) } - crate::ast::StmtKind::Delay(stmt) => { - super::ast::StmtKind::Delay(self.lower_delay(stmt)?) - } - crate::ast::StmtKind::Empty => { + syntax::StmtKind::Delay(stmt) => super::ast::StmtKind::Delay(self.lower_delay(stmt)?), + syntax::StmtKind::Empty => { // we ignore empty statements None? } - crate::ast::StmtKind::End(stmt) => { - super::ast::StmtKind::End(self.lower_end_stmt(stmt)?) - } - crate::ast::StmtKind::ExprStmt(stmt) => { + syntax::StmtKind::End(stmt) => super::ast::StmtKind::End(self.lower_end_stmt(stmt)?), + syntax::StmtKind::ExprStmt(stmt) => { super::ast::StmtKind::ExprStmt(self.lower_expr_stmt(stmt)?) } - crate::ast::StmtKind::ExternDecl(extern_decl) => { + syntax::StmtKind::ExternDecl(extern_decl) => { super::ast::StmtKind::ExternDecl(self.lower_extern(extern_decl)?) } - crate::ast::StmtKind::For(stmt) => { - super::ast::StmtKind::For(self.lower_for_stmt(stmt)?) - } - crate::ast::StmtKind::If(stmt) => super::ast::StmtKind::If(self.lower_if_stmt(stmt)?), - crate::ast::StmtKind::GateCall(stmt) => { + syntax::StmtKind::For(stmt) => super::ast::StmtKind::For(self.lower_for_stmt(stmt)?), + syntax::StmtKind::If(stmt) => super::ast::StmtKind::If(self.lower_if_stmt(stmt)?), + syntax::StmtKind::GateCall(stmt) => { super::ast::StmtKind::GateCall(self.lower_gate_call(stmt)?) } - crate::ast::StmtKind::GPhase(stmt) => { + syntax::StmtKind::GPhase(stmt) => { super::ast::StmtKind::GPhase(self.lower_gphase(stmt)?) } - crate::ast::StmtKind::Include(stmt) => { + syntax::StmtKind::Include(stmt) => { super::ast::StmtKind::Include(self.lower_include(stmt)?) } - crate::ast::StmtKind::IODeclaration(stmt) => { + syntax::StmtKind::IODeclaration(stmt) => { super::ast::StmtKind::IODeclaration(self.lower_io_decl(stmt)?) } - crate::ast::StmtKind::Measure(stmt) => { + syntax::StmtKind::Measure(stmt) => { super::ast::StmtKind::Measure(self.lower_measure(stmt)?) } - crate::ast::StmtKind::Pragma(stmt) => { + syntax::StmtKind::Pragma(stmt) => { super::ast::StmtKind::Pragma(self.lower_pragma(stmt)?) } - crate::ast::StmtKind::QuantumGateDefinition(stmt) => { + syntax::StmtKind::QuantumGateDefinition(stmt) => { super::ast::StmtKind::QuantumGateDefinition(self.lower_gate_def(stmt)?) } - crate::ast::StmtKind::QuantumDecl(stmt) => { + syntax::StmtKind::QuantumDecl(stmt) => { super::ast::StmtKind::QuantumDecl(self.lower_quantum_decl(stmt)?) } - crate::ast::StmtKind::Reset(stmt) => { - super::ast::StmtKind::Reset(self.lower_reset(stmt)?) - } - crate::ast::StmtKind::Return(stmt) => { + syntax::StmtKind::Reset(stmt) => super::ast::StmtKind::Reset(self.lower_reset(stmt)?), + syntax::StmtKind::Return(stmt) => { super::ast::StmtKind::Return(self.lower_return(stmt)?) } - crate::ast::StmtKind::Switch(stmt) => { + syntax::StmtKind::Switch(stmt) => { super::ast::StmtKind::Switch(self.lower_switch(stmt)?) } - crate::ast::StmtKind::WhileLoop(stmt) => { + syntax::StmtKind::WhileLoop(stmt) => { super::ast::StmtKind::WhileLoop(self.lower_while_loop(stmt)?) } - crate::ast::StmtKind::Err => { + syntax::StmtKind::Err => { self.push_semantic_error(SemanticErrorKind::UnexpectedParserError( "Unexpected error".to_string(), stmt.span, @@ -240,7 +235,7 @@ impl Lowerer { let annotations = self.lower_annotations(&stmt.annotations, &stmt.kind); Some(super::ast::Stmt { span: stmt.span, - annotations: list_from_iter(annotations), + annotations: syntax::list_from_iter(annotations), kind: Box::new(kind), }) } @@ -249,7 +244,7 @@ impl Lowerer { /// 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: &crate::ast::IncludeStmt) { + fn define_stdgates(&mut self, include: &syntax::IncludeStmt) { fn gate_symbol(name: &str, cargs: u32, qargs: u32) -> Symbol { Symbol { name: name.to_string(), @@ -330,10 +325,7 @@ impl Lowerer { WithSource::from_map(&self.source_map, offset_error) } - fn lower_alias( - &mut self, - alias: &crate::ast::AliasDeclStmt, - ) -> Option { + fn lower_alias(&mut self, alias: &syntax::AliasDeclStmt) -> Option { 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. @@ -343,9 +335,8 @@ impl Lowerer { .iter() .filter_map(|expr| self.lower_expr(expr)) .collect::>(); - // TODO: handle multiple rhs - // TODO: validate consistency of rhs types let first = rhs.first().expect("missing rhs"); + let symbol = Symbol { name: name.to_string(), ty: first.ty.clone(), @@ -353,10 +344,24 @@ impl Lowerer { span: alias.ident.span(), io_kind: IOKind::Default, }; - let Ok(symbol_id) = self.symbols.insert_symbol(symbol) else { - self.push_redefined_symbol_error(name, alias.span); + + let symbol_id = self.symbols.insert_symbol(symbol); + if symbol_id.is_err() { + self.push_redefined_symbol_error(name, alias.ident.span()); + } + + 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); return None; - }; + } + + let symbol_id = symbol_id.ok()?; if rhs.len() != alias.exprs.len() { // we failed @@ -365,54 +370,143 @@ impl Lowerer { Some(super::ast::AliasDeclStmt { span: alias.span, symbol_id, - exprs: list_from_iter(rhs), + exprs: syntax::list_from_iter(rhs), }) } - fn lower_assign(&mut self, assign: &crate::ast::AssignStmt) -> Option { - self.push_unimplemented_error_message("assign stmt", assign.span); - None + fn lower_assign(&mut self, stmt: &syntax::AssignStmt) -> Option { + if stmt.lhs.indices.is_empty() { + Some(super::ast::StmtKind::Assign( + self.lower_simple_assign_expr(&stmt.lhs.name, &stmt.rhs, stmt.span)?, + )) + } else { + Some(super::ast::StmtKind::IndexedAssign( + self.lower_indexed_assign_expr(&stmt.lhs, &stmt.rhs, stmt.span)?, + )) + } } - fn lower_assign_op( + fn lower_simple_assign_expr( &mut self, - assign_op: &crate::ast::AssignOpStmt, - ) -> Option { - self.push_unimplemented_error_message("assign op stmt", assign_op.span); - None + ident: &syntax::Ident, + rhs: &syntax::Expr, + span: Span, + ) -> Option { + let (lhs_symbol_id, lhs_symbol) = self.symbols.get_symbol_by_name(&ident.name)?; + let ty = lhs_symbol.ty.clone(); + if ty.is_const() { + let kind = + SemanticErrorKind::CannotUpdateConstVariable(ident.name.to_string(), ident.span); + self.push_semantic_error(kind); + // usually we'd return None here, but we'll continue to compile the rhs + // looking for more errors. There is nothing in this type of error that + // would prevent us from compiling the rhs. + } + + let rhs = self.lower_expr_with_target_type(Some(rhs), &ty, span)?; + Some(super::ast::AssignStmt { + symbold_id: lhs_symbol_id, + rhs, + span, + }) + } + + fn lower_indexed_assign_expr( + &mut self, + index_expr: &syntax::IndexedIdent, + rhs: &syntax::Expr, + span: Span, + ) -> Option { + let ident = index_expr.name.clone(); + let (lhs_symbol_id, lhs_symbol) = self.symbols.get_symbol_by_name(&ident.name)?; + let ty = lhs_symbol.ty.clone(); + if ty.is_const() { + let kind = + SemanticErrorKind::CannotUpdateConstVariable(ident.name.to_string(), ident.span); + self.push_semantic_error(kind); + // usually we'd return None here, but we'll continue to compile the rhs + // looking for more errors. There is nothing in this type of error that + // would prevent us from compiling the rhs. + return None; + } + + let lhs = self.lower_indexed_ident_expr(index_expr); + let rhs = self.lower_expr_with_target_type(Some(rhs), &ty, span); + let (lhs, rhs) = (lhs?, rhs?); + + Some(super::ast::IndexedAssignStmt { + symbold_id: lhs_symbol_id, + lhs, + rhs, + span, + }) } - fn lower_expr(&mut self, expr: &crate::ast::Expr) -> Option { + fn lower_assign_op(&mut self, stmt: &syntax::AssignOpStmt) -> Option { + let op = stmt.op; + let lhs = &stmt.lhs; + let rhs = &stmt.rhs; + + let ident = lhs.name.clone(); + + let (lhs_symbol_id, lhs_symbol) = self.symbols.get_symbol_by_name(&ident.name)?; + let ty = lhs_symbol.ty.clone(); + if ty.is_const() { + let kind = + SemanticErrorKind::CannotUpdateConstVariable(ident.name.to_string(), ident.span); + self.push_semantic_error(kind); + // usually we'd return None here, but we'll continue to compile the rhs + // looking for more errors. There is nothing in this type of error that + // would prevent us from compiling the rhs. + return None; + } + + let lhs = self.lower_indexed_ident_expr(lhs); + + let rhs = self.lower_expr(rhs); + let (lhs, rhs) = (lhs?, rhs?); + let rhs = self.lower_binary_op_expr(op, lhs.clone(), rhs, stmt.span)?; + let rhs = self.cast_expr_to_type(&ty, &rhs, stmt.span)?; + Some(super::ast::AssignOpStmt { + span: stmt.span, + symbold_id: lhs_symbol_id, + lhs, + rhs, + }) + } + + fn lower_expr(&mut self, expr: &syntax::Expr) -> Option { match &*expr.kind { - crate::ast::ExprKind::BinaryOp(_) => { - self.push_unimplemented_error_message("binary op expr", expr.span); - None - } - crate::ast::ExprKind::Cast(_) => { + syntax::ExprKind::BinaryOp(bin_op_expr) => { + let lhs = self.lower_expr(&bin_op_expr.lhs); + let rhs = self.lower_expr(&bin_op_expr.rhs); + // once we've compiled both sides, we can check if they failed + // and return None if they did so that the error is propagated + let (lhs, rhs) = (lhs?, 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); None } - crate::ast::ExprKind::Err => { + syntax::ExprKind::Err => { unreachable!("Err expr should not be lowered"); } - crate::ast::ExprKind::FunctionCall(_) => { + syntax::ExprKind::FunctionCall(_) => { self.push_unimplemented_error_message("function call expr", expr.span); None } - crate::ast::ExprKind::Ident(ident) => self.lower_ident_expr(ident), - crate::ast::ExprKind::IndexExpr(_) => { - self.push_unimplemented_error_message("index expr", expr.span); - None - } + syntax::ExprKind::Ident(ident) => self.lower_ident_expr(ident), + syntax::ExprKind::IndexExpr(expr) => self.lower_index_expr(expr), - crate::ast::ExprKind::Lit(lit) => self.lower_lit_expr(lit), + syntax::ExprKind::Lit(lit) => self.lower_lit_expr(lit), - crate::ast::ExprKind::Paren(expr) => self.lower_paren_expr(expr), - crate::ast::ExprKind::UnaryOp(expr) => self.lower_unary_op_expr(expr), + syntax::ExprKind::Paren(expr) => self.lower_paren_expr(expr), + syntax::ExprKind::UnaryOp(expr) => self.lower_unary_op_expr(expr), } } - fn lower_ident_expr(&mut self, ident: &crate::ast::Ident) -> Option { + fn lower_ident_expr(&mut self, ident: &syntax::Ident) -> Option { let name = ident.name.clone(); let Some((symbol_id, symbol)) = self.symbols.get_symbol_by_name(&name) else { self.push_missing_symbol_error(&name, ident.span); @@ -427,42 +521,42 @@ impl Lowerer { }) } - fn lower_lit_expr(&mut self, expr: &crate::ast::Lit) -> Option { + fn lower_lit_expr(&mut self, expr: &syntax::Lit) -> Option { let (kind, ty) = match &expr.kind { - crate::ast::LiteralKind::BigInt(value) => { + syntax::LiteralKind::BigInt(value) => { // todo: 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. (super::ast::LiteralKind::BigInt(value.clone()), Type::Err) } - crate::ast::LiteralKind::Bitstring(value, size) => ( + syntax::LiteralKind::Bitstring(value, size) => ( super::ast::LiteralKind::Bitstring(value.clone(), *size), Type::BitArray(super::types::ArrayDimensions::One(*size), true), ), - crate::ast::LiteralKind::Bool(value) => { + syntax::LiteralKind::Bool(value) => { (super::ast::LiteralKind::Bool(*value), Type::Bool(true)) } - crate::ast::LiteralKind::Int(value) => { + syntax::LiteralKind::Int(value) => { (super::ast::LiteralKind::Int(*value), Type::Int(None, true)) } - crate::ast::LiteralKind::Float(value) => ( + syntax::LiteralKind::Float(value) => ( super::ast::LiteralKind::Float(*value), Type::Float(None, true), ), - crate::ast::LiteralKind::Imaginary(value) => ( + syntax::LiteralKind::Imaginary(value) => ( super::ast::LiteralKind::Complex(0.0, *value), Type::Complex(None, true), ), - crate::ast::LiteralKind::String(_) => { + syntax::LiteralKind::String(_) => { self.push_unsupported_error_message("String literals", expr.span); return None; } - crate::ast::LiteralKind::Duration(_, _) => { + syntax::LiteralKind::Duration(_, _) => { self.push_unsupported_error_message("Duration literals", expr.span); return None; } - crate::ast::LiteralKind::Array(exprs) => { + 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. @@ -481,7 +575,7 @@ impl Lowerer { } ( - super::ast::LiteralKind::Array(list_from_iter(texprs)), + super::ast::LiteralKind::Array(syntax::list_from_iter(texprs)), Type::Err, ) } @@ -493,7 +587,7 @@ impl Lowerer { }) } - fn lower_paren_expr(&mut self, expr: &crate::ast::Expr) -> Option { + fn lower_paren_expr(&mut self, expr: &syntax::Expr) -> Option { let expr = self.lower_expr(expr)?; let span = expr.span; let ty = expr.ty.clone(); @@ -505,15 +599,15 @@ impl Lowerer { }) } - fn lower_unary_op_expr(&mut self, expr: &crate::ast::UnaryOpExpr) -> Option { + fn lower_unary_op_expr(&mut self, expr: &syntax::UnaryOpExpr) -> Option { match expr.op { - crate::ast::UnaryOp::Neg => { - if let crate::ast::ExprKind::Lit(lit) = expr.expr.kind.as_ref() { + syntax::UnaryOp::Neg => { + if let syntax::ExprKind::Lit(lit) = expr.expr.kind.as_ref() { self.lower_negated_literal_as_ty(lit, None, expr.expr.span) } else { let expr = self.lower_expr(&expr.expr)?; let ty = expr.ty.clone(); - if unary_op_can_be_applied_to_type(crate::ast::UnaryOp::Neg, &ty) { + if unary_op_can_be_applied_to_type(syntax::UnaryOp::Neg, &ty) { let span = expr.span; let unary = super::ast::UnaryOpExpr { op: super::ast::UnaryOp::Neg, @@ -534,10 +628,10 @@ impl Lowerer { } } } - crate::ast::UnaryOp::NotB => { + syntax::UnaryOp::NotB => { let expr = self.lower_expr(&expr.expr)?; let ty = expr.ty.clone(); - if unary_op_can_be_applied_to_type(crate::ast::UnaryOp::NotB, &ty) { + if unary_op_can_be_applied_to_type(syntax::UnaryOp::NotB, &ty) { let span = expr.span; let unary = super::ast::UnaryOpExpr { op: super::ast::UnaryOp::NotB, @@ -557,7 +651,7 @@ impl Lowerer { None } } - crate::ast::UnaryOp::NotL => { + 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 @@ -581,8 +675,8 @@ impl Lowerer { fn lower_annotations( &mut self, - annotations: &[Box], - kind: &crate::ast::StmtKind, + annotations: &[Box], + kind: &syntax::StmtKind, ) -> Vec { annotations .iter() @@ -592,8 +686,8 @@ impl Lowerer { fn lower_annotation( &mut self, - annotation: &crate::ast::Annotation, - kind: &crate::ast::StmtKind, + annotation: &syntax::Annotation, + kind: &syntax::StmtKind, ) -> super::ast::Annotation { if !matches!( annotation.identifier.to_string().as_str(), @@ -605,7 +699,7 @@ impl Lowerer { ); } - if let crate::ast::StmtKind::GateCall(_) = &kind { + if let syntax::StmtKind::GateCall(_) = &kind { self.push_unsupported_error_message( format!( "Annotation {} is only allowed on gate definitions.", @@ -691,29 +785,29 @@ impl Lowerer { } } - fn lower_barrier(&mut self, stmt: &crate::ast::BarrierStmt) -> Option { + fn lower_barrier(&mut self, stmt: &syntax::BarrierStmt) -> Option { self.push_unimplemented_error_message("barrier stmt", stmt.span); None } - fn lower_box(&mut self, stmt: &crate::ast::BoxStmt) -> Option { + fn lower_box(&mut self, stmt: &syntax::BoxStmt) -> Option { self.push_unimplemented_error_message("box stmt", stmt.span); None } - fn lower_break(&mut self, stmt: &crate::ast::BreakStmt) -> Option { + fn lower_break(&mut self, stmt: &syntax::BreakStmt) -> Option { self.push_unimplemented_error_message("break stmt", stmt.span); None } - fn lower_block(&mut self, stmt: &crate::ast::Block) -> Option { + fn lower_block(&mut self, stmt: &syntax::Block) -> Option { self.push_unimplemented_error_message("block stmt", stmt.span); None } fn lower_calibration( &mut self, - stmt: &crate::ast::CalibrationStmt, + stmt: &syntax::CalibrationStmt, ) -> Option { self.push_unimplemented_error_message("calibration stmt", stmt.span); None @@ -721,7 +815,7 @@ impl Lowerer { fn lower_calibration_grammar( &mut self, - stmt: &crate::ast::CalibrationGrammarStmt, + stmt: &syntax::CalibrationGrammarStmt, ) -> Option { self.push_unimplemented_error_message("calibration stmt", stmt.span); None @@ -729,7 +823,7 @@ impl Lowerer { fn lower_classical_decl( &mut self, - stmt: &crate::ast::ClassicalDeclarationStmt, + stmt: &syntax::ClassicalDeclarationStmt, ) -> Option { let is_const = false; // const decls are handled separately let ty = self.get_semantic_type_from_tydef(&stmt.ty, is_const)?; @@ -750,10 +844,10 @@ impl Lowerer { // process the symbol and init_expr gathering any errors let init_expr = match init_expr { Some(expr) => match expr { - crate::ast::ValueExpression::Expr(expr) => self + syntax::ValueExpression::Expr(expr) => self .lower_expr_with_target_type(Some(expr), &ty, stmt_span) .map(super::ast::ValueExpression::Expr), - crate::ast::ValueExpression::Measurement(measure_expr) => self + syntax::ValueExpression::Measurement(measure_expr) => self .lower_measure_expr_with_target_type(measure_expr, &ty, stmt_span) .map(super::ast::ValueExpression::Measurement), }, @@ -781,7 +875,7 @@ impl Lowerer { fn lower_const_decl( &mut self, - stmt: &crate::ast::ConstantDeclStmt, + stmt: &syntax::ConstantDeclStmt, ) -> Option { let is_const = true; let ty = self.get_semantic_type_from_tydef(&stmt.ty, is_const)?; @@ -816,90 +910,130 @@ impl Lowerer { }) } - fn lower_continue_stmt( - &mut self, - stmt: &crate::ast::ContinueStmt, - ) -> Option { + fn lower_continue_stmt(&mut self, stmt: &syntax::ContinueStmt) -> Option { self.push_unimplemented_error_message("continue stmt", stmt.span); None } - fn lower_def(&mut self, stmt: &crate::ast::DefStmt) -> Option { + fn lower_def(&mut self, stmt: &syntax::DefStmt) -> Option { self.push_unimplemented_error_message("def stmt", stmt.span); None } - fn lower_def_cal(&mut self, stmt: &crate::ast::DefCalStmt) -> Option { + fn lower_def_cal(&mut self, stmt: &syntax::DefCalStmt) -> Option { self.push_unimplemented_error_message("def cal stmt", stmt.span); None } - fn lower_delay(&mut self, stmt: &crate::ast::DelayStmt) -> Option { + fn lower_delay(&mut self, stmt: &syntax::DelayStmt) -> Option { self.push_unimplemented_error_message("delay stmt", stmt.span); None } - fn lower_end_stmt(&mut self, stmt: &crate::ast::EndStmt) -> Option { + fn lower_end_stmt(&mut self, stmt: &syntax::EndStmt) -> Option { self.push_unimplemented_error_message("end stmt", stmt.span); None } - fn lower_expr_stmt(&mut self, stmt: &crate::ast::ExprStmt) -> Option { - self.push_unimplemented_error_message("expr stmt", stmt.span); - None + fn lower_expr_stmt(&mut self, stmt: &syntax::ExprStmt) -> Option { + let expr = self.lower_expr(&stmt.expr)?; + Some(super::ast::ExprStmt { + span: stmt.span, + expr, + }) } - fn lower_extern(&mut self, stmt: &crate::ast::ExternDecl) -> Option { + fn lower_extern(&mut self, stmt: &syntax::ExternDecl) -> Option { self.push_unimplemented_error_message("extern stmt", stmt.span); None } - fn lower_for_stmt(&mut self, stmt: &crate::ast::ForStmt) -> Option { + fn lower_for_stmt(&mut self, stmt: &syntax::ForStmt) -> Option { self.push_unimplemented_error_message("for stmt", stmt.span); None } - fn lower_if_stmt(&mut self, stmt: &crate::ast::IfStmt) -> Option { + fn lower_if_stmt(&mut self, stmt: &syntax::IfStmt) -> Option { self.push_unimplemented_error_message("if stmt", stmt.span); None } - fn lower_gate_call(&mut self, stmt: &crate::ast::GateCall) -> Option { + fn lower_gate_call(&mut self, stmt: &syntax::GateCall) -> Option { self.push_unimplemented_error_message("gate call stmt", stmt.span); None } - fn lower_gphase(&mut self, stmt: &crate::ast::GPhase) -> Option { + fn lower_gphase(&mut self, stmt: &syntax::GPhase) -> Option { self.push_unimplemented_error_message("gphase stmt", stmt.span); None } - fn lower_include(&mut self, stmt: &crate::ast::IncludeStmt) -> Option { - self.push_unimplemented_error_message("include stmt", stmt.span); - None + /// This function is always a indication of a error. Either the + /// program is declaring include in a non-global scope or the + /// include is not handled in `self.lower_source` properly. + fn lower_include(&mut self, stmt: &syntax::IncludeStmt) -> Option { + // 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 None; + } + // if we are at the root and we have an include, we should have + // already handled it and we are in an invalid state + panic!("Include should have been handled in lower_source") } - fn lower_io_decl( - &mut self, - stmt: &crate::ast::IODeclaration, - ) -> Option { - self.push_unimplemented_error_message("io decl stmt", stmt.span); - None + fn lower_io_decl(&mut self, stmt: &syntax::IODeclaration) -> Option { + let is_const = false; + let ty = self.get_semantic_type_from_tydef(&stmt.ty, is_const)?; + let io_kind = stmt.io_identifier.into(); + + 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.clone(), ty_span)?; + let symbol = Symbol { + name: name.to_string(), + ty: ty.clone(), + qsharp_ty, + span: stmt.ident.span, + io_kind, + }; + + let Ok(symbol_id) = self.symbols.insert_symbol(symbol) else { + self.push_redefined_symbol_error(&name, stmt.ident.span); + return None; + }; + + // if we have output, we need to assign a default value to declare the variable + // if we have input, we can keep return none as we would promote the variable + // to a parameter in the function signature once we generate the function + if io_kind == IOKind::Output { + let init_expr = self.get_default_value(&ty, stmt_span)?; + Some(super::ast::IODeclaration { + span: stmt_span, + symbol_id, + init_expr: Box::new(init_expr), + }) + } else { + None + } } - fn lower_measure(&mut self, stmt: &crate::ast::MeasureStmt) -> Option { + fn lower_measure(&mut self, stmt: &syntax::MeasureStmt) -> Option { self.push_unimplemented_error_message("measure stmt", stmt.span); None } - fn lower_pragma(&mut self, stmt: &crate::ast::Pragma) -> Option { + fn lower_pragma(&mut self, stmt: &syntax::Pragma) -> Option { self.push_unimplemented_error_message("pragma stmt", stmt.span); None } fn lower_gate_def( &mut self, - stmt: &crate::ast::QuantumGateDefinition, + stmt: &syntax::QuantumGateDefinition, ) -> Option { self.push_unimplemented_error_message("gate def stmt", stmt.span); None @@ -907,45 +1041,45 @@ impl Lowerer { fn lower_quantum_decl( &mut self, - stmt: &crate::ast::QubitDeclaration, + stmt: &syntax::QubitDeclaration, ) -> Option { self.push_unimplemented_error_message("qubit decl stmt", stmt.span); None } - fn lower_reset(&mut self, stmt: &crate::ast::ResetStmt) -> Option { + fn lower_reset(&mut self, stmt: &syntax::ResetStmt) -> Option { self.push_unimplemented_error_message("reset stmt", stmt.span); None } - fn lower_return(&mut self, stmt: &crate::ast::ReturnStmt) -> Option { + fn lower_return(&mut self, stmt: &syntax::ReturnStmt) -> Option { self.push_unimplemented_error_message("return stmt", stmt.span); None } - fn lower_switch(&mut self, stmt: &crate::ast::SwitchStmt) -> Option { + fn lower_switch(&mut self, stmt: &syntax::SwitchStmt) -> Option { self.push_unimplemented_error_message("switch stmt", stmt.span); None } - fn lower_while_loop(&mut self, stmt: &crate::ast::WhileLoop) -> Option { + fn lower_while_loop(&mut self, stmt: &syntax::WhileLoop) -> Option { self.push_unimplemented_error_message("while loop stmt", stmt.span); None } fn get_semantic_type_from_tydef( &mut self, - scalar_ty: &crate::ast::TypeDef, + scalar_ty: &syntax::TypeDef, is_const: bool, ) -> Option { match scalar_ty { - crate::ast::TypeDef::Scalar(scalar_type) => { + syntax::TypeDef::Scalar(scalar_type) => { self.get_semantic_type_from_scalar_ty(scalar_type, is_const) } - crate::ast::TypeDef::Array(array_type) => { + syntax::TypeDef::Array(array_type) => { self.get_semantic_type_from_array_ty(array_type, is_const) } - crate::ast::TypeDef::ArrayReference(array_reference_type) => { + syntax::TypeDef::ArrayReference(array_reference_type) => { self.get_semantic_type_from_array_reference_ty(array_reference_type, is_const) } } @@ -953,9 +1087,9 @@ impl Lowerer { /// designators are positive integer literals when used /// in the context of a type definition. - fn get_size_designator_from_expr(&mut self, expr: &crate::ast::Expr) -> Option { - if let crate::ast::ExprKind::Lit(lit) = expr.kind.as_ref() { - if let crate::ast::LiteralKind::Int(value) = lit.kind { + fn get_size_designator_from_expr(&mut self, expr: &syntax::Expr) -> Option { + if let syntax::ExprKind::Lit(lit) = expr.kind.as_ref() { + if let syntax::LiteralKind::Int(value) = lit.kind { if value > 0 { if let Ok(value) = u32::try_from(value) { Some(value) @@ -985,39 +1119,39 @@ impl Lowerer { fn get_semantic_type_from_scalar_ty( &mut self, - scalar_ty: &crate::ast::ScalarType, + scalar_ty: &syntax::ScalarType, is_const: bool, ) -> Option { match &scalar_ty.kind { - crate::ast::ScalarTypeKind::Bit(bit_type) => match &bit_type.size { + syntax::ScalarTypeKind::Bit(bit_type) => match &bit_type.size { Some(size) => Some(crate::semantic::types::Type::BitArray( super::types::ArrayDimensions::One(self.get_size_designator_from_expr(size)?), is_const, )), None => Some(crate::semantic::types::Type::Bit(is_const)), }, - crate::ast::ScalarTypeKind::Int(int_type) => match &int_type.size { + syntax::ScalarTypeKind::Int(int_type) => match &int_type.size { Some(size) => Some(crate::semantic::types::Type::Int( Some(self.get_size_designator_from_expr(size)?), is_const, )), None => Some(crate::semantic::types::Type::Int(None, is_const)), }, - crate::ast::ScalarTypeKind::UInt(uint_type) => match &uint_type.size { + syntax::ScalarTypeKind::UInt(uint_type) => match &uint_type.size { Some(size) => Some(crate::semantic::types::Type::UInt( Some(self.get_size_designator_from_expr(size)?), is_const, )), None => Some(crate::semantic::types::Type::UInt(None, is_const)), }, - crate::ast::ScalarTypeKind::Float(float_type) => match &float_type.size { + syntax::ScalarTypeKind::Float(float_type) => match &float_type.size { Some(size) => Some(crate::semantic::types::Type::Float( Some(self.get_size_designator_from_expr(size)?), is_const, )), None => Some(crate::semantic::types::Type::Float(None, is_const)), }, - crate::ast::ScalarTypeKind::Complex(complex_type) => match &complex_type.base_size { + syntax::ScalarTypeKind::Complex(complex_type) => match &complex_type.base_size { Some(float_type) => match &float_type.size { Some(size) => Some(crate::semantic::types::Type::Complex( Some(self.get_size_designator_from_expr(size)?), @@ -1027,29 +1161,27 @@ impl Lowerer { }, None => Some(crate::semantic::types::Type::Complex(None, is_const)), }, - crate::ast::ScalarTypeKind::Angle(angle_type) => match &angle_type.size { + syntax::ScalarTypeKind::Angle(angle_type) => match &angle_type.size { Some(size) => Some(crate::semantic::types::Type::Angle( Some(self.get_size_designator_from_expr(size)?), is_const, )), None => Some(crate::semantic::types::Type::Angle(None, is_const)), }, - crate::ast::ScalarTypeKind::BoolType => { - Some(crate::semantic::types::Type::Bool(is_const)) - } - crate::ast::ScalarTypeKind::Duration => { + syntax::ScalarTypeKind::BoolType => Some(crate::semantic::types::Type::Bool(is_const)), + syntax::ScalarTypeKind::Duration => { Some(crate::semantic::types::Type::Duration(is_const)) } - crate::ast::ScalarTypeKind::Stretch => { + syntax::ScalarTypeKind::Stretch => { Some(crate::semantic::types::Type::Stretch(is_const)) } - crate::ast::ScalarTypeKind::Err => Some(crate::semantic::types::Type::Err), + syntax::ScalarTypeKind::Err => Some(crate::semantic::types::Type::Err), } } fn get_semantic_type_from_array_ty( &mut self, - array_ty: &crate::ast::ArrayType, + array_ty: &syntax::ArrayType, _is_const: bool, ) -> Option { self.push_unimplemented_error_message("semantic type from array type", array_ty.span); @@ -1058,7 +1190,7 @@ impl Lowerer { fn get_semantic_type_from_array_reference_ty( &mut self, - array_ref_ty: &crate::ast::ArrayReferenceType, + array_ref_ty: &syntax::ArrayReferenceType, _is_const: bool, ) -> Option { self.push_unimplemented_error_message( @@ -1069,7 +1201,7 @@ impl Lowerer { } fn lower_expr_with_target_type( &mut self, - expr: Option<&crate::ast::Expr>, + expr: Option<&syntax::Expr>, ty: &Type, span: Span, ) -> Option { @@ -1113,7 +1245,7 @@ impl Lowerer { fn lower_measure_expr_with_target_type( &mut self, - _expr: &crate::ast::MeasureExpr, + _expr: &syntax::MeasureExpr, _ty: &Type, span: Span, ) -> Option { @@ -1219,11 +1351,12 @@ impl Lowerer { if types_equal_except_const(ty, &rhs.ty) { // literals are always const, so we can safely return // the const ty - return Some(super::ast::Expr { - span: rhs.span, - kind: rhs.kind.clone(), - ty: ty.as_const(), - }); + if rhs.ty.is_const() { + return Some(rhs.clone()); + } + // the lsh is supposed to be const but is being initialized + // to a non-const value, we can't allow this + return None; } assert!(can_cast_literal(ty, &rhs.ty) || can_cast_literal_with_value_knowledge(ty, kind)); let lhs_ty = ty.clone(); @@ -1320,7 +1453,7 @@ impl Lowerer { } None } - (Type::Float(..), Type::Float(..)) => { + (Type::Angle(..) | Type::Float(..), Type::Float(..)) => { if let super::ast::LiteralKind::Float(value) = kind { return Some(super::ast::Expr { span, @@ -1448,55 +1581,55 @@ impl Lowerer { // based on other qasm implementations. fn lower_negated_literal_as_ty( &mut self, - lit: &crate::ast::Lit, + lit: &syntax::Lit, target_ty: Option, span: Span, ) -> Option { let (kind, ty) = (match &lit.kind { - crate::ast::LiteralKind::Float(value) => Some(( + syntax::LiteralKind::Float(value) => Some(( super::ast::LiteralKind::Float(-value), Type::Float(None, true), )), - crate::ast::LiteralKind::Imaginary(value) => Some(( + syntax::LiteralKind::Imaginary(value) => Some(( super::ast::LiteralKind::Complex(0.0, -value), Type::Complex(None, true), )), - crate::ast::LiteralKind::Int(value) => { + syntax::LiteralKind::Int(value) => { Some((super::ast::LiteralKind::Int(-value), Type::Int(None, true))) } - crate::ast::LiteralKind::BigInt(value) => { + syntax::LiteralKind::BigInt(value) => { let value = BigInt::from(-1) * value; Some(( super::ast::LiteralKind::BigInt(value), Type::Int(None, true), )) } - crate::ast::LiteralKind::Duration(value, time_unit) => { + syntax::LiteralKind::Duration(value, time_unit) => { let unit = match time_unit { - crate::ast::TimeUnit::Dt => super::ast::TimeUnit::Dt, - crate::ast::TimeUnit::Ms => super::ast::TimeUnit::Ms, - crate::ast::TimeUnit::Ns => super::ast::TimeUnit::Ns, - crate::ast::TimeUnit::S => super::ast::TimeUnit::S, - crate::ast::TimeUnit::Us => super::ast::TimeUnit::Us, + syntax::TimeUnit::Dt => super::ast::TimeUnit::Dt, + syntax::TimeUnit::Ms => super::ast::TimeUnit::Ms, + syntax::TimeUnit::Ns => super::ast::TimeUnit::Ns, + syntax::TimeUnit::S => super::ast::TimeUnit::S, + syntax::TimeUnit::Us => super::ast::TimeUnit::Us, }; Some(( super::ast::LiteralKind::Duration(-value, unit), Type::Duration(true), )) } - crate::ast::LiteralKind::Array(_) => { + syntax::LiteralKind::Array(_) => { self.push_unsupported_error_message("negated array literal expressions", span); None } - crate::ast::LiteralKind::Bitstring(_, _) => { + syntax::LiteralKind::Bitstring(_, _) => { self.push_unsupported_error_message("negated bitstring literal expressions", span); None } - crate::ast::LiteralKind::Bool(_) => { + syntax::LiteralKind::Bool(_) => { self.push_unsupported_error_message("negated bool literal expressions", span); None } - crate::ast::LiteralKind::String(_) => { + syntax::LiteralKind::String(_) => { self.push_unsupported_error_message("negated string literal expressions", span); None } @@ -1516,12 +1649,12 @@ impl Lowerer { fn cast_expr_to_type( &mut self, ty: &Type, - rhs: &super::ast::Expr, + expr: &super::ast::Expr, span: Span, ) -> Option { - let cast_expr = self.try_cast_expr_to_type(ty, rhs, span); + let cast_expr = self.try_cast_expr_to_type(ty, expr, span); if cast_expr.is_none() { - let rhs_ty_name = format!("{:?}", rhs.ty); + let rhs_ty_name = format!("{:?}", expr.ty); let lhs_ty_name = format!("{ty:?}"); let kind = SemanticErrorKind::CannotCast(rhs_ty_name, lhs_ty_name, span); self.push_semantic_error(kind); @@ -1532,18 +1665,18 @@ impl Lowerer { fn try_cast_expr_to_type( &mut self, ty: &Type, - rhs: &super::ast::Expr, + expr: &super::ast::Expr, span: Span, ) -> Option { - if *ty == rhs.ty { + if *ty == expr.ty { // Base case, we shouldn't have gotten here // but if we did, we can just return the rhs - return Some(rhs.clone()); + return Some(expr.clone()); } - if types_equal_except_const(ty, &rhs.ty) { - if rhs.ty.is_const() { + 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 - return Some(rhs.clone()); + return Some(expr.clone()); } // the lsh is supposed to be const but is being initialized // to a non-const value, we can't allow this @@ -1552,21 +1685,22 @@ impl Lowerer { // if the target type is wider, we can try to relax the rhs type // We only do this for float and complex. Int types // require extra handling for BigInts - match (ty, &rhs.ty) { - (Type::Float(w1, _), Type::Float(w2, _)) + 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(super::ast::Expr { - span: rhs.span, - kind: rhs.kind.clone(), + span: expr.span, + kind: expr.kind.clone(), ty: ty.clone(), }); } if *w1 >= *w2 { return Some(super::ast::Expr { - span: rhs.span, - kind: rhs.kind.clone(), + span: expr.span, + kind: expr.kind.clone(), ty: ty.clone(), }); } @@ -1576,14 +1710,14 @@ impl Lowerer { // Casting of literals is handled elsewhere. This is for casting expressions // which cannot be bypassed and must be handled by built-in Q# casts from // the standard library. - match &rhs.ty { - Type::Angle(_, _) => self.cast_angle_expr_to_type(ty, rhs, span), - Type::Bit(_) => self.cast_bit_expr_to_type(ty, rhs, span), - Type::Bool(_) => Self::cast_bool_expr_to_type(ty, rhs), - Type::Complex(_, _) => cast_complex_expr_to_type(ty, rhs), - Type::Float(_, _) => self.cast_float_expr_to_type(ty, rhs, span), - Type::Int(_, _) | Type::UInt(_, _) => Self::cast_int_expr_to_type(ty, rhs), - Type::BitArray(dims, _) => Self::cast_bitarray_expr_to_type(dims, ty, rhs), + match &expr.ty { + Type::Angle(_, _) => Self::cast_angle_expr_to_type(ty, expr), + Type::Bit(_) => self.cast_bit_expr_to_type(ty, expr, span), + 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, } } @@ -1595,25 +1729,12 @@ impl Lowerer { /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ /// | angle | Yes | No | No | No | - | Yes | No | No | /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - fn cast_angle_expr_to_type( - &mut self, - ty: &Type, - rhs: &super::ast::Expr, - span: Span, - ) -> Option { - assert!(matches!(rhs.ty, Type::Bit(..))); + fn cast_angle_expr_to_type(ty: &Type, rhs: &super::ast::Expr) -> Option { + assert!(matches!(rhs.ty, Type::Angle(..))); match ty { - Type::Bit(..) => { - let msg = "Cast angle to bit"; - self.push_unimplemented_error_message(msg, span); - None - } - Type::Bool(..) => { - let msg = "Cast angle to bool"; - self.push_unimplemented_error_message(msg, span); - None + Type::Bit(..) | Type::Bool(..) => { + Some(wrap_expr_in_implicit_cast_expr(ty.clone(), rhs.clone())) } - _ => None, } } @@ -1663,23 +1784,17 @@ impl Lowerer { /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ /// /// Additional cast to complex - fn cast_float_expr_to_type( - &mut self, - ty: &Type, - rhs: &super::ast::Expr, - span: Span, - ) -> Option { + fn cast_float_expr_to_type(ty: &Type, rhs: &super::ast::Expr) -> Option { assert!(matches!(rhs.ty, Type::Float(..))); match ty { - &Type::Complex(_, _) | &Type::Int(_, _) | &Type::UInt(_, _) | &Type::Bool(_) => { + &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())) } - &Type::Angle(..) => { - let msg = "Cast float to angle"; - self.push_unimplemented_error_message(msg, span); - None - } _ => None, } } @@ -1749,11 +1864,418 @@ impl Lowerer { None } } + + #[allow(clippy::too_many_lines)] + fn lower_binary_op_expr( + &mut self, + op: syntax::BinOp, + lhs: super::ast::Expr, + rhs: super::ast::Expr, + span: Span, + ) -> Option { + 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, false); + 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); + } + return None; + }; + // Now that we know the effective Uint type, we can cast the lhs and rhs + // so that operations can be performed on them. + let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs.span)?; + // 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, rhs.span)? + } else { + rhs + }; + + let bin_expr = super::ast::BinaryOpExpr { + lhs: new_lhs, + rhs: new_rhs, + op: op.into(), + }; + let kind = super::ast::ExprKind::BinaryOp(bin_expr); + let expr = super::ast::Expr { + span, + kind: Box::new(kind), + ty, + }; + + let final_expr = self.cast_expr_to_type(&left_type, &expr, span)?; + return Some(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 (lhs, rhs, ty) = if matches!(op, syntax::BinOp::AndL | syntax::BinOp::OrL) { + let ty = Type::Bool(false); + let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs.span)?; + let new_rhs = self.cast_expr_to_type(&ty, &rhs, rhs.span)?; + (new_lhs, new_rhs, ty) + } else if binop_requires_int_conversion_for_type(op, &left_type, &rhs.ty) { + let ty = Type::Int(None, false); + let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs.span)?; + let new_rhs = self.cast_expr_to_type(&ty, &rhs, lhs.span)?; + (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() { + super::ast::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, lhs.span)? + } + } + _ => self.cast_expr_to_type(&promoted_type, &lhs, lhs.span)?, + } + }; + let new_right = if promoted_type == right_type { + rhs + } else { + match &rhs.kind.as_ref() { + super::ast::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, rhs.span)? + } + } + _ => self.cast_expr_to_type(&promoted_type, &rhs, rhs.span)?, + } + }; + (new_left, new_right, promoted_type) + } else if binop_requires_symmetric_int_conversion(op) { + let ty = Type::Int(None, false); + let new_rhs = self.cast_expr_to_type(&ty, &rhs, rhs.span)?; + (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) { + // TODO: How do we handle complex binary expressions? + // this is going to be a call to a built-in function + // that doesn't map to qasm def semantics + self.push_unimplemented_error_message("complex binary exprs", span); + None + } else { + let kind = SemanticErrorKind::OperatorNotSupportedForTypes( + format!("{op:?}"), + format!("{ty:?}"), + format!("{ty:?}"), + span, + ); + self.push_semantic_error(kind); + None + } + } else { + let bin_expr = super::ast::BinaryOpExpr { + op: op.into(), + lhs, + rhs, + }; + let kind = super::ast::ExprKind::BinaryOp(bin_expr); + let expr = super::ast::Expr { + span, + kind: Box::new(kind), + ty: ty.clone(), + }; + Some(expr) + }; + + let ty = match op.into() { + super::ast::BinOp::AndL + | super::ast::BinOp::Eq + | super::ast::BinOp::Gt + | super::ast::BinOp::Gte + | super::ast::BinOp::Lt + | super::ast::BinOp::Lte + | super::ast::BinOp::Neq + | super::ast::BinOp::OrL => Type::Bool(false), + _ => ty, + }; + let mut expr = expr?; + expr.ty = ty; + Some(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::Angle(..) + | Type::BitArray(ArrayDimensions::One(_), _) + )) { + return true; + } + if (matches!(op, syntax::BinOp::Shl | syntax::BinOp::Shr) + && matches!( + left_type, + Type::Bit(..) + | Type::UInt(..) + | Type::Angle(..) + | 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 + ) + } + + // TODO: which these are parsed as different types, they are effectively the same + fn lower_index_element( + &mut self, + index: &syntax::IndexElement, + ) -> Option { + match index { + syntax::IndexElement::DiscreteSet(set) => { + let items = set + .values + .iter() + .filter_map(|expr| self.lower_expr(expr)) + .collect::>(); + if set.values.len() == items.len() { + return Some(super::ast::IndexElement::DiscreteSet( + super::ast::DiscreteSet { + span: set.span, + values: syntax::list_from_iter(items), + }, + )); + } + let kind = SemanticErrorKind::FailedToCompileExpressionList(set.span); + self.push_semantic_error(kind); + None + } + syntax::IndexElement::IndexSet(set) => { + let items = set + .values + .iter() + .filter_map(|expr| self.lower_index_set_item(expr)) + .collect::>(); + if set.values.len() == items.len() { + return Some(super::ast::IndexElement::IndexSet(super::ast::IndexSet { + span: set.span, + values: syntax::list_from_iter(items), + })); + } + let kind = SemanticErrorKind::FailedToCompileExpressionList(set.span); + self.push_semantic_error(kind); + None + } + } + } + + fn lower_index_set_item( + &mut self, + item: &syntax::IndexSetItem, + ) -> Option { + let item = match item { + syntax::IndexSetItem::RangeDefinition(range_definition) => { + super::ast::IndexSetItem::RangeDefinition( + self.lower_range_definition(range_definition)?, + ) + } + syntax::IndexSetItem::Expr(expr) => { + super::ast::IndexSetItem::Expr(self.lower_expr(expr)?) + } + syntax::IndexSetItem::Err => { + unreachable!("IndexSetItem::Err should have been handled") + } + }; + Some(item) + } + + fn lower_range_definition( + &mut self, + range_definition: &syntax::RangeDefinition, + ) -> Option { + let mut failed = false; + let start = range_definition.start.as_ref().map(|e| { + let expr = self.lower_expr(e); + if expr.is_none() { + failed = true; + } + expr + }); + let step = range_definition.step.as_ref().map(|e| { + let expr = self.lower_expr(e); + if expr.is_none() { + failed = true; + } + expr + }); + let end = range_definition.end.as_ref().map(|e| { + let expr = self.lower_expr(e); + if expr.is_none() { + failed = true; + } + expr + }); + if failed { + return None; + } + Some(super::ast::RangeDefinition { + span: range_definition.span, + start: start.map(|s| s.expect("start should be Some")), + step: step.map(|s| s.expect("step should be Some")), + end: end.map(|e| e.expect("end should be Some")), + }) + } + + fn lower_index_expr(&mut self, expr: &syntax::IndexExpr) -> Option { + let collection = self.lower_expr(&expr.collection); + let index = self.lower_index_element(&expr.index); + let collection = collection?; + let index = index?; + + let indexed_ty = self.get_indexed_type(&collection.ty, expr.span, 1)?; + + Some(super::ast::Expr { + span: expr.span, + kind: Box::new(super::ast::ExprKind::IndexExpr(super::ast::IndexExpr { + span: expr.span, + collection, + index, + })), + ty: indexed_ty, + }) + } + + fn get_indexed_type( + &mut self, + ty: &Type, + span: Span, + num_indices: usize, + ) -> Option { + if !ty.is_array() { + let kind = SemanticErrorKind::CannotIndexType(format!("{ty:?}"), span); + self.push_semantic_error(kind); + return None; + } + + if num_indices > ty.num_dims() { + let kind = SemanticErrorKind::TooManyIndices(span); + self.push_semantic_error(kind); + return None; + } + + 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 None; + }; + indexed_ty = ty; + } + Some(indexed_ty) + } + + fn lower_indexed_ident_expr( + &mut self, + indexed_ident: &syntax::IndexedIdent, + ) -> Option { + let ident = indexed_ident.name.clone(); + + let indices = indexed_ident + .indices + .iter() + .filter_map(|index| self.lower_index_element(index)) + .collect::>(); + + let (symbol_id, lhs_symbol) = self.symbols.get_symbol_by_name(&ident.name)?; + 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())?; + + if indices.len() != indexed_ident.indices.len() { + // we failed to lower all the indices, error was already pushed + return None; + } + + Some(super::ast::Expr { + span: indexed_ident.span, + kind: Box::new(super::ast::ExprKind::IndexedIdentifier( + super::ast::IndexedIdent { + span: indexed_ident.span, + symbol_id, + indices: syntax::list_from_iter(indices), + }, + )), + ty, + }) + } } fn wrap_expr_in_implicit_cast_expr(ty: Type, rhs: super::ast::Expr) -> super::ast::Expr { super::ast::Expr { - span: Span::default(), + span: rhs.span, kind: Box::new(super::ast::ExprKind::Cast(super::ast::Cast { span: Span::default(), expr: rhs, @@ -1791,9 +2313,9 @@ fn cast_complex_expr_to_type(ty: &Type, rhs: &super::ast::Expr) -> Option std::rc::Rc { +fn get_identifier_name(identifier: &syntax::Identifier) -> std::rc::Rc { match identifier { - crate::ast::Identifier::Ident(ident) => ident.name.clone(), - crate::ast::Identifier::IndexedIdent(ident) => ident.name.name.clone(), + syntax::Identifier::Ident(ident) => ident.name.clone(), + syntax::Identifier::IndexedIdent(ident) => ident.name.name.clone(), } } diff --git a/compiler/qsc_qasm3/src/semantic/symbols.rs b/compiler/qsc_qasm3/src/semantic/symbols.rs index a9ebfe99a2..da03471c63 100644 --- a/compiler/qsc_qasm3/src/semantic/symbols.rs +++ b/compiler/qsc_qasm3/src/semantic/symbols.rs @@ -98,7 +98,7 @@ pub struct Symbol { impl std::fmt::Display for Symbol { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use crate::ast::display_utils; + 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)?; @@ -131,19 +131,14 @@ pub enum SymbolError { /// The default I/O kind means no explicit kind was part of the decl. /// There is a specific statement for io decls which sets the I/O kind appropriately. /// This is used to determine the input and output symbols in the program. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Copy, Default, Debug, Clone, PartialEq, Eq)] pub enum IOKind { + #[default] Default, Input, Output, } -impl Default for IOKind { - fn default() -> Self { - Self::Default - } -} - impl std::fmt::Display for IOKind { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { diff --git a/compiler/qsc_qasm3/src/semantic/tests.rs b/compiler/qsc_qasm3/src/semantic/tests.rs index 8aa9e00e81..490a573561 100644 --- a/compiler/qsc_qasm3/src/semantic/tests.rs +++ b/compiler/qsc_qasm3/src/semantic/tests.rs @@ -1,8 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +pub mod assignment; pub mod decls; +pub mod expression; + use std::path::Path; use std::sync::Arc; @@ -89,15 +92,18 @@ pub(super) fn check_classical_decls(input: &str, expect: &Expect) { .map(|stmt| stmt.kind.as_ref().clone()) .collect::>(); let mut value = String::new(); - for kind in kinds { - let super::ast::StmtKind::ClassicalDecl(decl) = kind else { - panic!("expected classical declaration statement"); + for kind in &kinds { + let (symbol_id, str) = match kind { + super::ast::StmtKind::ClassicalDecl(decl) => (decl.symbol_id, decl.to_string()), + super::ast::StmtKind::IODeclaration(decl) => (decl.symbol_id, decl.to_string()), + super::ast::StmtKind::Assign(stmt) => (stmt.symbold_id, stmt.to_string()), + super::ast::StmtKind::AssignOp(stmt) => (stmt.symbold_id, stmt.to_string()), + _ => panic!("unsupported stmt type {kind}"), }; - value.push_str(&decl.to_string()); + + value.push_str(&str); value.push('\n'); - let symbol = s - .get_symbol_by_id(decl.symbol_id) - .expect("getting symbol by id"); + let symbol = s.get_symbol_by_id(symbol_id).expect("getting symbol by id"); value.push_str(&format!("[{}] {}", symbol.0, symbol.1)); value.push('\n'); } 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 index f5f926e956..b9ed2cc462 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +mod angle; mod bit; mod bool; mod complex; 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..22759f8ce0 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs @@ -0,0 +1,415 @@ +// 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: 6 + ty_span: [0-5] + init_expr: Expr [0-0]: + ty: Angle(None, true) + kind: Lit: Float(0.0) + [6] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_default_fails() { + check_classical_decl( + "const angle x;", + &expect![[r#" + Program: + version: + statements: + + [Qasm3.Parse.Token + + x expected `=`, found `;` + ,-[test:1:14] + 1 | const angle x; + : ^ + `---- + , Qsc.Qasm3.Compile.UnexpectedParserError + + x Unexpected parser error: Unexpected error. + ,-[test:1:1] + 1 | const angle x; + : ^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} + +#[test] +fn lit() { + check_classical_decl( + "angle x = 42.1;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 6 + ty_span: [0-5] + init_expr: Expr [10-14]: + ty: Angle(None, true) + kind: Lit: Float(42.1) + [6] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit() { + check_classical_decl( + "const angle x = 42.1;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 6 + ty_span: [6-11] + init_expr: Expr [16-20]: + ty: Angle(None, true) + kind: Lit: Float(42.1) + [6] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn lit_explicit_width() { + check_classical_decl( + "angle[64] x = 42.1;", + &expect![[r#" + ClassicalDeclarationStmt [0-19]: + symbol_id: 6 + ty_span: [0-9] + init_expr: Expr [14-18]: + ty: Angle(Some(64), true) + kind: Lit: Float(42.1) + [6] Symbol [10-11]: + name: x + type: Angle(Some(64), false) + qsharp_type: Double + 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: 6 + ty_span: [6-15] + init_expr: Expr [20-24]: + ty: Angle(Some(64), true) + kind: Lit: Float(42.1) + [6] Symbol [16-17]: + name: x + type: Angle(Some(64), true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_leading_dot() { + check_classical_decl( + "angle x = .421;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 6 + ty_span: [0-5] + init_expr: Expr [10-14]: + ty: Angle(None, true) + kind: Lit: Float(0.421) + [6] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_leading_dot() { + check_classical_decl( + "const angle x = .421;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 6 + ty_span: [6-11] + init_expr: Expr [16-20]: + ty: Angle(None, true) + kind: Lit: Float(0.421) + [6] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Double + 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: 6 + ty_span: [6-11] + init_expr: Expr [16-22]: + ty: Angle(None, true) + kind: Lit: Float(42.1) + [6] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_trailing_dot() { + check_classical_decl( + "angle x = 421.;", + &expect![[r#" + ClassicalDeclarationStmt [0-15]: + symbol_id: 6 + ty_span: [0-5] + init_expr: Expr [10-14]: + ty: Angle(None, true) + kind: Lit: Float(421.0) + [6] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_trailing_dot() { + check_classical_decl( + "const angle x = 421.;", + &expect![[r#" + ClassicalDeclarationStmt [0-21]: + symbol_id: 6 + ty_span: [6-11] + init_expr: Expr [16-20]: + ty: Angle(None, true) + kind: Lit: Float(421.0) + [6] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_scientific() { + check_classical_decl( + "angle x = 4.21e1;", + &expect![[r#" + ClassicalDeclarationStmt [0-17]: + symbol_id: 6 + ty_span: [0-5] + init_expr: Expr [10-16]: + ty: Angle(None, true) + kind: Lit: Float(42.1) + [6] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn const_lit_decl_scientific() { + check_classical_decl( + "const angle x = 4.21e1;", + &expect![[r#" + ClassicalDeclarationStmt [0-23]: + symbol_id: 6 + ty_span: [6-11] + init_expr: Expr [16-22]: + ty: Angle(None, true) + kind: Lit: Float(42.1) + [6] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Double + 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: 6 + ty_span: [0-5] + init_expr: Expr [10-17]: + ty: Angle(None, true) + kind: Lit: Float(42.1) + [6] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Double + 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: 6 + ty_span: [6-11] + init_expr: Expr [16-23]: + ty: Angle(None, true) + kind: Lit: Float(42.1) + [6] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Double + io_kind: Default"#]], + ); +} + +#[test] +fn lit_decl_scientific_cap_e() { + check_classical_decl( + "angle x = 4.21E1;", + &expect![[r#" + ClassicalDeclarationStmt [0-17]: + symbol_id: 6 + ty_span: [0-5] + init_expr: Expr [10-16]: + ty: Angle(None, true) + kind: Lit: Float(42.1) + [6] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Double + 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: 6 + ty_span: [6-11] + init_expr: Expr [16-22]: + ty: Angle(None, true) + kind: Lit: Float(42.1) + [6] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Double + 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: 6 + ty_span: [0-5] + init_expr: Expr [10-18]: + ty: Angle(None, true) + kind: Lit: Float(42.1) + [6] Symbol [6-7]: + name: x + type: Angle(None, false) + qsharp_type: Double + 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: 6 + ty_span: [6-11] + init_expr: Expr [16-24]: + ty: Angle(None, true) + kind: Lit: Float(42.1) + [6] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Double + 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: 6 + ty_span: [6-11] + init_expr: Expr [17-19]: + ty: Angle(None, true) + kind: Lit: Float(-7.0) + [6] Symbol [12-13]: + name: x + type: Angle(None, true) + qsharp_type: Double + 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: + + [Qsc.Qasm3.Compile.CannotAssignToType + + x Cannot assign a value of Int(None, true) type to a classical variable of + | Angle(None, true) type. + ,-[test:1:1] + 1 | const angle x = -7; + : ^^^^^^^^^^^^^^^^^^^ + `---- + ]"#]], + ); +} 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..783217bc1d --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/expression.rs @@ -0,0 +1,57 @@ +// 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; + +use expect_test::expect; + +use super::check_stmt_kinds; + +#[test] +fn a() { + check_stmt_kinds( + r#" + true && false; + false || true; + !true; + "#, + &expect![[r#" + ExprStmt [9-23]: + expr: Expr [9-22]: + ty: Bool(false) + kind: BinaryOpExpr: + op: AndL + lhs: Expr [9-13]: + ty: Bool(true) + kind: Lit: Bool(true) + rhs: Expr [17-22]: + ty: Bool(true) + kind: Lit: Bool(false) + ExprStmt [32-46]: + expr: Expr [32-45]: + ty: Bool(false) + kind: BinaryOpExpr: + op: OrL + lhs: Expr [32-37]: + ty: Bool(true) + kind: Lit: Bool(false) + rhs: Expr [41-45]: + ty: Bool(true) + kind: Lit: Bool(true) + ExprStmt [55-61]: + expr: Expr [56-60]: + ty: Bool(true) + kind: UnaryOpExpr: + op: NotL + expr: Expr [56-60]: + ty: Bool(true) + kind: Lit: Bool(true) + "#]], + ); +} 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..1df284f262 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/arithmetic_conversions.rs @@ -0,0 +1,264 @@ +// 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, true) + 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(6) + rhs: Expr [51-52]: + ty: Int(None, false) + kind: SymbolId(7) + "#]], + ); +} + +#[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: 6 + ty_span: [9-16] + init_expr: Expr [21-22]: + ty: Int(Some(32), true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [32-46]: + symbol_id: 7 + 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(6) + rhs: Expr [59-60]: + ty: Int(Some(32), false) + kind: SymbolId(7) + "#]], + ); +} + +#[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: 6 + ty_span: [9-16] + init_expr: Expr [21-22]: + ty: Int(Some(32), true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [32-46]: + symbol_id: 7 + 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(6) + rhs: Expr [59-60]: + ty: Int(Some(64), false) + kind: SymbolId(7) + "#]], + ); +} + +#[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: 6 + ty_span: [9-16] + init_expr: Expr [21-22]: + ty: Int(Some(32), true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [32-46]: + symbol_id: 7 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Int(Some(64), true) + kind: Lit: Int(3) + ClassicalDeclarationStmt [55-73]: + symbol_id: 8 + 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(6) + rhs: Expr [71-72]: + ty: Int(Some(64), false) + kind: SymbolId(7) + "#]], + ); +} + +#[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: 6 + ty_span: [9-16] + init_expr: Expr [21-22]: + ty: Int(Some(32), true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [32-46]: + symbol_id: 7 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Int(Some(64), true) + kind: Lit: Int(3) + ClassicalDeclarationStmt [55-69]: + symbol_id: 8 + 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(6) + rhs: Expr [67-68]: + ty: Int(Some(64), false) + kind: SymbolId(7) + "#]], + ); +} + +#[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: 6 + ty_span: [9-16] + init_expr: Expr [21-22]: + ty: Int(Some(32), true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [32-46]: + symbol_id: 7 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Int(Some(64), true) + kind: Lit: Int(3) + ClassicalDeclarationStmt [55-73]: + symbol_id: 8 + 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(6) + rhs: Expr [71-72]: + ty: Int(Some(64), false) + kind: SymbolId(7) + "#]], + ); +} 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..1743c4c3ec --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison.rs @@ -0,0 +1,53 @@ +// 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; + +#[test] +#[ignore = "not yet implemented"] +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#""#]]); +} + +#[test] +#[ignore = "not yet implemented"] +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#""#]]); +} 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..2aa0c1eec2 --- /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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Int(0) + [7] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-63]: + symbol_id: 8 + 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(6) + rhs: Expr [61-62]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [61-62]: + ty: Bit(false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Int(0) + [7] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-63]: + symbol_id: 8 + 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(6) + rhs: Expr [61-62]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [61-62]: + ty: Bit(false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Int(0) + [7] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-65]: + symbol_id: 8 + 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: + 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(6) + rhs: Expr [63-64]: + ty: Bool(false) + kind: UnaryOpExpr: + 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(7) + [8] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Int(0) + [7] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-65]: + symbol_id: 8 + 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: + 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(6) + rhs: Expr [63-64]: + ty: Bool(false) + kind: UnaryOpExpr: + 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(7) + [8] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Int(0) + [7] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-64]: + symbol_id: 8 + 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: + 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(6) + rhs: Expr [62-63]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [62-63]: + ty: Bit(false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Int(0) + [7] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-64]: + symbol_id: 8 + 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: + 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(6) + rhs: Expr [62-63]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [62-63]: + ty: Bit(false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Int(0) + [7] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-64]: + symbol_id: 8 + 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(6) + rhs: Expr [62-63]: + ty: Bool(false) + kind: UnaryOpExpr: + 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(7) + [8] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Bit(true) + kind: Lit: Int(0) + [7] Symbol [32-33]: + name: y + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [47-64]: + symbol_id: 8 + 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(6) + rhs: Expr [62-63]: + ty: Bool(false) + kind: UnaryOpExpr: + 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(7) + [8] 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..df79f99889 --- /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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 7 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(true) + kind: Lit: Bool(false) + [7] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-72]: + symbol_id: 8 + 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(6) + rhs: Expr [70-71]: + ty: Bool(false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 7 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(true) + kind: Lit: Bool(false) + [7] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-72]: + symbol_id: 8 + 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(6) + rhs: Expr [70-71]: + ty: Bool(false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 7 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(true) + kind: Lit: Bool(false) + [7] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-74]: + symbol_id: 8 + 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: + op: NotL + expr: Expr [66-67]: + ty: Bool(false) + kind: SymbolId(6) + rhs: Expr [72-73]: + ty: Bool(false) + kind: UnaryOpExpr: + op: NotL + expr: Expr [72-73]: + ty: Bool(false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 7 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(true) + kind: Lit: Bool(false) + [7] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-74]: + symbol_id: 8 + 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: + op: NotL + expr: Expr [66-67]: + ty: Bool(false) + kind: SymbolId(6) + rhs: Expr [72-73]: + ty: Bool(false) + kind: UnaryOpExpr: + op: NotL + expr: Expr [72-73]: + ty: Bool(false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 7 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(true) + kind: Lit: Bool(false) + [7] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-73]: + symbol_id: 8 + 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: + op: NotL + expr: Expr [66-67]: + ty: Bool(false) + kind: SymbolId(6) + rhs: Expr [71-72]: + ty: Bool(false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 7 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(true) + kind: Lit: Bool(false) + [7] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-73]: + symbol_id: 8 + 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: + op: NotL + expr: Expr [66-67]: + ty: Bool(false) + kind: SymbolId(6) + rhs: Expr [71-72]: + ty: Bool(false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 7 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(true) + kind: Lit: Bool(false) + [7] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-73]: + symbol_id: 8 + 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(6) + rhs: Expr [71-72]: + ty: Bool(false) + kind: UnaryOpExpr: + op: NotL + expr: Expr [71-72]: + ty: Bool(false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 7 + ty_span: [32-36] + init_expr: Expr [41-46]: + ty: Bool(true) + kind: Lit: Bool(false) + [7] Symbol [37-38]: + name: y + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [56-73]: + symbol_id: 8 + 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(6) + rhs: Expr [71-72]: + ty: Bool(false) + kind: UnaryOpExpr: + op: NotL + expr: Expr [71-72]: + ty: Bool(false) + kind: SymbolId(7) + [8] 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..76cf8683f6 --- /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: 6 + ty_span: [9-14] + init_expr: Expr [19-21]: + ty: Float(None, true) + kind: Lit: Float(5.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [31-44]: + symbol_id: 7 + ty_span: [31-36] + init_expr: Expr [41-43]: + ty: Float(None, true) + kind: Lit: Float(3.0) + [7] Symbol [37-38]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [53-68]: + symbol_id: 8 + 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(6) + rhs: Expr [66-67]: + ty: Float(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-21]: + ty: Float(None, true) + kind: Lit: Float(5.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [31-44]: + symbol_id: 7 + ty_span: [31-36] + init_expr: Expr [41-43]: + ty: Float(None, true) + kind: Lit: Float(3.0) + [7] Symbol [37-38]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [53-69]: + symbol_id: 8 + 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(6) + rhs: Expr [67-68]: + ty: Float(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-21]: + ty: Float(None, true) + kind: Lit: Float(5.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [31-44]: + symbol_id: 7 + ty_span: [31-36] + init_expr: Expr [41-43]: + ty: Float(None, true) + kind: Lit: Float(3.0) + [7] Symbol [37-38]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [53-68]: + symbol_id: 8 + 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(6) + rhs: Expr [66-67]: + ty: Float(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-21]: + ty: Float(None, true) + kind: Lit: Float(5.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [31-44]: + symbol_id: 7 + ty_span: [31-36] + init_expr: Expr [41-43]: + ty: Float(None, true) + kind: Lit: Float(3.0) + [7] Symbol [37-38]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [53-69]: + symbol_id: 8 + 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(6) + rhs: Expr [67-68]: + ty: Float(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-21]: + ty: Float(None, true) + kind: Lit: Float(5.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [31-44]: + symbol_id: 7 + ty_span: [31-36] + init_expr: Expr [41-43]: + ty: Float(None, true) + kind: Lit: Float(3.0) + [7] Symbol [37-38]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [53-69]: + symbol_id: 8 + 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(6) + rhs: Expr [67-68]: + ty: Float(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-21]: + ty: Float(None, true) + kind: Lit: Float(5.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [31-44]: + symbol_id: 7 + ty_span: [31-36] + init_expr: Expr [41-43]: + ty: Float(None, true) + kind: Lit: Float(3.0) + [7] Symbol [37-38]: + name: y + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [53-69]: + symbol_id: 8 + 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(6) + rhs: Expr [67-68]: + ty: Float(None, false) + kind: SymbolId(7) + [8] 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..b9a35b85f0 --- /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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, true) + kind: Lit: Int(5) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, true) + kind: Lit: Int(3) + [7] Symbol [32-33]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [47-62]: + symbol_id: 8 + 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(6) + rhs: Expr [60-61]: + ty: Int(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, true) + kind: Lit: Int(5) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, true) + kind: Lit: Int(3) + [7] Symbol [32-33]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [47-63]: + symbol_id: 8 + 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(6) + rhs: Expr [61-62]: + ty: Int(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, true) + kind: Lit: Int(5) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, true) + kind: Lit: Int(3) + [7] Symbol [32-33]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [47-62]: + symbol_id: 8 + 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(6) + rhs: Expr [60-61]: + ty: Int(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, true) + kind: Lit: Int(5) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, true) + kind: Lit: Int(3) + [7] Symbol [32-33]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [47-63]: + symbol_id: 8 + 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(6) + rhs: Expr [61-62]: + ty: Int(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, true) + kind: Lit: Int(5) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, true) + kind: Lit: Int(3) + [7] Symbol [32-33]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [47-63]: + symbol_id: 8 + 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(6) + rhs: Expr [61-62]: + ty: Int(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, true) + kind: Lit: Int(5) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, true) + kind: Lit: Int(3) + [7] Symbol [32-33]: + name: y + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [47-63]: + symbol_id: 8 + 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(6) + rhs: Expr [61-62]: + ty: Int(None, false) + kind: SymbolId(7) + [8] 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..ba39fdf302 --- /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: 6 + ty_span: [9-13] + init_expr: Expr [18-19]: + ty: UInt(None, true) + kind: Lit: Int(5) + [6] Symbol [14-15]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 7 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: UInt(None, true) + kind: Lit: Int(3) + [7] Symbol [34-35]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [49-64]: + symbol_id: 8 + 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(6) + rhs: Expr [62-63]: + ty: UInt(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-19]: + ty: UInt(None, true) + kind: Lit: Int(5) + [6] Symbol [14-15]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 7 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: UInt(None, true) + kind: Lit: Int(3) + [7] Symbol [34-35]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [49-65]: + symbol_id: 8 + 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(6) + rhs: Expr [63-64]: + ty: UInt(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-19]: + ty: UInt(None, true) + kind: Lit: Int(5) + [6] Symbol [14-15]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 7 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: UInt(None, true) + kind: Lit: Int(3) + [7] Symbol [34-35]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [49-64]: + symbol_id: 8 + 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(6) + rhs: Expr [62-63]: + ty: UInt(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-19]: + ty: UInt(None, true) + kind: Lit: Int(5) + [6] Symbol [14-15]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 7 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: UInt(None, true) + kind: Lit: Int(3) + [7] Symbol [34-35]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [49-65]: + symbol_id: 8 + 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(6) + rhs: Expr [63-64]: + ty: UInt(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-19]: + ty: UInt(None, true) + kind: Lit: Int(5) + [6] Symbol [14-15]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 7 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: UInt(None, true) + kind: Lit: Int(3) + [7] Symbol [34-35]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [49-65]: + symbol_id: 8 + 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(6) + rhs: Expr [63-64]: + ty: UInt(None, false) + kind: SymbolId(7) + [8] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-19]: + ty: UInt(None, true) + kind: Lit: Int(5) + [6] Symbol [14-15]: + name: x + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 7 + ty_span: [29-33] + init_expr: Expr [38-39]: + ty: UInt(None, true) + kind: Lit: Int(3) + [7] Symbol [34-35]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [49-65]: + symbol_id: 8 + 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(6) + rhs: Expr [63-64]: + ty: UInt(None, false) + kind: SymbolId(7) + [8] 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..8e3f78604f --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_stmt_kinds; + +#[test] +#[ignore = "not yet implemented"] +fn subtraction() { + let input = " + input complex[float] a; + input complex[float] b; + complex x = (a - b); + "; + + check_stmt_kinds(input, &expect![[r#""#]]); +} + +#[test] +#[ignore = "not yet implemented"] +fn addition() { + let input = " + input complex[float] a; + input complex[float] b; + complex x = (a + b); + "; + + check_stmt_kinds(input, &expect![[r#""#]]); +} + +#[test] +#[ignore = "not yet implemented"] +fn multiplication() { + let input = " + input complex[float] a; + input complex[float] b; + complex x = (a * b); + "; + + check_stmt_kinds(input, &expect![[r#""#]]); +} + +#[test] +#[ignore = "not yet implemented"] +fn division() { + let input = " + input complex[float] a; + input complex[float] b; + complex x = (a / b); + "; + + check_stmt_kinds(input, &expect![[r#""#]]); +} + +#[test] +#[ignore = "not yet implemented"] +fn power() { + let input = " + input complex[float] a; + input complex[float] b; + complex x = (a ** b); + "; + + check_stmt_kinds(input, &expect![[r#""#]]); +} 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..60b21ef238 --- /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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + ty_span: [28-31] + init_expr: Expr [36-37]: + ty: Int(None, true) + 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(6) + rhs: Expr [51-52]: + ty: Int(None, false) + kind: SymbolId(7) + "#]], + ); +} + +#[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: 6 + ty_span: [15-18] + init_expr: Expr [23-24]: + ty: Int(None, true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [34-50]: + symbol_id: 7 + 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(6) + rhs: Expr [63-64]: + ty: Int(None, true) + kind: SymbolId(7) + "#]], + ); +} + +#[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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Int(None, true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [28-44]: + symbol_id: 7 + 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(6) + rhs: Expr [57-58]: + ty: Int(None, true) + kind: SymbolId(7) + "#]], + ); +} + +#[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: 6 + ty_span: [15-22] + init_expr: Expr [27-28]: + ty: Int(Some(32), true) + kind: Lit: Int(5) + ClassicalDeclarationStmt [38-54]: + symbol_id: 7 + 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(6) + rhs: Expr [67-68]: + ty: Int(None, true) + kind: SymbolId(7) + "#]], + ); +} 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..b09516234a --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_angle.rs @@ -0,0 +1,556 @@ +// 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Angle(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-42]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-18] + init_expr: Expr [23-26]: + ty: Angle(Some(64), true) + kind: Lit: Float(42.0) + [6] Symbol [19-20]: + name: x + type: Angle(Some(64), false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [36-46]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Angle(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-43]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + + [Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Angle(None, false) to type Int(None, false) + ,-[test:3:9] + 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + + [Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Angle(None, false) to type Int(Some(32), + | false) + ,-[test:3:9] + 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + + [Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Angle(None, false) to type UInt(None, + | false) + ,-[test:3:9] + 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: 6 + ty_span: [9-14] + init_expr: Expr [20-23]: + ty: Angle(None, true) + kind: Lit: Float(-42.0) + + [Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Angle(None, false) to type UInt(None, + | false) + ,-[test:3:9] + 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + + [Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Angle(None, false) to type UInt(Some(32), + | false) + ,-[test:3:9] + 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + + [Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Angle(None, false) to type Int(Some(65), + | false) + ,-[test:3:9] + 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + + [Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Angle(None, false) to type Float(None, + | false) + ,-[test:3:9] + 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + + [Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Angle(None, false) to type Float(Some(32), + | false) + ,-[test:3:9] + 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + + [Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Angle(None, false) to type Complex(None, + | false) + ,-[test:3:9] + 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + + [Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Angle(None, false) to type + | Complex(Some(32), false) + ,-[test:3:9] + 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Angle(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-44]: + symbol_id: 7 + ty_span: [32-37] + init_expr: Expr [42-43]: + ty: Angle(None, false) + kind: SymbolId(6) + [7] Symbol [38-39]: + name: y + type: Angle(None, false) + qsharp_type: Double + 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Angle(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 7 + ty_span: [32-40] + init_expr: Expr [45-46]: + ty: Angle(Some(4), false) + kind: SymbolId(6) + [7] Symbol [41-42]: + name: y + type: Angle(Some(4), false) + qsharp_type: Double + 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..0c701f3601 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_bit.rs @@ -0,0 +1,303 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decls; + +#[test] +#[ignore = "not yet implemented"] +fn to_angle_implicitly() { + let input = r#" + bit x = 1; + angle y = x; + "#; + + check_classical_decls(input, &expect![[r#""#]]); +} + +#[test] +#[ignore = "not yet implemented"] +fn to_explicit_angle_implicitly() { + let input = r#" + bit x = 1; + angle[4] y = x; + "#; + + check_classical_decls(input, &expect![[r#""#]]); +} + +#[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: 6 + ty_span: [10-13] + init_expr: Expr [18-19]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [14-15]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [30-41]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-38]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-42]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-39]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-43]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + [6] Symbol [13-14]: + name: x + type: Bit(false) + qsharp_type: Result + io_kind: Default + ClassicalDeclarationStmt [28-42]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-18]: + ty: Bit(true) + kind: Lit: Int(1) + + [Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Bit(false) to type Float(None, false) + ,-[test:3:9] + 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..019a219e04 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_bitarray.rs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_classical_decls; + +#[test] +#[ignore = "not yet implemented"] +fn to_int_decl_implicitly() { + let input = r#" + bit[5] reg; + int b = reg; + "#; + + check_classical_decls(input, &expect![[r#""#]]); +} + +#[test] +#[ignore = "not yet implemented"] +fn to_int_assignment_implicitly() { + let input = r#" + bit[5] reg; + int a; + a = reg; + "#; + + check_classical_decls(input, &expect![[r#""#]]); +} + +#[test] +#[ignore = "not yet implemented"] +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#""#]]); +} + +#[test] +#[ignore = "not yet implemented"] +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#""#]]); +} + +#[test] +#[ignore = "not yet implemented"] +fn to_int_with_higher_width_implicitly_fails() { + let input = " + int[6] a; + bit[5] reg; + a = reg; + "; + + check_classical_decls(input, &expect![[r#""#]]); +} + +#[test] +#[ignore = "not yet implemented"] +fn to_int_with_higher_width_decl_implicitly_fails() { + let input = " + bit[5] reg; + int[6] a = reg; + "; + check_classical_decls(input, &expect![[r#""#]]); +} + +#[test] +#[ignore = "not yet implemented"] +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#""#]]); +} + +#[test] +#[ignore = "not yet implemented"] +fn to_int_with_lower_width_decl_implicitly_fails() { + let input = " + bit[5] reg; + int[4] a = reg; + "; + + check_classical_decls(input, &expect![[r#""#]]); +} 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..cd4178ebe8 --- /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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-42]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-42]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-46]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-43]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-46]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-44]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-13] + init_expr: Expr [18-22]: + ty: Bool(true) + kind: Lit: Bool(true) + [6] Symbol [14-15]: + name: x + type: Bool(false) + qsharp_type: bool + io_kind: Default + ClassicalDeclarationStmt [32-48]: + symbol_id: 7 + 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(6) + [7] 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..bd1de21f74 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_float.rs @@ -0,0 +1,601 @@ +// 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, true) + kind: Lit: Float(42.0) + + [Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Float(None, false) to type Bit(false) + ,-[test:3:9] + 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: 6 + ty_span: [9-18] + init_expr: Expr [23-26]: + ty: Float(Some(64), true) + kind: Lit: Float(42.0) + + [Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Float(Some(64), false) to type Bit(false) + ,-[test:3:9] + 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-43]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-42]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-46]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-43]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-14] + init_expr: Expr [20-23]: + ty: Float(None, true) + kind: Lit: Float(-42.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [33-44]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-47]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-46]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-44]: + symbol_id: 7 + ty_span: [32-37] + init_expr: Expr [42-43]: + ty: Float(None, false) + kind: SymbolId(6) + [7] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-48]: + symbol_id: 7 + ty_span: [32-41] + init_expr: Expr [46-47]: + ty: Float(Some(32), false) + kind: SymbolId(6) + [7] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-53]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, true) + kind: Lit: Float(42.0) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-57]: + symbol_id: 7 + 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(6) + [7] Symbol [51-52]: + name: y + type: Complex(Some(32), false) + qsharp_type: Complex + io_kind: Default + "#]], + ); +} + +#[test] +#[ignore = "not yet implemented"] +fn to_angle_implicitly() { + let input = " + float x = 42.; + angle y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-23]: + annotations: + kind: ClassicalDeclarationStmt [9-23]: + symbol_id: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, true) + kind: Lit: Float(42.0) + + [Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: Cast float + | to angle + ,-[test:3:9] + 2 | float x = 42.; + 3 | angle y = x; + : ^^^^^^^^^^^^ + 4 | + `---- + , Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Float(None, false) to type Angle(None, + | false) + ,-[test:3:9] + 2 | float x = 42.; + 3 | angle y = x; + : ^^^^^^^^^^^^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +#[ignore = "not yet implemented"] +fn to_explicit_angle_implicitly() { + let input = " + float x = 42.; + angle[4] y = x; + "; + + check_classical_decls( + input, + &expect![[r#" + Program: + version: + statements: + Stmt [9-23]: + annotations: + kind: ClassicalDeclarationStmt [9-23]: + symbol_id: 6 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Float(None, true) + kind: Lit: Float(42.0) + + [Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: Cast float + | to angle + ,-[test:3:9] + 2 | float x = 42.; + 3 | angle[4] y = x; + : ^^^^^^^^^^^^^^^ + 4 | + `---- + , Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Float(None, false) to type Angle(Some(4), + | false) + ,-[test:3:9] + 2 | float x = 42.; + 3 | angle[4] y = x; + : ^^^^^^^^^^^^^^^ + 4 | + `---- + ]"#]], + ); +} 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..1e8664c0bb --- /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: 6 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, true) + kind: Lit: Int(42) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-39]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, true) + kind: Lit: Int(42) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, true) + kind: Lit: Int(42) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-39]: + symbol_id: 7 + ty_span: [29-32] + init_expr: Expr [37-38]: + ty: Int(None, false) + kind: SymbolId(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, true) + kind: Lit: Int(42) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-43]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, true) + kind: Lit: Int(42) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-40]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, true) + kind: Lit: Int(42) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-44]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, true) + kind: Lit: Int(42) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-43]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, true) + kind: Lit: Int(42) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-41]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, true) + kind: Lit: Int(42) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-45]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, true) + kind: Lit: Int(42) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-50]: + symbol_id: 7 + 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(6) + [7] 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: 6 + ty_span: [9-12] + init_expr: Expr [17-19]: + ty: Int(None, true) + kind: Lit: Int(42) + [6] Symbol [13-14]: + name: x + type: Int(None, false) + qsharp_type: Int + io_kind: Default + ClassicalDeclarationStmt [29-54]: + symbol_id: 7 + 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(6) + [7] Symbol [48-49]: + name: y + type: Complex(Some(32), false) + qsharp_type: Complex + io_kind: Default + "#]], + ); +} diff --git a/compiler/qsc_qasm3/src/semantic/types.rs b/compiler/qsc_qasm3/src/semantic/types.rs index f111bef11d..62ccc95288 100644 --- a/compiler/qsc_qasm3/src/semantic/types.rs +++ b/compiler/qsc_qasm3/src/semantic/types.rs @@ -6,7 +6,7 @@ use std::cmp::max; use core::fmt; use std::fmt::{Display, Formatter}; -use crate::ast::UnaryOp::NotL; +use crate::parser::ast as syntax; use super::ast::LiteralKind; @@ -87,14 +87,14 @@ impl Type { pub fn is_array(&self) -> bool { matches!( self, - Type::BitArray(..) - | Type::QubitArray(..) - | Type::AngleArray(..) + Type::AngleArray(..) + | Type::BitArray(..) | Type::BoolArray(..) | Type::ComplexArray(..) | Type::DurationArray(..) | Type::FloatArray(..) | Type::IntArray(..) + | Type::QubitArray(..) | Type::UIntArray(..) ) } @@ -151,6 +151,22 @@ impl Type { ) } + #[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`. @@ -362,6 +378,13 @@ impl Type { _ => self.clone(), } } + + pub(crate) fn is_quantum(&self) -> bool { + matches!( + self, + Type::HardwareQubit | Type::Qubit | Type::QubitArray(_) + ) + } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] @@ -392,6 +415,22 @@ impl Display for ArrayDimensions { } } +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] @@ -617,6 +656,11 @@ pub fn can_cast_literal(lhs_ty: &Type, ty_lit: &Type) -> bool { } 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), ( @@ -656,9 +700,9 @@ pub(crate) fn can_cast_literal_with_value_knowledge(lhs_ty: &Type, kind: &Litera } // https://openqasm.com/language/classical.html -pub(crate) fn unary_op_can_be_applied_to_type(op: crate::ast::UnaryOp, ty: &Type) -> bool { +pub(crate) fn unary_op_can_be_applied_to_type(op: syntax::UnaryOp, ty: &Type) -> bool { match op { - crate::ast::UnaryOp::NotB => match ty { + 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 @@ -667,9 +711,148 @@ pub(crate) fn unary_op_can_be_applied_to_type(op: crate::ast::UnaryOp, ty: &Type } _ => false, }, - NotL => matches!(ty, Type::Bool(_)), - crate::ast::UnaryOp::Neg => { + syntax::UnaryOp::NotL => matches!(ty, Type::Bool(_)), + syntax::UnaryOp::Neg => { matches!(ty, Type::Int(_, _) | Type::Float(_, _) | Type::Angle(_, _)) } } } + +/// 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, false), right_type); + let promoted_lhs = promote_types(left_type, &Type::Float(None, false)); + + 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() != 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() != 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_int_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 + ) +} From a8852c7c53061c7018142ed1158fb0e12176900d Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 13 Mar 2025 11:26:07 -0700 Subject: [PATCH 59/98] Make control flow stmts use Stmt instead of List for their bodies. (#2226) If/else blocks, while, and for loops in QASM introduce new scopes only when their body enclosed by curly braces. To make lowering easier, this PR changes their bodies from `List` to `Stmt`. In the body enclosed in curly braces case, the Stmt will be of kind Block. --- compiler/qsc_qasm3/src/parser/ast.rs | 22 +- compiler/qsc_qasm3/src/parser/stmt.rs | 50 ++- .../src/parser/stmt/tests/for_loops.rs | 237 +++++++++---- .../src/parser/stmt/tests/if_stmt.rs | 327 ++++++++++++++---- .../parser/stmt/tests/invalid_stmts/branch.rs | 41 ++- .../parser/stmt/tests/invalid_stmts/loops.rs | 46 ++- .../src/parser/stmt/tests/while_loops.rs | 167 ++++++--- 7 files changed, 637 insertions(+), 253 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/ast.rs b/compiler/qsc_qasm3/src/parser/ast.rs index 03193a2d0c..922cc9a27a 100644 --- a/compiler/qsc_qasm3/src/parser/ast.rs +++ b/compiler/qsc_qasm3/src/parser/ast.rs @@ -317,7 +317,7 @@ pub enum StmtKind { Barrier(BarrierStmt), Box(BoxStmt), Break(BreakStmt), - Block(Box), + Block(Block), Cal(CalibrationStmt), CalibrationGrammar(CalibrationGrammarStmt), ClassicalDecl(ClassicalDeclarationStmt), @@ -419,16 +419,16 @@ impl Display for DefCalStmt { pub struct IfStmt { pub span: Span, pub condition: Expr, - pub if_block: List, - pub else_block: Option>, + 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_list_field(f, "if_block", &self.if_block)?; - write_opt_list_field(f, "else_block", self.else_block.as_ref()) + writeln_field(f, "if_body", &self.if_body)?; + write_opt_field(f, "else_body", self.else_body.as_ref()) } } @@ -1323,8 +1323,8 @@ pub struct DefStmt { pub span: Span, pub name: Box, pub params: List, - pub body: Box, - pub return_type: Option, + pub body: Block, + pub return_type: Option>, } impl Display for DefStmt { @@ -1354,14 +1354,14 @@ impl Display for ReturnStmt { pub struct WhileLoop { pub span: Span, pub while_condition: Expr, - pub block: List, + 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_list_field(f, "block", &self.block) + write_field(f, "body", &self.body) } } @@ -1371,7 +1371,7 @@ pub struct ForStmt { pub ty: ScalarType, pub identifier: Identifier, pub set_declaration: Box, - pub block: List, + pub body: Stmt, } impl Display for ForStmt { @@ -1380,7 +1380,7 @@ impl Display for ForStmt { writeln_field(f, "variable_type", &self.ty)?; writeln_field(f, "variable_name", &self.identifier)?; writeln_field(f, "iterable", &self.set_declaration)?; - write_list_field(f, "block", &self.block) + write_field(f, "body", &self.body) } } diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index c8f27caedd..2625a887cd 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -278,20 +278,22 @@ fn disambiguate_ident(s: &mut ParserContext, indexed_ident: IndexedIdent) -> Res #[allow(clippy::vec_box)] pub(super) fn parse_many(s: &mut ParserContext) -> Result>> { many(s, |s| { - recovering(s, default, &[TokenKind::Semicolon], parse_block_or_stmt) + recovering(s, default, &[TokenKind::Semicolon], |s| { + parse_block_or_stmt(s).map(Box::new) + }) }) } /// Grammar: `LBRACE statementOrScope* RBRACE`. -pub(super) fn parse_block(s: &mut ParserContext) -> Result> { +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(Box::new(Block { + Ok(Block { span: s.span(lo), stmts: stmts.into_boxed_slice(), - })) + }) } #[allow(clippy::unnecessary_box_returns)] @@ -454,7 +456,7 @@ fn parse_def(s: &mut ParserContext) -> Result { 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)?; + let return_type = opt(s, return_sig)?.map(Box::new); let body = parse_block(s)?; let kind = StmtKind::Def(DefStmt { span: s.span(lo), @@ -605,7 +607,7 @@ fn parse_gatedef(s: &mut ParserContext) -> Result { 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 = parse_block(s)?; + let body = Box::new(parse_block(s)?); Ok(StmtKind::QuantumGateDefinition(QuantumGateDefinition { span: s.span(lo), ident, @@ -1122,7 +1124,7 @@ fn case_stmt(s: &mut ParserContext) -> Result { s.push_error(Error::new(ErrorKind::MissingSwitchCaseLabels(s.span(lo)))); } - let block = parse_block(s).map(|block| *block)?; + let block = parse_block(s)?; Ok(SwitchCase { span: s.span(lo), @@ -1134,27 +1136,19 @@ fn case_stmt(s: &mut ParserContext) -> Result { /// Grammar: `DEFAULT scope`. fn default_case_stmt(s: &mut ParserContext) -> Result { token(s, TokenKind::Keyword(Keyword::Default))?; - parse_block(s).map(|block| *block) + parse_block(s) } /// Grammar: `statement | scope`. -fn parse_block_or_stmt(s: &mut ParserContext) -> Result> { +fn parse_block_or_stmt(s: &mut ParserContext) -> Result { if let Some(block) = opt(s, parse_block)? { - Ok(Box::new(Stmt { + Ok(Stmt { span: block.span, annotations: Default::default(), kind: Box::new(StmtKind::Block(block)), - })) - } else { - Ok(parse(s)?) - } -} - -fn into_stmt_list(stmt: Stmt) -> List { - if let StmtKind::Block(block) = *stmt.kind { - block.stmts + }) } else { - Box::new([Box::new(stmt)]) + Ok(*parse(s)?) } } @@ -1167,9 +1161,9 @@ pub fn parse_if_stmt(s: &mut ParserContext) -> Result { let condition = expr::expr(s)?; recovering_token(s, TokenKind::Close(Delim::Paren)); - let if_block = into_stmt_list(*parse_block_or_stmt(s)?); + let if_block = parse_block_or_stmt(s)?; let else_block = if opt(s, |s| token(s, TokenKind::Keyword(Keyword::Else)))?.is_some() { - Some(into_stmt_list(*parse_block_or_stmt(s)?)) + Some(parse_block_or_stmt(s)?) } else { None }; @@ -1177,8 +1171,8 @@ pub fn parse_if_stmt(s: &mut ParserContext) -> Result { Ok(IfStmt { span: s.span(lo), condition, - if_block, - else_block, + if_body: if_block, + else_body: else_block, }) } @@ -1236,14 +1230,14 @@ pub fn parse_for_loop(s: &mut ParserContext) -> Result { let identifier = Identifier::Ident(Box::new(prim::ident(s)?)); token(s, TokenKind::Keyword(Keyword::In))?; let set_declaration = Box::new(for_loop_iterable_expr(s)?); - let block = into_stmt_list(*parse_block_or_stmt(s)?); + let block = parse_block_or_stmt(s)?; Ok(ForStmt { span: s.span(lo), ty, identifier, set_declaration, - block, + body: block, }) } @@ -1255,12 +1249,12 @@ pub fn parse_while_loop(s: &mut ParserContext) -> Result { token(s, TokenKind::Open(Delim::Paren))?; let while_condition = expr::expr(s)?; recovering_token(s, TokenKind::Close(Delim::Paren)); - let block = into_stmt_list(*parse_block_or_stmt(s)?); + let block = parse_block_or_stmt(s)?; Ok(WhileLoop { span: s.span(lo), while_condition, - block, + body: block, }) } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs index 30239971b5..0f5822a6e0 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs @@ -24,14 +24,16 @@ fn simple_for_loop() { Expr [19-20]: Lit: Int(1) Expr [22-23]: Lit: Int(2) Expr [25-26]: Lit: Int(3) - block: - Stmt [38-44]: - annotations: - kind: AssignStmt [38-44]: - lhs: IndexedIdent [38-39]: - name: Ident [38-39] "a" - indices: - rhs: Expr [42-43]: Lit: Int(0)"#]], + 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" + indices: + rhs: Expr [42-43]: Lit: Int(0)"#]], ); } @@ -49,7 +51,9 @@ fn empty_for_loop() { variable_name: Ident [8-9] "x" iterable: DiscreteSet [13-15]: values: - block: "#]], + body: Stmt [16-18]: + annotations: + kind: Block [16-18]: "#]], ); } @@ -73,14 +77,13 @@ fn simple_for_loop_stmt_body() { Expr [19-20]: Lit: Int(1) Expr [22-23]: Lit: Int(2) Expr [25-26]: Lit: Int(3) - block: - Stmt [36-42]: - annotations: - kind: AssignStmt [36-42]: - lhs: IndexedIdent [36-37]: - name: Ident [36-37] "a" - indices: - rhs: Expr [40-41]: Lit: Int(0)"#]], + body: Stmt [36-42]: + annotations: + kind: AssignStmt [36-42]: + lhs: IndexedIdent [36-37]: + name: Ident [36-37] "a" + indices: + rhs: Expr [40-41]: Lit: Int(0)"#]], ); } @@ -103,14 +106,16 @@ fn for_loop_range() { start: Expr [19-20]: Lit: Int(0) step: Expr [21-22]: Lit: Int(2) end: Expr [23-24]: Lit: Int(7) - block: - Stmt [36-42]: - annotations: - kind: AssignStmt [36-42]: - lhs: IndexedIdent [36-37]: - name: Ident [36-37] "a" - indices: - rhs: Expr [40-41]: Lit: Int(0)"#]], + 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" + indices: + rhs: Expr [40-41]: Lit: Int(0)"#]], ); } @@ -133,14 +138,16 @@ fn for_loop_range_no_step() { start: Expr [19-20]: Lit: Int(0) step: end: Expr [21-22]: Lit: Int(7) - block: - Stmt [34-40]: - annotations: - kind: AssignStmt [34-40]: - lhs: IndexedIdent [34-35]: - name: Ident [34-35] "a" - indices: - rhs: Expr [38-39]: Lit: Int(0)"#]], + 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" + indices: + rhs: Expr [38-39]: Lit: Int(0)"#]], ); } @@ -160,14 +167,16 @@ fn for_loop_expr() { size: variable_name: Ident [13-14] "x" iterable: Expr [18-20]: Ident [18-20] "xs" - block: - Stmt [31-37]: - annotations: - kind: AssignStmt [31-37]: - lhs: IndexedIdent [31-32]: - name: Ident [31-32] "a" - indices: - rhs: Expr [35-36]: Lit: Int(0)"#]], + 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" + indices: + rhs: Expr [35-36]: Lit: Int(0)"#]], ); } @@ -192,17 +201,19 @@ fn for_loop_with_continue_stmt() { Expr [19-20]: Lit: Int(1) Expr [22-23]: Lit: Int(2) Expr [25-26]: Lit: Int(3) - block: - Stmt [38-44]: - annotations: - kind: AssignStmt [38-44]: - lhs: IndexedIdent [38-39]: - name: Ident [38-39] "a" - indices: - rhs: Expr [42-43]: Lit: Int(0) - Stmt [53-62]: - annotations: - kind: ContinueStmt [53-62]"#]], + 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" + indices: + rhs: Expr [42-43]: Lit: Int(0) + Stmt [53-62]: + annotations: + kind: ContinueStmt [53-62]"#]], ); } @@ -227,16 +238,116 @@ fn for_loop_with_break_stmt() { Expr [19-20]: Lit: Int(1) Expr [22-23]: Lit: Int(2) Expr [25-26]: Lit: Int(3) - block: - Stmt [38-44]: - annotations: - kind: AssignStmt [38-44]: - lhs: IndexedIdent [38-39]: - name: Ident [38-39] "a" - indices: - rhs: Expr [42-43]: Lit: Int(0) - Stmt [53-59]: - annotations: - kind: BreakStmt [53-59]"#]], + 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" + 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: + IndexedIdent [18-19]: + name: Ident [18-19] "q" + 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" + 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: + IndexedIdent [34-35]: + name: Ident [34-35] "q" + 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 index e2c242942a..f74dbc053b 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs @@ -23,22 +23,26 @@ fn simple_if_stmt() { op: Eq lhs: Expr [9-10]: Ident [9-10] "x" rhs: Expr [14-15]: Ident [14-15] "y" - if_block: - Stmt [27-33]: - annotations: - kind: AssignStmt [27-33]: - lhs: IndexedIdent [27-28]: - name: Ident [27-28] "a" - indices: - rhs: Expr [31-32]: Lit: Int(0) - else_block: - Stmt [55-61]: - annotations: - kind: AssignStmt [55-61]: - lhs: IndexedIdent [55-56]: - name: Ident [55-56] "a" - indices: - rhs: Expr [59-60]: Lit: Int(1)"#]], + 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" + 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" + indices: + rhs: Expr [59-60]: Lit: Int(1)"#]], ); } @@ -59,15 +63,17 @@ fn if_stmt_missing_else() { op: Eq lhs: Expr [9-10]: Ident [9-10] "x" rhs: Expr [14-15]: Ident [14-15] "y" - if_block: - Stmt [27-33]: - annotations: - kind: AssignStmt [27-33]: - lhs: IndexedIdent [27-28]: - name: Ident [27-28] "a" - indices: - rhs: Expr [31-32]: Lit: Int(0) - else_block: "#]], + 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" + indices: + rhs: Expr [31-32]: Lit: Int(0) + else_body: "#]], ); } @@ -98,53 +104,236 @@ fn nested_if_stmts() { op: Eq lhs: Expr [9-10]: Ident [9-10] "x" rhs: Expr [14-15]: Ident [14-15] "y" - if_block: - 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_block: - Stmt [55-61]: + 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: AssignStmt [55-61]: - lhs: IndexedIdent [55-56]: - name: Ident [55-56] "a" - indices: - rhs: Expr [59-60]: Lit: Int(0) - else_block: - Stmt [91-97]: + kind: Block [41-71]: + Stmt [55-61]: + annotations: + kind: AssignStmt [55-61]: + lhs: IndexedIdent [55-56]: + name: Ident [55-56] "a" + indices: + rhs: Expr [59-60]: Lit: Int(0) + else_body: Stmt [77-107]: annotations: - kind: AssignStmt [91-97]: - lhs: IndexedIdent [91-92]: - name: Ident [91-92] "a" - indices: - rhs: Expr [95-96]: Lit: Int(1) - else_block: - 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_block: - Stmt [157-163]: + kind: Block [77-107]: + Stmt [91-97]: + annotations: + kind: AssignStmt [91-97]: + lhs: IndexedIdent [91-92]: + name: Ident [91-92] "a" + 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: AssignStmt [157-163]: - lhs: IndexedIdent [157-158]: - name: Ident [157-158] "a" - indices: - rhs: Expr [161-162]: Lit: Int(2) - else_block: - Stmt [193-199]: + kind: Block [143-173]: + Stmt [157-163]: + annotations: + kind: AssignStmt [157-163]: + lhs: IndexedIdent [157-158]: + name: Ident [157-158] "a" + indices: + rhs: Expr [161-162]: Lit: Int(2) + else_body: Stmt [179-209]: annotations: - kind: AssignStmt [193-199]: - lhs: IndexedIdent [193-194]: - name: Ident [193-194] "a" - indices: - rhs: Expr [197-198]: Lit: Int(3)"#]], + kind: Block [179-209]: + Stmt [193-199]: + annotations: + kind: AssignStmt [193-199]: + lhs: IndexedIdent [193-194]: + name: Ident [193-194] "a" + 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: + IndexedIdent [9-10]: + name: Ident [9-10] "q" + 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" + 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: + IndexedIdent [16-17]: + name: Ident [16-17] "q" + 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: + IndexedIdent [16-17]: + name: Ident [16-17] "q" + 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: + IndexedIdent [40-41]: + name: Ident [40-41] "q" + 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: + IndexedIdent [9-10]: + name: Ident [9-10] "q" + indices: + else_body: Stmt [17-21]: + annotations: + kind: GateCall [17-21]: + modifiers: + name: Ident [17-18] "x" + args: + duration: + qubits: + IndexedIdent [19-20]: + name: Ident [19-20] "q" + indices: "#]], ); } 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 index 0229f5bbb4..a054a7861a 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/branch.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/branch.rs @@ -60,12 +60,14 @@ fn assignment_in_if_condition() { annotations: kind: IfStmt [0-17]: condition: Expr [4-5]: Ident [4-5] "x" - if_block: - Stmt [13-15]: - annotations: - kind: ExprStmt [13-15]: - expr: Expr [13-14]: Lit: Int(3) - else_block: + 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( @@ -94,12 +96,14 @@ fn binary_op_assignment_in_if_condition() { annotations: kind: IfStmt [0-18]: condition: Expr [4-5]: Ident [4-5] "x" - if_block: - Stmt [14-16]: - annotations: - kind: ExprStmt [14-16]: - expr: Expr [14-15]: Lit: Int(3) - else_block: + 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( @@ -126,15 +130,14 @@ fn empty_if_block() { parse, "if (true);", &expect![[r#" - Stmt [0-10]: - annotations: - kind: IfStmt [0-10]: - condition: Expr [4-8]: Lit: Bool(true) - if_block: - Stmt [9-10]: + Stmt [0-10]: + annotations: + kind: IfStmt [0-10]: + condition: Expr [4-8]: Lit: Bool(true) + if_body: Stmt [9-10]: annotations: kind: Empty - else_block: "#]], + else_body: "#]], ); } 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 index 5258d77869..9f6d3bd5b9 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/loops.rs @@ -222,30 +222,29 @@ fn while_multi_condition() { parse, "while (true) (true) { x $0; }", &expect![[r#" - Stmt [0-19]: - annotations: - kind: WhileLoop [0-19]: - condition: Expr [7-11]: Lit: Bool(true) - block: - Stmt [13-19]: + 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, + [ + Error( + Token( + Semicolon, + Open( + Brace, + ), + Span { + lo: 20, + hi: 21, + }, ), - Span { - lo: 20, - hi: 21, - }, ), - ), - ]"#]], + ]"#]], ); } @@ -277,12 +276,11 @@ fn while_missing_body() { parse, "while (true);", &expect![[r#" - Stmt [0-13]: - annotations: - kind: WhileLoop [0-13]: - condition: Expr [7-11]: Lit: Bool(true) - block: - Stmt [12-13]: + Stmt [0-13]: + annotations: + kind: WhileLoop [0-13]: + condition: Expr [7-11]: Lit: Bool(true) + body: Stmt [12-13]: annotations: kind: Empty"#]], ); diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs index 4c5a1e0af1..2f54709b85 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs @@ -20,14 +20,16 @@ fn simple_while() { op: Neq lhs: Expr [12-13]: Ident [12-13] "x" rhs: Expr [17-18]: Lit: Int(2) - block: - Stmt [30-36]: - annotations: - kind: AssignStmt [30-36]: - lhs: IndexedIdent [30-31]: - name: Ident [30-31] "a" - indices: - rhs: Expr [34-35]: Lit: Int(0)"#]], + 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" + indices: + rhs: Expr [34-35]: Lit: Int(0)"#]], ); } @@ -41,7 +43,9 @@ fn empty_while() { annotations: kind: WhileLoop [0-15]: condition: Expr [7-11]: Lit: Bool(true) - block: "#]], + body: Stmt [13-15]: + annotations: + kind: Block [13-15]: "#]], ); } @@ -60,14 +64,13 @@ fn while_stmt_body() { op: Neq lhs: Expr [12-13]: Ident [12-13] "x" rhs: Expr [17-18]: Lit: Int(2) - block: - Stmt [28-34]: - annotations: - kind: AssignStmt [28-34]: - lhs: IndexedIdent [28-29]: - name: Ident [28-29] "a" - indices: - rhs: Expr [32-33]: Lit: Int(0)"#]], + body: Stmt [28-34]: + annotations: + kind: AssignStmt [28-34]: + lhs: IndexedIdent [28-29]: + name: Ident [28-29] "a" + indices: + rhs: Expr [32-33]: Lit: Int(0)"#]], ); } @@ -88,17 +91,19 @@ fn while_loop_with_continue_stmt() { op: Neq lhs: Expr [12-13]: Ident [12-13] "x" rhs: Expr [17-18]: Lit: Int(2) - block: - Stmt [30-36]: - annotations: - kind: AssignStmt [30-36]: - lhs: IndexedIdent [30-31]: - name: Ident [30-31] "a" - indices: - rhs: Expr [34-35]: Lit: Int(0) - Stmt [45-54]: - annotations: - kind: ContinueStmt [45-54]"#]], + 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" + indices: + rhs: Expr [34-35]: Lit: Int(0) + Stmt [45-54]: + annotations: + kind: ContinueStmt [45-54]"#]], ); } @@ -119,16 +124,100 @@ fn while_loop_with_break_stmt() { op: Neq lhs: Expr [12-13]: Ident [12-13] "x" rhs: Expr [17-18]: Lit: Int(2) - block: - Stmt [30-36]: - annotations: - kind: AssignStmt [30-36]: - lhs: IndexedIdent [30-31]: - name: Ident [30-31] "a" - indices: - rhs: Expr [34-35]: Lit: Int(0) - Stmt [45-51]: - annotations: - kind: BreakStmt [45-51]"#]], + 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" + 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: + IndexedIdent [12-13]: + name: Ident [12-13] "q" + 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" + 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: + IndexedIdent [22-23]: + name: Ident [22-23] "q" + indices: "#]], ); } From 3848f81cd0522da5ad10e244041147af1476c516 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Thu, 13 Mar 2025 12:16:27 -0700 Subject: [PATCH 60/98] Update cross file spans, separate parse/semantic result blurring (#2227) - Adding span offsetter for parsed files - Clean up error spans - Move box validation - Add a mutable visitor for AST nodes. --- compiler/qsc_qasm3/src/parser.rs | 61 +- compiler/qsc_qasm3/src/parser/ast.rs | 8 +- compiler/qsc_qasm3/src/parser/error.rs | 8 +- compiler/qsc_qasm3/src/parser/mut_visit.rs | 839 ++++++++++++++++++ compiler/qsc_qasm3/src/parser/prgm.rs | 2 +- compiler/qsc_qasm3/src/parser/stmt.rs | 121 ++- .../src/parser/stmt/tests/box_stmt.rs | 54 -- .../src/parser/stmt/tests/classical_decl.rs | 160 ++++ .../parser/stmt/tests/invalid_stmts/branch.rs | 17 +- .../parser/stmt/tests/invalid_stmts/loops.rs | 15 +- .../parser/stmt/tests/invalid_stmts/tokens.rs | 67 +- compiler/qsc_qasm3/src/semantic.rs | 35 +- compiler/qsc_qasm3/src/semantic/error.rs | 4 + compiler/qsc_qasm3/src/semantic/lowerer.rs | 75 +- compiler/qsc_qasm3/src/semantic/tests.rs | 146 ++- .../src/semantic/tests/decls/angle.rs | 27 - .../src/semantic/tests/decls/complex.rs | 54 -- .../src/semantic/tests/decls/float.rs | 27 - .../qsc_qasm3/src/semantic/tests/decls/int.rs | 54 -- .../src/semantic/tests/decls/uint.rs | 54 -- .../src/semantic/tests/statements.rs | 4 + .../src/semantic/tests/statements/box_stmt.rs | 50 ++ 22 files changed, 1447 insertions(+), 435 deletions(-) create mode 100644 compiler/qsc_qasm3/src/parser/mut_visit.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/statements.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/statements/box_stmt.rs diff --git a/compiler/qsc_qasm3/src/parser.rs b/compiler/qsc_qasm3/src/parser.rs index cdd177cc0f..6ec86a8472 100644 --- a/compiler/qsc_qasm3/src/parser.rs +++ b/compiler/qsc_qasm3/src/parser.rs @@ -4,6 +4,8 @@ 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; @@ -17,11 +19,21 @@ 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, @@ -31,6 +43,8 @@ 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 } } @@ -63,19 +77,35 @@ impl QasmParseResult { } fn map_error(&self, error: Error) -> WithSource { - let path = self.source.path().display().to_string(); - let source = self.source_map.find_by_name(&path); - let offset = source.map_or(0, |source| source.offset); - - let offset_error = error.with_offset(offset); - WithSource::from_map( &self.source_map, - crate::Error(crate::ErrorKind::Parser(offset_error)), + 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. @@ -95,15 +125,7 @@ where /// and parse results. fn create_source_map(source: &QasmSource) -> SourceMap { let mut files: Vec<(Arc, Arc)> = Vec::new(); - files.push(( - Arc::from(source.path().to_string_lossy().to_string()), - Arc::from(source.source()), - )); - // Collect all source files from the includes, this - // begins the recursive process of collecting all source files. - for include in source.includes() { - collect_source_files(include, &mut files); - } + collect_source_files(source, &mut files); SourceMap::new(files, None) } @@ -113,6 +135,8 @@ fn collect_source_files(source: &QasmSource, files: &mut Vec<(Arc, Arc 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); } @@ -173,6 +197,11 @@ impl QasmSource { 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 diff --git a/compiler/qsc_qasm3/src/parser/ast.rs b/compiler/qsc_qasm3/src/parser/ast.rs index 922cc9a27a..a4e829e319 100644 --- a/compiler/qsc_qasm3/src/parser/ast.rs +++ b/compiler/qsc_qasm3/src/parser/ast.rs @@ -686,9 +686,9 @@ impl Display for ClassicalArgument { #[derive(Clone, Debug)] pub enum ExternParameter { - Scalar(ScalarType, Span), - Quantum(Option, Span), ArrayReference(ArrayReferenceType, Span), + Quantum(Option, Span), + Scalar(ScalarType, Span), } impl Display for ExternParameter { @@ -1217,9 +1217,9 @@ impl Display for CalibrationStmt { #[derive(Clone, Debug)] pub enum TypedParameter { - Scalar(ScalarTypedParameter), - Quantum(QuantumTypedParameter), ArrayReference(ArrayTypedParameter), + Quantum(QuantumTypedParameter), + Scalar(ScalarTypedParameter), } impl WithSpan for TypedParameter { diff --git a/compiler/qsc_qasm3/src/parser/error.rs b/compiler/qsc_qasm3/src/parser/error.rs index fbba90d248..da16aa0353 100644 --- a/compiler/qsc_qasm3/src/parser/error.rs +++ b/compiler/qsc_qasm3/src/parser/error.rs @@ -95,15 +95,15 @@ pub enum ErrorKind { #[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("expected statement after annotation")] #[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("invalid classical statement in box")] - #[diagnostic(code("Qasm3.Parse.ClassicalStmtInBox"))] - ClassicalStmtInBox(#[label] Span), #[error("expected {0}, found {1}")] #[diagnostic(code("Qasm3.Parse.Convert"))] Convert(&'static str, &'static str, #[label] Span), @@ -146,8 +146,8 @@ impl ErrorKind { 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::ClassicalStmtInBox(span) => Self::ClassicalStmtInBox(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), 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..b9ffcd2113 --- /dev/null +++ b/compiler/qsc_qasm3/src/parser/mut_visit.rs @@ -0,0 +1,839 @@ +// 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, HardwareQubit, IODeclaration, Ident, Identifier, + IfStmt, IncludeStmt, IndexElement, IndexExpr, IndexSet, IndexSetItem, IndexedIdent, Lit, + LiteralKind, MeasureExpr, MeasureStmt, Pragma, Program, QuantumGateDefinition, + QuantumGateModifier, QuantumTypedParameter, QubitDeclaration, RangeDefinition, ResetStmt, + ReturnStmt, ScalarType, ScalarTypedParameter, Stmt, StmtKind, SwitchCase, SwitchStmt, TypeDef, + TypedParameter, UnaryOp, UnaryOpExpr, ValueExpression, 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 MeasureStmt) { + 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 ValueExpression) { + 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::Empty | 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_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_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_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_identifier(&mut stmt.identifier); + 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); + 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 MeasureStmt) { + 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_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_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 ValueExpression) { + match &mut *expr { + ValueExpression::Expr(expr) => vis.visit_expr(expr), + ValueExpression::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_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) { + match operand { + GateOperand::IndexedIdent(ident) => vis.visit_indexed_ident(ident), + GateOperand::HardwareQubit(hardware_qubit) => vis.visit_hardware_qubit(hardware_qubit), + GateOperand::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::Quantum(expr, span) => { + vis.visit_span(span); + expr.iter_mut().for_each(|expr| vis.visit_expr(expr)); + } + 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 index 52f836ba20..d87859a59e 100644 --- a/compiler/qsc_qasm3/src/parser/prgm.rs +++ b/compiler/qsc_qasm3/src/parser/prgm.rs @@ -74,6 +74,6 @@ fn parse_top_level_node(s: &mut ParserContext) -> Result { kind: Box::new(StmtKind::Block(block)), }) } else { - Ok(*stmt::parse(s)?) + Ok(stmt::parse(s)?) } } diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 2625a887cd..08745117ef 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -73,85 +73,88 @@ use super::{prim::token, ParserContext}; /// | whileStatement /// ) /// ``` -pub(super) fn parse(s: &mut ParserContext) -> Result> { +pub(super) fn parse(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; if let Some(pragma) = opt(s, parse_pragma)? { - return Ok(Box::new(Stmt { + 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() { - Box::new(StmtKind::Empty) - } else { let err_item = default(s.span(lo)); - s.push_error(Error::new(ErrorKind::FloatingAnnotation(err_item.span))); + 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)? { - Box::new(decl) + decl } else if let Some(decl) = opt(s, parse_def)? { - Box::new(decl) + decl } else if let Some(include) = opt(s, parse_include)? { - Box::new(include) + include } else if let Some(ty) = opt(s, scalar_or_array_type)? { - Box::new(disambiguate_type(s, ty)?) + disambiguate_type(s, ty)? } else if let Some(decl) = opt(s, parse_constant_classical_decl)? { - Box::new(decl) + decl } else if let Some(decl) = opt(s, parse_quantum_decl)? { - Box::new(decl) + decl } else if let Some(decl) = opt(s, parse_io_decl)? { - Box::new(decl) + decl } else if let Some(decl) = opt(s, qreg_decl)? { - Box::new(decl) + decl } else if let Some(decl) = opt(s, creg_decl)? { - Box::new(decl) + decl } else if let Some(decl) = opt(s, parse_extern)? { - Box::new(decl) + decl } else if let Some(switch) = opt(s, parse_switch_stmt)? { - Box::new(StmtKind::Switch(switch)) + StmtKind::Switch(switch) } else if let Some(stmt) = opt(s, parse_if_stmt)? { - Box::new(StmtKind::If(stmt)) + StmtKind::If(stmt) } else if let Some(stmt) = opt(s, parse_for_loop)? { - Box::new(StmtKind::For(stmt)) + StmtKind::For(stmt) } else if let Some(stmt) = opt(s, parse_while_loop)? { - Box::new(StmtKind::WhileLoop(stmt)) + StmtKind::WhileLoop(stmt) } else if let Some(stmt) = opt(s, parse_return)? { - Box::new(stmt) + stmt } else if let Some(stmt) = opt(s, parse_continue_stmt)? { - Box::new(StmtKind::Continue(stmt)) + StmtKind::Continue(stmt) } else if let Some(stmt) = opt(s, parse_break_stmt)? { - Box::new(StmtKind::Break(stmt)) + StmtKind::Break(stmt) } else if let Some(stmt) = opt(s, parse_end_stmt)? { - Box::new(StmtKind::End(stmt)) + StmtKind::End(stmt) } else if let Some(indexed_ident) = opt(s, indexed_identifier)? { - Box::new(disambiguate_ident(s, indexed_ident)?) + disambiguate_ident(s, indexed_ident)? } else if let Some(stmt_kind) = opt(s, parse_gate_call_stmt)? { - Box::new(stmt_kind) + stmt_kind } else if let Some(stmt) = opt(s, |s| parse_expression_stmt(s, None))? { - Box::new(StmtKind::ExprStmt(stmt)) + StmtKind::ExprStmt(stmt) } else if let Some(alias) = opt(s, parse_alias_stmt)? { - Box::new(StmtKind::Alias(alias)) + StmtKind::Alias(alias) } else if let Some(stmt) = opt(s, parse_box)? { - Box::new(StmtKind::Box(stmt)) + StmtKind::Box(stmt) } else if let Some(stmt) = opt(s, parse_calibration_grammar_stmt)? { - Box::new(StmtKind::CalibrationGrammar(stmt)) + StmtKind::CalibrationGrammar(stmt) } else if let Some(stmt) = opt(s, parse_defcal_stmt)? { - Box::new(StmtKind::DefCal(stmt)) + StmtKind::DefCal(stmt) } else if let Some(stmt) = opt(s, parse_cal)? { - Box::new(StmtKind::Cal(stmt)) + StmtKind::Cal(stmt) } else if let Some(stmt) = opt(s, parse_barrier)? { - Box::new(StmtKind::Barrier(stmt)) + StmtKind::Barrier(stmt) } else if let Some(stmt) = opt(s, parse_delay)? { - Box::new(StmtKind::Delay(stmt)) + StmtKind::Delay(stmt) } else if let Some(stmt) = opt(s, parse_reset)? { - Box::new(StmtKind::Reset(stmt)) + StmtKind::Reset(stmt) } else if let Some(stmt) = opt(s, parse_measure_stmt)? { - Box::new(StmtKind::Measure(stmt)) + StmtKind::Measure(stmt) } else { return Err(Error::new(ErrorKind::Rule( "statement", @@ -160,11 +163,11 @@ pub(super) fn parse(s: &mut ParserContext) -> Result> { ))); }; - Ok(Box::new(Stmt { + Ok(Stmt { span: s.span(lo), annotations: attrs.into_boxed_slice(), - kind, - })) + kind: Box::new(kind), + }) } /// This helper function allows us to disambiguate between @@ -276,10 +279,10 @@ fn disambiguate_ident(s: &mut ParserContext, indexed_ident: IndexedIdent) -> Res } #[allow(clippy::vec_box)] -pub(super) fn parse_many(s: &mut ParserContext) -> Result>> { +pub(super) fn parse_many(s: &mut ParserContext) -> Result> { many(s, |s| { recovering(s, default, &[TokenKind::Semicolon], |s| { - parse_block_or_stmt(s).map(Box::new) + parse_block_or_stmt(s) }) }) } @@ -292,17 +295,17 @@ pub(super) fn parse_block(s: &mut ParserContext) -> Result { recovering_token(s, TokenKind::Close(Delim::Brace)); Ok(Block { span: s.span(lo), - stmts: stmts.into_boxed_slice(), + stmts: list_from_iter(stmts), }) } #[allow(clippy::unnecessary_box_returns)] -fn default(span: Span) -> Box { - Box::new(Stmt { +fn default(span: Span) -> Stmt { + Stmt { span, annotations: Vec::new().into_boxed_slice(), kind: Box::new(StmtKind::Err), - }) + } } /// Grammar: `AnnotationKeyword RemainingLineContent?`. @@ -1148,7 +1151,7 @@ fn parse_block_or_stmt(s: &mut ParserContext) -> Result { kind: Box::new(StmtKind::Block(block)), }) } else { - Ok(*parse(s)?) + parse(s) } } @@ -1347,37 +1350,13 @@ fn parse_many_boxable_stmt(s: &mut ParserContext) -> Result> { annotations: Box::new([]), }, &[TokenKind::Semicolon], - parse_boxable_stmt, + parse, ) }); Ok(list_from_iter(stmts?)) } -/// These "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 parse_boxable_stmt(s: &mut ParserContext) -> Result { - let stmt = *parse(s)?; - match &*stmt.kind { - StmtKind::Barrier(_) - | StmtKind::Delay(_) - | StmtKind::Reset(_) - | StmtKind::GateCall(_) - | StmtKind::GPhase(_) - | StmtKind::Box(_) => Ok(stmt), - _ => { - s.push_error(Error::new(ErrorKind::ClassicalStmtInBox(stmt.span))); - Ok(Stmt { - span: stmt.span, - annotations: stmt.annotations, - kind: Box::new(StmtKind::Err), - }) - } - } -} - /// 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;` diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs index 6e500319fa..6972c345af 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs @@ -86,57 +86,3 @@ fn box_stmt_with_designator() { indices: "#]], ); } - -#[test] -fn box_stmt_with_invalid_instruction() { - check( - parse, - "box { - H q0; - 2 + 4; - X q1; - }", - &expect![[r#" - Stmt [0-54]: - annotations: - kind: BoxStmt [0-54]: - duration: - body: - Stmt [14-19]: - annotations: - kind: GateCall [14-19]: - modifiers: - name: Ident [14-15] "H" - args: - duration: - qubits: - IndexedIdent [16-18]: - name: Ident [16-18] "q0" - indices: - Stmt [28-34]: - annotations: - kind: Err - Stmt [43-48]: - annotations: - kind: GateCall [43-48]: - modifiers: - name: Ident [43-44] "X" - args: - duration: - qubits: - IndexedIdent [45-47]: - name: Ident [45-47] "q1" - indices: - - [ - Error( - ClassicalStmtInBox( - Span { - lo: 28, - hi: 34, - }, - ), - ), - ]"#]], - ); -} diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs index e9292638af..bdbe7580d9 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs @@ -288,6 +288,46 @@ fn const_complex_sized_decl_complex_lit() { ); } +#[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( @@ -320,6 +360,46 @@ fn int_decl_int_lit() { ); } +#[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( @@ -416,6 +496,46 @@ fn uint_decl_uint_lit() { ); } +#[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( @@ -528,6 +648,46 @@ fn const_float_decl_float_lit() { ); } +#[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( 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 index a054a7861a..59d12cb6dc 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/branch.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/branch.rs @@ -125,7 +125,7 @@ fn binary_op_assignment_in_if_condition() { } #[test] -fn empty_if_block() { +fn empty_if_block_fails() { check( parse, "if (true);", @@ -136,8 +136,19 @@ fn empty_if_block() { condition: Expr [4-8]: Lit: Bool(true) if_body: Stmt [9-10]: annotations: - kind: Empty - else_body: "#]], + kind: Err + else_body: + + [ + Error( + EmptyStatement( + Span { + lo: 9, + hi: 10, + }, + ), + ), + ]"#]], ); } 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 index 9f6d3bd5b9..5e775bc19f 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/loops.rs @@ -271,7 +271,7 @@ fn while_with_for_syntax() { } #[test] -fn while_missing_body() { +fn while_missing_body_fails() { check( parse, "while (true);", @@ -282,6 +282,17 @@ fn while_missing_body() { condition: Expr [7-11]: Lit: Bool(true) body: Stmt [12-13]: annotations: - kind: Empty"#]], + kind: Err + + [ + Error( + EmptyStatement( + Span { + lo: 12, + hi: 13, + }, + ), + ), + ]"#]], ); } 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 index 79c685326c..3be5bbc5ba 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/tokens.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/tokens.rs @@ -5,32 +5,41 @@ 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: Empty + Stmt [1-2]: + annotations: + kind: Err - [ - Error( - Lex( - Incomplete( - Ident, - Identifier, - Single( - Semi, + [ + Error( + Lex( + Incomplete( + Ident, + Identifier, + Single( + Semi, + ), + Span { + lo: 1, + hi: 2, + }, ), + ), + ), + Error( + EmptyStatement( Span { lo: 1, hi: 2, }, ), ), - ), - ]"#]], + ]"#]], ); check( @@ -121,23 +130,31 @@ fn bad_integer_literals() { parse, "3_4_;", &expect![[r#" - Stmt [4-5]: - annotations: - kind: Empty + Stmt [4-5]: + annotations: + kind: Err - [ - Error( - Lex( - Unknown( - '3', + [ + Error( + Lex( + Unknown( + '3', + Span { + lo: 0, + hi: 4, + }, + ), + ), + ), + Error( + EmptyStatement( Span { - lo: 0, - hi: 4, + lo: 4, + hi: 5, }, ), ), - ), - ]"#]], + ]"#]], ); check( diff --git a/compiler/qsc_qasm3/src/semantic.rs b/compiler/qsc_qasm3/src/semantic.rs index 1f676049b2..1682b3a953 100644 --- a/compiler/qsc_qasm3/src/semantic.rs +++ b/compiler/qsc_qasm3/src/semantic.rs @@ -4,13 +4,14 @@ use crate::io::SourceResolver; use crate::parser::QasmSource; +use lowerer::Lowerer; use qsc_frontend::compile::SourceMap; use qsc_frontend::error::WithSource; use symbols::SymbolTable; use std::path::Path; -mod ast; +pub(crate) mod ast; pub mod error; mod lowerer; pub use error::Error; @@ -25,7 +26,7 @@ pub struct QasmSemanticParseResult { pub source: QasmSource, pub source_map: SourceMap, pub symbols: self::symbols::SymbolTable, - pub program: self::ast::Program, + pub program: Option, pub errors: Vec>, } @@ -83,15 +84,9 @@ impl QasmSemanticParseResult { } fn map_parse_error(&self, error: crate::parser::Error) -> WithSource { - let path = self.source.path().display().to_string(); - let source = self.source_map.find_by_name(&path); - let offset = source.map_or(0, |source| source.offset); - - let offset_error = error.with_offset(offset); - WithSource::from_map( &self.source_map, - crate::Error(crate::ErrorKind::Parser(offset_error)), + crate::Error(crate::ErrorKind::Parser(error)), ) } } @@ -111,15 +106,19 @@ where R: SourceResolver, { let res = crate::parser::parse_source(source, path, resolver)?; - let analyzer = crate::semantic::lowerer::Lowerer { - source: res.source, - source_map: res.source_map, - errors: vec![], - file_stack: vec![], - symbols: SymbolTable::default(), - version: None, - stmts: vec![], - }; + + // If there are syntax errors, return early + if res.source.has_errors() { + return Ok(QasmSemanticParseResult { + source: res.source, + source_map: res.source_map, + symbols: SymbolTable::default(), + program: None, + errors: vec![], + }); + } + + let analyzer = Lowerer::new(res.source, res.source_map); let sem_res = analyzer.lower(); Ok(QasmSemanticParseResult { source: sem_res.source, diff --git a/compiler/qsc_qasm3/src/semantic/error.rs b/compiler/qsc_qasm3/src/semantic/error.rs index 48b7334ff6..68bb7d7721 100644 --- a/compiler/qsc_qasm3/src/semantic/error.rs +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -60,6 +60,9 @@ pub enum SemanticErrorKind { #[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), @@ -218,6 +221,7 @@ impl SemanticErrorKind { 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) } diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index 4656c9a6b3..679700fef1 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -2,7 +2,6 @@ // Licensed under the MIT License. use std::ops::ShlAssign; -use std::path::PathBuf; use super::types::binop_requires_int_conversion_for_type; use super::types::binop_requires_symmetric_int_conversion; @@ -44,12 +43,26 @@ pub(super) struct Lowerer { /// 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 file_stack: Vec, 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(); @@ -66,7 +79,7 @@ impl Lowerer { source: self.source, source_map: self.source_map, symbols: self.symbols, - program, + program: Some(program), errors: self.errors, } } @@ -97,11 +110,6 @@ impl Lowerer { /// Root recursive function for lowering the source. fn lower_source(&mut self, source: &QasmSource) { - // we push the file path to the stack so we can track the current file - // for reporting errors. This saves us from having to pass around - // the current QasmSource value. - self.file_stack.push(source.path()); - // 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 @@ -139,10 +147,6 @@ impl Lowerer { } } } - - // Finally we pop the file path from the stack so that we - // can return to the previous file for error handling. - self.file_stack.pop(); } #[allow(clippy::too_many_lines)] @@ -316,13 +320,7 @@ impl Lowerer { /// Creates an error from the given kind with the current source mapping. fn create_err(&self, kind: crate::ErrorKind) -> WithSource { let error = crate::Error(kind); - let path = self.file_stack.last().map_or("", |p| { - p.to_str().expect("expected source mapping to exist.") - }); - let source = self.source_map.find_by_name(path); - let offset = source.map_or(0, |x| x.offset); - let offset_error = error.with_offset(offset); - WithSource::from_map(&self.source_map, offset_error) + WithSource::from_map(&self.source_map, error) } fn lower_alias(&mut self, alias: &syntax::AliasDeclStmt) -> Option { @@ -790,7 +788,46 @@ impl Lowerer { None } + /// 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) -> Option { + 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); + return None; + } + + if has_invalid_stmt_kinds || stmts.len() != stmt.body.len() { + return None; + } + + // 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); None } diff --git a/compiler/qsc_qasm3/src/semantic/tests.rs b/compiler/qsc_qasm3/src/semantic/tests.rs index 490a573561..cfa028a5b4 100644 --- a/compiler/qsc_qasm3/src/semantic/tests.rs +++ b/compiler/qsc_qasm3/src/semantic/tests.rs @@ -5,6 +5,7 @@ pub mod assignment; pub mod decls; pub mod expression; +pub mod statements; use std::path::Path; use std::sync::Arc; @@ -16,6 +17,7 @@ use super::parse_source; use super::QasmSemanticParseResult; +use expect_test::expect; use miette::Report; use expect_test::Expect; @@ -141,14 +143,74 @@ fn check_map( let res = parse_source(input, "test", &resolver) .map_err(|e| vec![e]) .expect("failed to parse"); + + let errors = res.all_errors(); + + assert!( + !res.has_syntax_errors(), + "syntax errors: {:?}", + res.parse_errors() + ); + + let program = res.program.expect("no program"); + + if errors.is_empty() { + expect.assert_eq(&selector(&program, &res.symbols)); + } else { + expect.assert_eq(&format!( + "{}\n\n{:?}", + 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::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) + .map_err(|e| vec![e]) + .expect("failed to parse"); + let errors = res.all_errors(); + assert!( + !res.has_syntax_errors(), + "syntax errors: {:?}", + res.parse_errors() + ); + let program = res.program.expect("no program"); + if errors.is_empty() { - expect.assert_eq(&selector(&res.program, &res.symbols)); + expect.assert_eq(&selector(&program, &res.symbols)); } else { expect.assert_eq(&format!( "{}\n\n{:?}", - res.program, + program, errors .iter() .map(|e| Report::new(e.clone())) @@ -156,3 +218,83 @@ fn check_map( )); } } + +#[test] +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 z = 7.0; + float k = z + false; // invalid cast"#; + let source2 = "bit x = 1; + bool x = y && x; // undefined y, redefine x"; + 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: 24 + ty_span: [196-199] + init_expr: Expr [204-205]: + ty: Bit(true) + kind: Lit: Int(1) + Stmt [140-154]: + annotations: + kind: ClassicalDeclarationStmt [140-154]: + symbol_id: 26 + ty_span: [140-145] + init_expr: Expr [150-153]: + ty: Angle(None, true) + kind: Lit: Float(7.0) + + [Qsc.Qasm3.Compile.UndefinedSymbol + + x Undefined symbol: y. + ,-[source2.qasm:2:14] + 1 | bit x = 1; + 2 | bool x = y && x; // undefined y, redefine x + : ^ + `---- + , Qsc.Qasm3.Compile.RedefinedSymbol + + x Redefined symbol: x. + ,-[source2.qasm:2:10] + 1 | bit x = 1; + 2 | bool x = y && x; // undefined y, redefine x + : ^ + `---- + , Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Angle(None, false) to type Float(None, + | false) + ,-[source1.qasm:3:15] + 2 | angle z = 7.0; + 3 | float k = z + 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 | + `---- + ]"#]], + ); +} diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs index 22759f8ce0..04bc074d19 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs @@ -24,33 +24,6 @@ fn implicit_bitness_default() { ); } -#[test] -fn const_default_fails() { - check_classical_decl( - "const angle x;", - &expect![[r#" - Program: - version: - statements: - - [Qasm3.Parse.Token - - x expected `=`, found `;` - ,-[test:1:14] - 1 | const angle x; - : ^ - `---- - , Qsc.Qasm3.Compile.UnexpectedParserError - - x Unexpected parser error: Unexpected error. - ,-[test:1:1] - 1 | const angle x; - : ^^^^^^^^^^^^^^ - `---- - ]"#]], - ); -} - #[test] fn lit() { check_classical_decl( diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs index 62c3a38ad8..ab56fbd88b 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs @@ -24,33 +24,6 @@ fn implicit_bitness_default() { ); } -#[test] -fn const_implicit_bitness_default() { - check_classical_decl( - "const complex[float] x;", - &expect![[r#" - Program: - version: - statements: - - [Qasm3.Parse.Token - - x expected `=`, found `;` - ,-[test:1:23] - 1 | const complex[float] x; - : ^ - `---- - , Qsc.Qasm3.Compile.UnexpectedParserError - - x Unexpected parser error: Unexpected error. - ,-[test:1:1] - 1 | const complex[float] x; - : ^^^^^^^^^^^^^^^^^^^^^^^ - `---- - ]"#]], - ); -} - #[test] fn explicit_bitness_default() { check_classical_decl( @@ -70,33 +43,6 @@ fn explicit_bitness_default() { ); } -#[test] -fn const_explicit_bitness_default() { - check_classical_decl( - "const complex[float[42]] x;", - &expect![[r#" - Program: - version: - statements: - - [Qasm3.Parse.Token - - x expected `=`, found `;` - ,-[test:1:27] - 1 | const complex[float[42]] x; - : ^ - `---- - , Qsc.Qasm3.Compile.UnexpectedParserError - - x Unexpected parser error: Unexpected error. - ,-[test:1:1] - 1 | const complex[float[42]] x; - : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - `---- - ]"#]], - ); -} - #[test] fn const_implicit_bitness_double_img_only() { check_classical_decl( diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs index 7975ff3a9e..c04f74943f 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs @@ -24,33 +24,6 @@ fn implicit_bitness_default() { ); } -#[test] -fn const_default() { - check_classical_decl( - "const float x;", - &expect![[r#" - Program: - version: - statements: - - [Qasm3.Parse.Token - - x expected `=`, found `;` - ,-[test:1:14] - 1 | const float x; - : ^ - `---- - , Qsc.Qasm3.Compile.UnexpectedParserError - - x Unexpected parser error: Unexpected error. - ,-[test:1:1] - 1 | const float x; - : ^^^^^^^^^^^^^^ - `---- - ]"#]], - ); -} - #[test] fn lit() { check_classical_decl( diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs index 7a0f38a21c..be6373acae 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs @@ -62,33 +62,6 @@ fn implicit_bitness_int_default() { ); } -#[test] -fn const_implicit_bitness_int_default() { - check_classical_decl( - "const int x;", - &expect![[r#" - Program: - version: - statements: - - [Qasm3.Parse.Token - - x expected `=`, found `;` - ,-[test:1:12] - 1 | const int x; - : ^ - `---- - , Qsc.Qasm3.Compile.UnexpectedParserError - - x Unexpected parser error: Unexpected error. - ,-[test:1:1] - 1 | const int x; - : ^^^^^^^^^^^^ - `---- - ]"#]], - ); -} - #[test] fn const_implicit_bitness_int_lit() { check_classical_decl( @@ -336,33 +309,6 @@ fn explicit_bitness_int_default() { ); } -#[test] -fn const_explicit_bitness_int_default() { - check_classical_decl( - "const int[10] x;", - &expect![[r#" - Program: - version: - statements: - - [Qasm3.Parse.Token - - x expected `=`, found `;` - ,-[test:1:16] - 1 | const int[10] x; - : ^ - `---- - , Qsc.Qasm3.Compile.UnexpectedParserError - - x Unexpected parser error: Unexpected error. - ,-[test:1:1] - 1 | const int[10] x; - : ^^^^^^^^^^^^^^^^ - `---- - ]"#]], - ); -} - #[test] fn explicit_bitness_int() { check_classical_decl( diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs index 78ffe2cc58..0e7ecb3f53 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs @@ -24,33 +24,6 @@ fn implicit_bitness_int_default() { ); } -#[test] -fn const_implicit_bitness_int_default() { - check_classical_decl( - "const uint x;", - &expect![[r#" - Program: - version: - statements: - - [Qasm3.Parse.Token - - x expected `=`, found `;` - ,-[test:1:13] - 1 | const uint x; - : ^ - `---- - , Qsc.Qasm3.Compile.UnexpectedParserError - - x Unexpected parser error: Unexpected error. - ,-[test:1:1] - 1 | const uint x; - : ^^^^^^^^^^^^^ - `---- - ]"#]], - ); -} - #[test] fn const_implicit_bitness_int_lit() { check_classical_decl( @@ -336,33 +309,6 @@ fn const_explicit_bitness_int() { ); } -#[test] -fn explicit_bitness_int() { - check_classical_decl( - "const uint[10] x;", - &expect![[r#" - Program: - version: - statements: - - [Qasm3.Parse.Token - - x expected `=`, found `;` - ,-[test:1:17] - 1 | const uint[10] x; - : ^ - `---- - , Qsc.Qasm3.Compile.UnexpectedParserError - - x Unexpected parser error: Unexpected error. - ,-[test:1:1] - 1 | const uint[10] x; - : ^^^^^^^^^^^^^^^^^ - `---- - ]"#]], - ); -} - #[test] fn assigning_uint_to_negative_lit_results_in_semantic_error() { check_classical_decl( 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..8b24eb8bbc --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/statements.rs @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +pub mod box_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..ff478ed7bd --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/box_stmt.rs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use expect_test::expect; + +use crate::semantic::tests::check_stmt_kinds; + +#[test] +fn with_invalid_instruction_fails() { + check_stmt_kinds( + "box { + 2 + 4; + }", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.ClassicalStmtInBox + + x invalid classical statement in box + ,-[test:2:9] + 1 | box { + 2 | 2 + 4; + : ^^^^^^ + 3 | } + `---- + ]"#]], + ); +} + +#[test] +fn with_duration_fails() { + check_stmt_kinds( + "box [4us] { }", + &expect![[r#" + Program: + version: + statements: + + [Qsc.Qasm3.Compile.NotSupported + + x Box with duration are not supported. + ,-[test:1:6] + 1 | box [4us] { } + : ^^^ + `---- + ]"#]], + ); +} From 4f352af9f36e81a56044e12052ab3a5e87dc0dde Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Mon, 17 Mar 2025 06:13:34 -0700 Subject: [PATCH 61/98] Lower control flow stmts in QASM3 parser (#2232) This PR: 1. Aliases super::ast to semantic. 2. Fixes The bodies of if, for, and while to be `Stmt` instead of `List`. 3. Lowers the if, for, while, and switch stmts. 4. Refactors the pattern used to handle lowering of optional items by introducing the `short_circuit_opt_item!` macro. --- compiler/qsc_qasm3/src/parser/ast.rs | 4 +- compiler/qsc_qasm3/src/parser/mut_visit.rs | 2 +- compiler/qsc_qasm3/src/parser/stmt.rs | 8 +- .../src/parser/stmt/tests/for_loops.rs | 38 +- compiler/qsc_qasm3/src/semantic/ast.rs | 54 +- compiler/qsc_qasm3/src/semantic/error.rs | 6 + compiler/qsc_qasm3/src/semantic/lowerer.rs | 898 +++++++++++------- .../src/semantic/tests/statements.rs | 6 +- .../src/semantic/tests/statements/box_stmt.rs | 3 +- .../src/semantic/tests/statements/for_stmt.rs | 92 ++ .../src/semantic/tests/statements/if_stmt.rs | 175 ++++ .../semantic/tests/statements/switch_stmt.rs | 117 +++ .../semantic/tests/statements/while_stmt.rs | 94 ++ 13 files changed, 1128 insertions(+), 369 deletions(-) create mode 100644 compiler/qsc_qasm3/src/semantic/tests/statements/for_stmt.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/statements/if_stmt.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/statements/switch_stmt.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs diff --git a/compiler/qsc_qasm3/src/parser/ast.rs b/compiler/qsc_qasm3/src/parser/ast.rs index a4e829e319..3dea4a2096 100644 --- a/compiler/qsc_qasm3/src/parser/ast.rs +++ b/compiler/qsc_qasm3/src/parser/ast.rs @@ -1369,7 +1369,7 @@ impl Display for WhileLoop { pub struct ForStmt { pub span: Span, pub ty: ScalarType, - pub identifier: Identifier, + pub ident: Ident, pub set_declaration: Box, pub body: Stmt, } @@ -1378,7 +1378,7 @@ 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.identifier)?; + writeln_field(f, "variable_name", &self.ident)?; writeln_field(f, "iterable", &self.set_declaration)?; write_field(f, "body", &self.body) } diff --git a/compiler/qsc_qasm3/src/parser/mut_visit.rs b/compiler/qsc_qasm3/src/parser/mut_visit.rs index b9ffcd2113..e25c5985c2 100644 --- a/compiler/qsc_qasm3/src/parser/mut_visit.rs +++ b/compiler/qsc_qasm3/src/parser/mut_visit.rs @@ -486,7 +486,7 @@ fn walk_extern_stmt(vis: &mut impl MutVisitor, stmt: &mut ExternDecl) { 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_identifier(&mut stmt.identifier); + vis.visit_ident(&mut stmt.ident); vis.visit_enumerable_set(&mut stmt.set_declaration); vis.visit_stmt(&mut stmt.body); } diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 08745117ef..c2915be11b 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -119,7 +119,7 @@ pub(super) fn parse(s: &mut ParserContext) -> Result { 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_loop)? { + } 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) @@ -1226,11 +1226,11 @@ fn for_loop_iterable_expr(s: &mut ParserContext) -> Result { /// Grammar: /// `FOR scalarType Identifier IN (setExpression | LBRACKET rangeExpression RBRACKET | expression) body=statementOrScope`. /// Reference: . -pub fn parse_for_loop(s: &mut ParserContext) -> Result { +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 identifier = Identifier::Ident(Box::new(prim::ident(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)?; @@ -1238,7 +1238,7 @@ pub fn parse_for_loop(s: &mut ParserContext) -> Result { Ok(ForStmt { span: s.span(lo), ty, - identifier, + ident, set_declaration, body: block, }) diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs index 0f5822a6e0..c8d5517bcc 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs @@ -5,7 +5,7 @@ use crate::parser::{stmt::parse, tests::check}; use expect_test::expect; #[test] -fn simple_for_loop() { +fn simple_for_stmt() { check( parse, " @@ -38,7 +38,7 @@ fn simple_for_loop() { } #[test] -fn empty_for_loop() { +fn empty_for_stmt_body() { check( parse, "for int x in {} {}", @@ -58,7 +58,7 @@ fn empty_for_loop() { } #[test] -fn simple_for_loop_stmt_body() { +fn simple_for_stmt_stmt_body() { check( parse, " @@ -88,7 +88,7 @@ fn simple_for_loop_stmt_body() { } #[test] -fn for_loop_range() { +fn for_stmt_iterating_over_range() { check( parse, " @@ -120,7 +120,7 @@ fn for_loop_range() { } #[test] -fn for_loop_range_no_step() { +fn for_stmt_iterating_over_range_no_step() { check( parse, " @@ -152,7 +152,7 @@ fn for_loop_range_no_step() { } #[test] -fn for_loop_expr() { +fn for_stmt_iterating_over_expr() { check( parse, " @@ -181,7 +181,7 @@ fn for_loop_expr() { } #[test] -fn for_loop_with_continue_stmt() { +fn for_stmt_with_continue_stmt() { check( parse, " @@ -351,3 +351,27 @@ fn nested_single_stmt_for_stmt() { 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/semantic/ast.rs b/compiler/qsc_qasm3/src/semantic/ast.rs index ecb4d15e2f..9e004b5715 100644 --- a/compiler/qsc_qasm3/src/semantic/ast.rs +++ b/compiler/qsc_qasm3/src/semantic/ast.rs @@ -433,16 +433,16 @@ impl Display for DefCalStmt { pub struct IfStmt { pub span: Span, pub condition: Expr, - pub if_block: List, - pub else_block: Option>, + 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_list_field(f, "if_block", &self.if_block)?; - write_opt_list_field(f, "else_block", self.else_block.as_ref()) + writeln_field(f, "if_body", &self.if_body)?; + write_opt_field(f, "else_body", self.else_body.as_ref()) } } @@ -1316,33 +1316,31 @@ impl Display for ReturnStmt { pub struct WhileLoop { pub span: Span, pub while_condition: Expr, - pub block: List, + 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_list_field(f, "block", &self.block) + write_field(f, "body", &self.body) } } #[derive(Clone, Debug)] pub struct ForStmt { pub span: Span, - pub ty: ScalarType, - pub identifier: Identifier, + pub loop_variable: SymbolId, pub set_declaration: Box, - pub block: List, + 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.identifier)?; + writeln_field(f, "loop_variable", &self.loop_variable)?; writeln_field(f, "iterable", &self.set_declaration)?; - write_list_field(f, "block", &self.block) + write_field(f, "body", &self.body) } } @@ -1599,13 +1597,43 @@ impl Display for LiteralKind { } } -#[derive(Debug, Clone, Copy, PartialEq)] +#[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 { diff --git a/compiler/qsc_qasm3/src/semantic/error.rs b/compiler/qsc_qasm3/src/semantic/error.rs index 68bb7d7721..b520e6cfe4 100644 --- a/compiler/qsc_qasm3/src/semantic/error.rs +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -132,6 +132,9 @@ pub enum SemanticErrorKind { #[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), @@ -286,6 +289,9 @@ impl SemanticErrorKind { } 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) } diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index 679700fef1..51fff75839 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -3,6 +3,7 @@ use std::ops::ShlAssign; +use super::symbols::ScopeKind; use super::types::binop_requires_int_conversion_for_type; use super::types::binop_requires_symmetric_int_conversion; use super::types::is_complex_binop_supported; @@ -21,11 +22,13 @@ 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 super::ast as semantic; use crate::parser::ast as syntax; use super::{ @@ -33,6 +36,55 @@ use super::{ SemanticErrorKind, }; +/// This macro allow us to apply the `?` to the inner option +/// in a situation where we have two nested options. This +/// situation is common when lowering items that were originally +/// optional. +/// +/// Example usage: +/// ```ignore +/// let item: Option = ...; +/// let item: Option> = item.as_ref().map(|s| self.lower_stmt(s)); +/// +/// // lower some other items in between +/// // ... +/// +/// // We short-circuit after lowering all the items to +/// // catch as many errors as possible before returning. +/// +/// // Note that here we are applying the `?` operator to +/// // the inner option, and not to the outer one. That is, +/// // because the outer option being `None` is not necessarily +/// // an error, e.g.: the else-body of an if_stmt. +/// // But the inner option being `None` is always an error, +/// // which occured during lowering. +/// let item: Option = short_circuit_opt_item!(item); +/// ``` +macro_rules! short_circuit_opt_item { + ($nested_opt:expr) => { + if let Some(inner_opt) = $nested_opt { + Some(inner_opt?) + } else { + None + } + }; +} + +/// This helper function evaluates the contents of `f` within a scope +/// of kind `kind`. It's purpose is to avoid making the mistake of +/// returning early from a function while a scope is pushed, for example, +/// by using the `?` operator. +#[must_use] +fn with_scope(lw: &mut Lowerer, kind: ScopeKind, f: F) -> Option +where + F: FnOnce(&mut Lowerer) -> Option, +{ + lw.symbols.push_scope(kind); + let res = f(lw); + lw.symbols.pop_scope(); + res +} + pub(super) struct Lowerer { /// The root QASM source to compile. pub source: QasmSource, @@ -70,7 +122,7 @@ impl Lowerer { self.lower_source(source); - let program = super::ast::Program { + let program = semantic::Program { version: self.version, statements: syntax::list_from_iter(self.stmts), }; @@ -150,83 +202,73 @@ impl Lowerer { } #[allow(clippy::too_many_lines)] - fn lower_stmt(&mut self, stmt: &syntax::Stmt) -> Option { + fn lower_stmt(&mut self, stmt: &syntax::Stmt) -> Option { let kind = match &*stmt.kind { - syntax::StmtKind::Alias(stmt) => super::ast::StmtKind::Alias(self.lower_alias(stmt)?), + syntax::StmtKind::Alias(stmt) => semantic::StmtKind::Alias(self.lower_alias(stmt)?), syntax::StmtKind::Assign(stmt) => self.lower_assign(stmt)?, syntax::StmtKind::AssignOp(stmt) => { - super::ast::StmtKind::AssignOp(self.lower_assign_op(stmt)?) + semantic::StmtKind::AssignOp(self.lower_assign_op(stmt)?) } syntax::StmtKind::Barrier(stmt) => { - super::ast::StmtKind::Barrier(self.lower_barrier(stmt)?) + semantic::StmtKind::Barrier(self.lower_barrier(stmt)?) } - syntax::StmtKind::Box(stmt) => super::ast::StmtKind::Box(self.lower_box(stmt)?), + syntax::StmtKind::Box(stmt) => semantic::StmtKind::Box(self.lower_box(stmt)?), syntax::StmtKind::Break(stmt) => self.lower_break(stmt)?, syntax::StmtKind::Block(stmt) => { - super::ast::StmtKind::Block(Box::new(self.lower_block(stmt)?)) + semantic::StmtKind::Block(Box::new(self.lower_block(stmt)?)) } syntax::StmtKind::Cal(stmt) => self.lower_calibration(stmt)?, syntax::StmtKind::CalibrationGrammar(stmt) => { - super::ast::StmtKind::CalibrationGrammar(self.lower_calibration_grammar(stmt)?) + semantic::StmtKind::CalibrationGrammar(self.lower_calibration_grammar(stmt)?) } syntax::StmtKind::ClassicalDecl(stmt) => { - super::ast::StmtKind::ClassicalDecl(self.lower_classical_decl(stmt)?) + semantic::StmtKind::ClassicalDecl(self.lower_classical_decl(stmt)?) } syntax::StmtKind::ConstDecl(stmt) => { - super::ast::StmtKind::ClassicalDecl(self.lower_const_decl(stmt)?) + semantic::StmtKind::ClassicalDecl(self.lower_const_decl(stmt)?) } syntax::StmtKind::Continue(stmt) => self.lower_continue_stmt(stmt)?, - syntax::StmtKind::Def(stmt) => super::ast::StmtKind::Def(self.lower_def(stmt)?), - syntax::StmtKind::DefCal(stmt) => { - super::ast::StmtKind::DefCal(self.lower_def_cal(stmt)?) - } - syntax::StmtKind::Delay(stmt) => super::ast::StmtKind::Delay(self.lower_delay(stmt)?), + syntax::StmtKind::Def(stmt) => semantic::StmtKind::Def(self.lower_def(stmt)?), + syntax::StmtKind::DefCal(stmt) => semantic::StmtKind::DefCal(self.lower_def_cal(stmt)?), + syntax::StmtKind::Delay(stmt) => semantic::StmtKind::Delay(self.lower_delay(stmt)?), syntax::StmtKind::Empty => { // we ignore empty statements None? } - syntax::StmtKind::End(stmt) => super::ast::StmtKind::End(self.lower_end_stmt(stmt)?), + syntax::StmtKind::End(stmt) => semantic::StmtKind::End(self.lower_end_stmt(stmt)?), syntax::StmtKind::ExprStmt(stmt) => { - super::ast::StmtKind::ExprStmt(self.lower_expr_stmt(stmt)?) + semantic::StmtKind::ExprStmt(self.lower_expr_stmt(stmt)?) } syntax::StmtKind::ExternDecl(extern_decl) => { - super::ast::StmtKind::ExternDecl(self.lower_extern(extern_decl)?) + semantic::StmtKind::ExternDecl(self.lower_extern(extern_decl)?) } - syntax::StmtKind::For(stmt) => super::ast::StmtKind::For(self.lower_for_stmt(stmt)?), - syntax::StmtKind::If(stmt) => super::ast::StmtKind::If(self.lower_if_stmt(stmt)?), + syntax::StmtKind::For(stmt) => semantic::StmtKind::For(self.lower_for_stmt(stmt)?), + syntax::StmtKind::If(stmt) => semantic::StmtKind::If(self.lower_if_stmt(stmt)?), syntax::StmtKind::GateCall(stmt) => { - super::ast::StmtKind::GateCall(self.lower_gate_call(stmt)?) - } - syntax::StmtKind::GPhase(stmt) => { - super::ast::StmtKind::GPhase(self.lower_gphase(stmt)?) + semantic::StmtKind::GateCall(self.lower_gate_call(stmt)?) } + syntax::StmtKind::GPhase(stmt) => semantic::StmtKind::GPhase(self.lower_gphase(stmt)?), syntax::StmtKind::Include(stmt) => { - super::ast::StmtKind::Include(self.lower_include(stmt)?) + semantic::StmtKind::Include(self.lower_include(stmt)?) } syntax::StmtKind::IODeclaration(stmt) => { - super::ast::StmtKind::IODeclaration(self.lower_io_decl(stmt)?) + semantic::StmtKind::IODeclaration(self.lower_io_decl(stmt)?) } syntax::StmtKind::Measure(stmt) => { - super::ast::StmtKind::Measure(self.lower_measure(stmt)?) - } - syntax::StmtKind::Pragma(stmt) => { - super::ast::StmtKind::Pragma(self.lower_pragma(stmt)?) + semantic::StmtKind::Measure(self.lower_measure(stmt)?) } + syntax::StmtKind::Pragma(stmt) => semantic::StmtKind::Pragma(self.lower_pragma(stmt)?), syntax::StmtKind::QuantumGateDefinition(stmt) => { - super::ast::StmtKind::QuantumGateDefinition(self.lower_gate_def(stmt)?) + semantic::StmtKind::QuantumGateDefinition(self.lower_gate_def(stmt)?) } syntax::StmtKind::QuantumDecl(stmt) => { - super::ast::StmtKind::QuantumDecl(self.lower_quantum_decl(stmt)?) - } - syntax::StmtKind::Reset(stmt) => super::ast::StmtKind::Reset(self.lower_reset(stmt)?), - syntax::StmtKind::Return(stmt) => { - super::ast::StmtKind::Return(self.lower_return(stmt)?) - } - syntax::StmtKind::Switch(stmt) => { - super::ast::StmtKind::Switch(self.lower_switch(stmt)?) + semantic::StmtKind::QuantumDecl(self.lower_quantum_decl(stmt)?) } + syntax::StmtKind::Reset(stmt) => semantic::StmtKind::Reset(self.lower_reset(stmt)?), + syntax::StmtKind::Return(stmt) => semantic::StmtKind::Return(self.lower_return(stmt)?), + syntax::StmtKind::Switch(stmt) => semantic::StmtKind::Switch(self.lower_switch(stmt)?), syntax::StmtKind::WhileLoop(stmt) => { - super::ast::StmtKind::WhileLoop(self.lower_while_loop(stmt)?) + semantic::StmtKind::WhileLoop(self.lower_while_stmt(stmt)?) } syntax::StmtKind::Err => { self.push_semantic_error(SemanticErrorKind::UnexpectedParserError( @@ -237,7 +279,7 @@ impl Lowerer { } }; let annotations = self.lower_annotations(&stmt.annotations, &stmt.kind); - Some(super::ast::Stmt { + Some(semantic::Stmt { span: stmt.span, annotations: syntax::list_from_iter(annotations), kind: Box::new(kind), @@ -304,6 +346,18 @@ impl Lowerer { 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); @@ -323,7 +377,7 @@ impl Lowerer { WithSource::from_map(&self.source_map, error) } - fn lower_alias(&mut self, alias: &syntax::AliasDeclStmt) -> Option { + fn lower_alias(&mut self, alias: &syntax::AliasDeclStmt) -> Option { 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. @@ -365,20 +419,22 @@ impl Lowerer { // we failed return None; } - Some(super::ast::AliasDeclStmt { + Some(semantic::AliasDeclStmt { span: alias.span, symbol_id, exprs: syntax::list_from_iter(rhs), }) } - fn lower_assign(&mut self, stmt: &syntax::AssignStmt) -> Option { + fn lower_assign(&mut self, stmt: &syntax::AssignStmt) -> Option { if stmt.lhs.indices.is_empty() { - Some(super::ast::StmtKind::Assign( - self.lower_simple_assign_expr(&stmt.lhs.name, &stmt.rhs, stmt.span)?, - )) + Some(semantic::StmtKind::Assign(self.lower_simple_assign_expr( + &stmt.lhs.name, + &stmt.rhs, + stmt.span, + )?)) } else { - Some(super::ast::StmtKind::IndexedAssign( + Some(semantic::StmtKind::IndexedAssign( self.lower_indexed_assign_expr(&stmt.lhs, &stmt.rhs, stmt.span)?, )) } @@ -389,20 +445,19 @@ impl Lowerer { ident: &syntax::Ident, rhs: &syntax::Expr, span: Span, - ) -> Option { + ) -> Option { let (lhs_symbol_id, lhs_symbol) = self.symbols.get_symbol_by_name(&ident.name)?; let ty = lhs_symbol.ty.clone(); + let rhs = self.lower_expr_with_target_type(Some(rhs), &ty, span); if ty.is_const() { let kind = SemanticErrorKind::CannotUpdateConstVariable(ident.name.to_string(), ident.span); self.push_semantic_error(kind); - // usually we'd return None here, but we'll continue to compile the rhs - // looking for more errors. There is nothing in this type of error that - // would prevent us from compiling the rhs. + return None; } + let rhs = rhs?; - let rhs = self.lower_expr_with_target_type(Some(rhs), &ty, span)?; - Some(super::ast::AssignStmt { + Some(semantic::AssignStmt { symbold_id: lhs_symbol_id, rhs, span, @@ -414,25 +469,23 @@ impl Lowerer { index_expr: &syntax::IndexedIdent, rhs: &syntax::Expr, span: Span, - ) -> Option { + ) -> Option { let ident = index_expr.name.clone(); let (lhs_symbol_id, lhs_symbol) = self.symbols.get_symbol_by_name(&ident.name)?; let ty = lhs_symbol.ty.clone(); + let lhs = self.lower_indexed_ident_expr(index_expr); + let rhs = self.lower_expr_with_target_type(Some(rhs), &ty, span); + if ty.is_const() { let kind = SemanticErrorKind::CannotUpdateConstVariable(ident.name.to_string(), ident.span); self.push_semantic_error(kind); - // usually we'd return None here, but we'll continue to compile the rhs - // looking for more errors. There is nothing in this type of error that - // would prevent us from compiling the rhs. return None; } - let lhs = self.lower_indexed_ident_expr(index_expr); - let rhs = self.lower_expr_with_target_type(Some(rhs), &ty, span); let (lhs, rhs) = (lhs?, rhs?); - Some(super::ast::IndexedAssignStmt { + Some(semantic::IndexedAssignStmt { symbold_id: lhs_symbol_id, lhs, rhs, @@ -440,15 +493,14 @@ impl Lowerer { }) } - fn lower_assign_op(&mut self, stmt: &syntax::AssignOpStmt) -> Option { + fn lower_assign_op(&mut self, stmt: &syntax::AssignOpStmt) -> Option { let op = stmt.op; let lhs = &stmt.lhs; let rhs = &stmt.rhs; - let ident = lhs.name.clone(); - let (lhs_symbol_id, lhs_symbol) = self.symbols.get_symbol_by_name(&ident.name)?; let ty = lhs_symbol.ty.clone(); + if ty.is_const() { let kind = SemanticErrorKind::CannotUpdateConstVariable(ident.name.to_string(), ident.span); @@ -465,7 +517,7 @@ impl Lowerer { let (lhs, rhs) = (lhs?, rhs?); let rhs = self.lower_binary_op_expr(op, lhs.clone(), rhs, stmt.span)?; let rhs = self.cast_expr_to_type(&ty, &rhs, stmt.span)?; - Some(super::ast::AssignOpStmt { + Some(semantic::AssignOpStmt { span: stmt.span, symbold_id: lhs_symbol_id, lhs, @@ -473,7 +525,7 @@ impl Lowerer { }) } - fn lower_expr(&mut self, expr: &syntax::Expr) -> Option { + fn lower_expr(&mut self, expr: &syntax::Expr) -> Option { match &*expr.kind { syntax::ExprKind::BinaryOp(bin_op_expr) => { let lhs = self.lower_expr(&bin_op_expr.lhs); @@ -504,46 +556,46 @@ impl Lowerer { } } - fn lower_ident_expr(&mut self, ident: &syntax::Ident) -> Option { + fn lower_ident_expr(&mut self, ident: &syntax::Ident) -> Option { let name = ident.name.clone(); let Some((symbol_id, symbol)) = self.symbols.get_symbol_by_name(&name) else { self.push_missing_symbol_error(&name, ident.span); return None; }; - let kind = super::ast::ExprKind::Ident(symbol_id); - Some(super::ast::Expr { + let kind = semantic::ExprKind::Ident(symbol_id); + Some(semantic::Expr { span: ident.span, kind: Box::new(kind), ty: symbol.ty.clone(), }) } - fn lower_lit_expr(&mut self, expr: &syntax::Lit) -> Option { + fn lower_lit_expr(&mut self, expr: &syntax::Lit) -> Option { let (kind, ty) = match &expr.kind { syntax::LiteralKind::BigInt(value) => { // todo: 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. - (super::ast::LiteralKind::BigInt(value.clone()), Type::Err) + (semantic::LiteralKind::BigInt(value.clone()), Type::Err) } syntax::LiteralKind::Bitstring(value, size) => ( - super::ast::LiteralKind::Bitstring(value.clone(), *size), + semantic::LiteralKind::Bitstring(value.clone(), *size), Type::BitArray(super::types::ArrayDimensions::One(*size), true), ), syntax::LiteralKind::Bool(value) => { - (super::ast::LiteralKind::Bool(*value), Type::Bool(true)) + (semantic::LiteralKind::Bool(*value), Type::Bool(true)) } syntax::LiteralKind::Int(value) => { - (super::ast::LiteralKind::Int(*value), Type::Int(None, true)) + (semantic::LiteralKind::Int(*value), Type::Int(None, true)) } syntax::LiteralKind::Float(value) => ( - super::ast::LiteralKind::Float(*value), + semantic::LiteralKind::Float(*value), Type::Float(None, true), ), syntax::LiteralKind::Imaginary(value) => ( - super::ast::LiteralKind::Complex(0.0, *value), + semantic::LiteralKind::Complex(0.0, *value), Type::Complex(None, true), ), syntax::LiteralKind::String(_) => { @@ -573,31 +625,31 @@ impl Lowerer { } ( - super::ast::LiteralKind::Array(syntax::list_from_iter(texprs)), + semantic::LiteralKind::Array(syntax::list_from_iter(texprs)), Type::Err, ) } }; - Some(super::ast::Expr { + Some(semantic::Expr { span: expr.span, - kind: Box::new(super::ast::ExprKind::Lit(kind)), + kind: Box::new(semantic::ExprKind::Lit(kind)), ty, }) } - fn lower_paren_expr(&mut self, expr: &syntax::Expr) -> Option { + fn lower_paren_expr(&mut self, expr: &syntax::Expr) -> Option { let expr = self.lower_expr(expr)?; let span = expr.span; let ty = expr.ty.clone(); - let kind = super::ast::ExprKind::Paren(expr); - Some(super::ast::Expr { + let kind = semantic::ExprKind::Paren(expr); + Some(semantic::Expr { span, kind: Box::new(kind), ty, }) } - fn lower_unary_op_expr(&mut self, expr: &syntax::UnaryOpExpr) -> Option { + fn lower_unary_op_expr(&mut self, expr: &syntax::UnaryOpExpr) -> Option { match expr.op { syntax::UnaryOp::Neg => { if let syntax::ExprKind::Lit(lit) = expr.expr.kind.as_ref() { @@ -607,13 +659,13 @@ impl Lowerer { let ty = expr.ty.clone(); if unary_op_can_be_applied_to_type(syntax::UnaryOp::Neg, &ty) { let span = expr.span; - let unary = super::ast::UnaryOpExpr { - op: super::ast::UnaryOp::Neg, + let unary = semantic::UnaryOpExpr { + op: semantic::UnaryOp::Neg, expr, }; - Some(super::ast::Expr { + Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::UnaryOp(unary)), + kind: Box::new(semantic::ExprKind::UnaryOp(unary)), ty, }) } else { @@ -631,13 +683,13 @@ impl Lowerer { let ty = expr.ty.clone(); if unary_op_can_be_applied_to_type(syntax::UnaryOp::NotB, &ty) { let span = expr.span; - let unary = super::ast::UnaryOpExpr { - op: super::ast::UnaryOp::NotB, + let unary = semantic::UnaryOpExpr { + op: semantic::UnaryOp::NotB, expr, }; - Some(super::ast::Expr { + Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::UnaryOp(unary)), + kind: Box::new(semantic::ExprKind::UnaryOp(unary)), ty, }) } else { @@ -659,10 +711,10 @@ impl Lowerer { let ty = expr.ty.clone(); - Some(super::ast::Expr { + Some(semantic::Expr { span: expr.span, - kind: Box::new(super::ast::ExprKind::UnaryOp(super::ast::UnaryOpExpr { - op: super::ast::UnaryOp::NotL, + kind: Box::new(semantic::ExprKind::UnaryOp(semantic::UnaryOpExpr { + op: semantic::UnaryOp::NotL, expr, })), ty, @@ -675,7 +727,7 @@ impl Lowerer { &mut self, annotations: &[Box], kind: &syntax::StmtKind, - ) -> Vec { + ) -> Vec { annotations .iter() .map(|annotation| self.lower_annotation(annotation, kind)) @@ -686,7 +738,7 @@ impl Lowerer { &mut self, annotation: &syntax::Annotation, kind: &syntax::StmtKind, - ) -> super::ast::Annotation { + ) -> semantic::Annotation { if !matches!( annotation.identifier.to_string().as_str(), "SimulatableIntrinsic" | "Config" @@ -707,7 +759,7 @@ impl Lowerer { ); } - super::ast::Annotation { + semantic::Annotation { span: annotation.span, identifier: annotation.identifier.clone(), value: annotation.value.as_ref().map(Clone::clone), @@ -783,7 +835,7 @@ impl Lowerer { } } - fn lower_barrier(&mut self, stmt: &syntax::BarrierStmt) -> Option { + fn lower_barrier(&mut self, stmt: &syntax::BarrierStmt) -> Option { self.push_unimplemented_error_message("barrier stmt", stmt.span); None } @@ -792,7 +844,7 @@ impl Lowerer { /// . /// Search for the definition of `Box` there, and then for all the classes /// inhereting from `QuantumStatement`. - fn lower_box(&mut self, stmt: &syntax::BoxStmt) -> Option { + fn lower_box(&mut self, stmt: &syntax::BoxStmt) -> Option { let stmts = stmt .body .iter() @@ -832,20 +884,28 @@ impl Lowerer { None } - fn lower_break(&mut self, stmt: &syntax::BreakStmt) -> Option { + fn lower_break(&mut self, stmt: &syntax::BreakStmt) -> Option { self.push_unimplemented_error_message("break stmt", stmt.span); None } - fn lower_block(&mut self, stmt: &syntax::Block) -> Option { - self.push_unimplemented_error_message("block stmt", stmt.span); - None + fn lower_block(&mut self, stmt: &syntax::Block) -> Option { + self.symbols.push_scope(ScopeKind::Block); + let stmts = stmt.stmts.iter().filter_map(|stmt| self.lower_stmt(stmt)); + let stmts = list_from_iter(stmts); + self.symbols.pop_scope(); + + if stmts.len() != stmt.stmts.len() { + return None; + } + + Some(semantic::Block { + span: stmt.span, + stmts, + }) } - fn lower_calibration( - &mut self, - stmt: &syntax::CalibrationStmt, - ) -> Option { + fn lower_calibration(&mut self, stmt: &syntax::CalibrationStmt) -> Option { self.push_unimplemented_error_message("calibration stmt", stmt.span); None } @@ -853,7 +913,7 @@ impl Lowerer { fn lower_calibration_grammar( &mut self, stmt: &syntax::CalibrationGrammarStmt, - ) -> Option { + ) -> Option { self.push_unimplemented_error_message("calibration stmt", stmt.span); None } @@ -861,7 +921,7 @@ impl Lowerer { fn lower_classical_decl( &mut self, stmt: &syntax::ClassicalDeclarationStmt, - ) -> Option { + ) -> Option { let is_const = false; // const decls are handled separately let ty = self.get_semantic_type_from_tydef(&stmt.ty, is_const)?; @@ -883,14 +943,14 @@ impl Lowerer { Some(expr) => match expr { syntax::ValueExpression::Expr(expr) => self .lower_expr_with_target_type(Some(expr), &ty, stmt_span) - .map(super::ast::ValueExpression::Expr), + .map(semantic::ValueExpression::Expr), syntax::ValueExpression::Measurement(measure_expr) => self .lower_measure_expr_with_target_type(measure_expr, &ty, stmt_span) - .map(super::ast::ValueExpression::Measurement), + .map(semantic::ValueExpression::Measurement), }, None => self .lower_expr_with_target_type(None, &ty, stmt_span) - .map(super::ast::ValueExpression::Expr), + .map(semantic::ValueExpression::Expr), }; let Ok(symbol_id) = self.symbols.insert_symbol(symbol) else { @@ -902,7 +962,7 @@ impl Lowerer { // for classical declarations. So if None is returned we hit an error with the expression. let init_expr = init_expr?; - Some(super::ast::ClassicalDeclarationStmt { + Some(semantic::ClassicalDeclarationStmt { span: stmt_span, ty_span, symbol_id, @@ -913,7 +973,7 @@ impl Lowerer { fn lower_const_decl( &mut self, stmt: &syntax::ConstantDeclStmt, - ) -> Option { + ) -> Option { let is_const = true; let ty = self.get_semantic_type_from_tydef(&stmt.ty, is_const)?; let ty_span = stmt.ty.span(); @@ -939,68 +999,120 @@ impl Lowerer { // for classical declarations. So if None is returned we hit an error with the expression. let init_expr = init_expr?; - Some(super::ast::ClassicalDeclarationStmt { + Some(semantic::ClassicalDeclarationStmt { span: stmt.span, ty_span, symbol_id, - init_expr: Box::new(super::ast::ValueExpression::Expr(init_expr)), + init_expr: Box::new(semantic::ValueExpression::Expr(init_expr)), }) } - fn lower_continue_stmt(&mut self, stmt: &syntax::ContinueStmt) -> Option { + fn lower_continue_stmt(&mut self, stmt: &syntax::ContinueStmt) -> Option { self.push_unimplemented_error_message("continue stmt", stmt.span); None } - fn lower_def(&mut self, stmt: &syntax::DefStmt) -> Option { + fn lower_def(&mut self, stmt: &syntax::DefStmt) -> Option { self.push_unimplemented_error_message("def stmt", stmt.span); None } - fn lower_def_cal(&mut self, stmt: &syntax::DefCalStmt) -> Option { + fn lower_def_cal(&mut self, stmt: &syntax::DefCalStmt) -> Option { self.push_unimplemented_error_message("def cal stmt", stmt.span); None } - fn lower_delay(&mut self, stmt: &syntax::DelayStmt) -> Option { + fn lower_delay(&mut self, stmt: &syntax::DelayStmt) -> Option { self.push_unimplemented_error_message("delay stmt", stmt.span); None } - fn lower_end_stmt(&mut self, stmt: &syntax::EndStmt) -> Option { + fn lower_end_stmt(&mut self, stmt: &syntax::EndStmt) -> Option { self.push_unimplemented_error_message("end stmt", stmt.span); None } - fn lower_expr_stmt(&mut self, stmt: &syntax::ExprStmt) -> Option { + fn lower_expr_stmt(&mut self, stmt: &syntax::ExprStmt) -> Option { let expr = self.lower_expr(&stmt.expr)?; - Some(super::ast::ExprStmt { + Some(semantic::ExprStmt { span: stmt.span, expr, }) } - fn lower_extern(&mut self, stmt: &syntax::ExternDecl) -> Option { + fn lower_extern(&mut self, stmt: &syntax::ExternDecl) -> Option { self.push_unimplemented_error_message("extern stmt", stmt.span); None } - fn lower_for_stmt(&mut self, stmt: &syntax::ForStmt) -> Option { - self.push_unimplemented_error_message("for stmt", stmt.span); - None + fn lower_for_stmt(&mut self, stmt: &syntax::ForStmt) -> Option { + let set_declaration = self.lower_enumerable_set(&stmt.set_declaration); + + // Push scope where the loop variable lives. + let (loop_variable, body) = with_scope(self, ScopeKind::Block, |lw| { + let ty = lw.get_semantic_type_from_scalar_ty(&stmt.ty, false)?; + let qsharp_ty = lw.convert_semantic_type_to_qsharp_type(&ty.clone(), stmt.ty.span)?; + let symbol = Symbol { + name: stmt.ident.name.to_string(), + span: stmt.ident.span, + ty: ty.clone(), + qsharp_ty, + io_kind: IOKind::Default, + }; + + // This is the first variable in this scope, so + // we don't need to check for redefined symbols. + let symbol_id = lw + .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 = lw.lower_stmt(&stmt.body); + + Some((symbol_id, body?)) + })?; + + // We use the `?` operator after lowering all the fields + // to report as many errors as possible before exiting the function. + Some(semantic::ForStmt { + span: stmt.span, + loop_variable, + set_declaration: Box::new(set_declaration?), + body, + }) } - fn lower_if_stmt(&mut self, stmt: &syntax::IfStmt) -> Option { - self.push_unimplemented_error_message("if stmt", stmt.span); - None + fn lower_if_stmt(&mut self, stmt: &syntax::IfStmt) -> Option { + 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(false); + let condition = condition?; + let condition = self.cast_expr_to_type(&cond_ty, &condition, condition.span); + + // We use the `?` operator after lowering all the fields + // to report as many errors as possible before exiting the function. + Some(semantic::IfStmt { + span: stmt.span, + condition: condition?, + if_body: if_body?, + else_body: short_circuit_opt_item!(else_body), + }) } - fn lower_gate_call(&mut self, stmt: &syntax::GateCall) -> Option { + fn lower_gate_call(&mut self, stmt: &syntax::GateCall) -> Option { self.push_unimplemented_error_message("gate call stmt", stmt.span); None } - fn lower_gphase(&mut self, stmt: &syntax::GPhase) -> Option { + fn lower_gphase(&mut self, stmt: &syntax::GPhase) -> Option { self.push_unimplemented_error_message("gphase stmt", stmt.span); None } @@ -1008,7 +1120,7 @@ impl Lowerer { /// 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) -> Option { + fn lower_include(&mut self, stmt: &syntax::IncludeStmt) -> Option { // 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(); @@ -1021,7 +1133,7 @@ impl Lowerer { panic!("Include should have been handled in lower_source") } - fn lower_io_decl(&mut self, stmt: &syntax::IODeclaration) -> Option { + fn lower_io_decl(&mut self, stmt: &syntax::IODeclaration) -> Option { let is_const = false; let ty = self.get_semantic_type_from_tydef(&stmt.ty, is_const)?; let io_kind = stmt.io_identifier.into(); @@ -1029,7 +1141,7 @@ impl Lowerer { 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.clone(), ty_span)?; + let qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty, ty_span)?; let symbol = Symbol { name: name.to_string(), ty: ty.clone(), @@ -1048,7 +1160,7 @@ impl Lowerer { // to a parameter in the function signature once we generate the function if io_kind == IOKind::Output { let init_expr = self.get_default_value(&ty, stmt_span)?; - Some(super::ast::IODeclaration { + Some(semantic::IODeclaration { span: stmt_span, symbol_id, init_expr: Box::new(init_expr), @@ -1058,12 +1170,12 @@ impl Lowerer { } } - fn lower_measure(&mut self, stmt: &syntax::MeasureStmt) -> Option { + fn lower_measure(&mut self, stmt: &syntax::MeasureStmt) -> Option { self.push_unimplemented_error_message("measure stmt", stmt.span); None } - fn lower_pragma(&mut self, stmt: &syntax::Pragma) -> Option { + fn lower_pragma(&mut self, stmt: &syntax::Pragma) -> Option { self.push_unimplemented_error_message("pragma stmt", stmt.span); None } @@ -1071,7 +1183,7 @@ impl Lowerer { fn lower_gate_def( &mut self, stmt: &syntax::QuantumGateDefinition, - ) -> Option { + ) -> Option { self.push_unimplemented_error_message("gate def stmt", stmt.span); None } @@ -1079,29 +1191,133 @@ impl Lowerer { fn lower_quantum_decl( &mut self, stmt: &syntax::QubitDeclaration, - ) -> Option { + ) -> Option { self.push_unimplemented_error_message("qubit decl stmt", stmt.span); None } - fn lower_reset(&mut self, stmt: &syntax::ResetStmt) -> Option { + fn lower_reset(&mut self, stmt: &syntax::ResetStmt) -> Option { self.push_unimplemented_error_message("reset stmt", stmt.span); None } - fn lower_return(&mut self, stmt: &syntax::ReturnStmt) -> Option { + fn lower_return(&mut self, stmt: &syntax::ReturnStmt) -> Option { self.push_unimplemented_error_message("return stmt", stmt.span); None } - fn lower_switch(&mut self, stmt: &syntax::SwitchStmt) -> Option { - self.push_unimplemented_error_message("switch stmt", stmt.span); - None + fn lower_switch(&mut self, stmt: &syntax::SwitchStmt) -> Option { + // 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() + .filter_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, false); + let target = self.cast_expr_to_type(&target_ty, &target, target.span)?; + + // We use the `?` operator after casting the condition to int + // to report as many errors as possible before exiting the function. + let default = short_circuit_opt_item!(default); + + if cases.len() != stmt.cases.len() { + return None; + } + + // It is a parse error to have a switch statement with no cases, + // even if the default block is present. Getting here means the + // parser is broken or they changed the grammar. + assert!( + !cases.is_empty(), + "switch statement must have a control expression and at least one case" + ); + + // 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, + ); + return None; + } + } + + Some(semantic::SwitchStmt { + span: stmt.span, + target, + cases: list_from_iter(cases), + default, + }) } - fn lower_while_loop(&mut self, stmt: &syntax::WhileLoop) -> Option { - self.push_unimplemented_error_message("while loop stmt", stmt.span); - None + fn lower_switch_case( + &mut self, + switch_case: &syntax::SwitchCase, + ) -> Option { + let label_ty = Type::Int(None, false); + let labels = switch_case + .labels + .iter() + .filter_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, label.span) + }) + .collect::>(); + + let block = self.lower_block(&switch_case.block)?; + + if labels.len() != switch_case.labels.len() { + return None; + } + + Some(semantic::SwitchCase { + span: switch_case.span, + labels: list_from_iter(labels), + block, + }) + } + + fn lower_while_stmt(&mut self, stmt: &syntax::WhileLoop) -> Option { + let while_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(false); + let while_condition = while_condition?; + let while_condition = + self.cast_expr_to_type(&cond_ty, &while_condition, while_condition.span)?; + + Some(semantic::WhileLoop { + span: stmt.span, + while_condition, + body: body?, + }) } fn get_semantic_type_from_tydef( @@ -1241,7 +1457,7 @@ impl Lowerer { expr: Option<&syntax::Expr>, ty: &Type, span: Span, - ) -> Option { + ) -> Option { let Some(expr) = expr else { // In OpenQASM, classical variables may be uninitialized, but in Q#, // they must be initialized. We will use the default value for the type @@ -1257,7 +1473,7 @@ impl Lowerer { // 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 super::ast::ExprKind::Lit(lit) = &*rhs.kind { + 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) { @@ -1285,15 +1501,15 @@ impl Lowerer { _expr: &syntax::MeasureExpr, _ty: &Type, span: Span, - ) -> Option { + ) -> Option { self.push_unimplemented_error_message("measure expr with target type", span); None } - fn get_default_value(&mut self, ty: &Type, span: Span) -> Option { - use super::ast::Expr; - use super::ast::ExprKind; - use super::ast::LiteralKind; + fn get_default_value(&mut self, ty: &Type, span: Span) -> Option { + use semantic::Expr; + use semantic::ExprKind; + use semantic::LiteralKind; let from_lit_kind = |kind| -> Expr { Expr { span: Span::default(), @@ -1377,9 +1593,9 @@ impl Lowerer { fn coerce_literal_expr_to_type( &mut self, ty: &Type, - rhs: &super::ast::Expr, - kind: &super::ast::LiteralKind, - ) -> Option { + rhs: &semantic::Expr, + kind: &semantic::LiteralKind, + ) -> Option { if *ty == rhs.ty { // Base case, we shouldn't have gotten here // but if we did, we can just return the rhs @@ -1401,19 +1617,17 @@ impl Lowerer { let span = rhs.span; if matches!(lhs_ty, Type::Bit(..)) { - if let super::ast::LiteralKind::Int(value) = kind { + if let semantic::LiteralKind::Int(value) = kind { // can_cast_literal_with_value_knowledge guarantees that value is 0 or 1 - return Some(super::ast::Expr { + return Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::Lit(super::ast::LiteralKind::Int( - *value, - ))), + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Int(*value))), ty: lhs_ty.as_const(), }); - } else if let super::ast::LiteralKind::Bool(value) = kind { - return Some(super::ast::Expr { + } else if let semantic::LiteralKind::Bool(value) = kind { + return Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::Lit(super::ast::LiteralKind::Int( + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Int( i64::from(*value), ))), ty: lhs_ty.as_const(), @@ -1435,7 +1649,7 @@ impl Lowerer { _ => (false, 0), }; if is_int_to_bit_array { - if let super::ast::LiteralKind::Int(value) = kind { + if let semantic::LiteralKind::Int(value) = kind { if *value < 0 || *value >= (1 << size) { // todo: error message return None; @@ -1448,36 +1662,34 @@ impl Lowerer { return None; }; - return Some(super::ast::Expr { + return Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::Lit( - super::ast::LiteralKind::Bitstring(value, size), - )), + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Bitstring( + value, size, + ))), ty: lhs_ty.as_const(), }); } } if matches!(lhs_ty, Type::UInt(..)) { - if let super::ast::LiteralKind::Int(value) = kind { + if let semantic::LiteralKind::Int(value) = kind { // this should have been validated by can_cast_literal_with_value_knowledge - return Some(super::ast::Expr { + return Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::Lit(super::ast::LiteralKind::Int( - *value, - ))), + 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 super::ast::LiteralKind::Int(value) = kind { + if let semantic::LiteralKind::Int(value) = kind { if let Some(value) = safe_i64_to_f64(*value) { - return Some(super::ast::Expr { + return Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::Lit( - super::ast::LiteralKind::Float(value), - )), + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Float( + value, + ))), ty: lhs_ty.as_const(), }); } @@ -1491,10 +1703,10 @@ impl Lowerer { None } (Type::Angle(..) | Type::Float(..), Type::Float(..)) => { - if let super::ast::LiteralKind::Float(value) = kind { - return Some(super::ast::Expr { + if let semantic::LiteralKind::Float(value) = kind { + return Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::Lit(super::ast::LiteralKind::Float( + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Float( *value, ))), ty: lhs_ty.as_const(), @@ -1503,24 +1715,24 @@ impl Lowerer { None } (Type::Complex(..), Type::Complex(..)) => { - if let super::ast::LiteralKind::Complex(real, imag) = kind { - return Some(super::ast::Expr { + if let semantic::LiteralKind::Complex(real, imag) = kind { + return Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::Lit( - super::ast::LiteralKind::Complex(*real, *imag), - )), + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Complex( + *real, *imag, + ))), ty: lhs_ty.as_const(), }); } None } (Type::Complex(..), Type::Float(..)) => { - if let super::ast::LiteralKind::Float(value) = kind { - return Some(super::ast::Expr { + if let semantic::LiteralKind::Float(value) = kind { + return Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::Lit( - super::ast::LiteralKind::Complex(*value, 0.0), - )), + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Complex( + *value, 0.0, + ))), ty: lhs_ty.as_const(), }); } @@ -1529,12 +1741,12 @@ impl Lowerer { (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 super::ast::LiteralKind::Int(value) = kind { + if let semantic::LiteralKind::Int(value) = kind { if let Some(value) = safe_i64_to_f64(*value) { - return Some(super::ast::Expr { + return Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::Lit( - super::ast::LiteralKind::Complex(value, 0.0), + kind: Box::new(semantic::ExprKind::Lit( + semantic::LiteralKind::Complex(value, 0.0), )), ty: lhs_ty.as_const(), }); @@ -1551,13 +1763,13 @@ impl Lowerer { } (Type::Bit(..), Type::Int(..) | Type::UInt(..)) => { // we've already checked that the value is 0 or 1 - if let super::ast::LiteralKind::Int(value) = kind { + if let semantic::LiteralKind::Int(value) = kind { if *value == 0 || *value == 1 { - return Some(super::ast::Expr { + return Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::Lit( - super::ast::LiteralKind::Int(*value), - )), + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Int( + *value, + ))), ty: lhs_ty.as_const(), }); } @@ -1569,16 +1781,16 @@ impl Lowerer { (Type::Int(width, _), Type::Int(_, _) | Type::UInt(_, _)) => { // we've already checked that this conversion can happen from a signed to unsigned int match kind { - super::ast::LiteralKind::Int(value) => { - return Some(super::ast::Expr { + semantic::LiteralKind::Int(value) => { + return Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::Lit( - super::ast::LiteralKind::Int(*value), - )), + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Int( + *value, + ))), ty: lhs_ty.as_const(), }); } - super::ast::LiteralKind::BigInt(value) => { + 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); @@ -1591,11 +1803,11 @@ impl Lowerer { return None; } } - return Some(super::ast::Expr { + return Some(semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::Lit( - super::ast::LiteralKind::BigInt(value.clone()), - )), + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::BigInt( + value.clone(), + ))), ty: lhs_ty.as_const(), }); } @@ -1621,36 +1833,33 @@ impl Lowerer { lit: &syntax::Lit, target_ty: Option, span: Span, - ) -> Option { + ) -> Option { let (kind, ty) = (match &lit.kind { syntax::LiteralKind::Float(value) => Some(( - super::ast::LiteralKind::Float(-value), + semantic::LiteralKind::Float(-value), Type::Float(None, true), )), syntax::LiteralKind::Imaginary(value) => Some(( - super::ast::LiteralKind::Complex(0.0, -value), + semantic::LiteralKind::Complex(0.0, -value), Type::Complex(None, true), )), syntax::LiteralKind::Int(value) => { - Some((super::ast::LiteralKind::Int(-value), Type::Int(None, true))) + Some((semantic::LiteralKind::Int(-value), Type::Int(None, true))) } syntax::LiteralKind::BigInt(value) => { let value = BigInt::from(-1) * value; - Some(( - super::ast::LiteralKind::BigInt(value), - Type::Int(None, true), - )) + Some((semantic::LiteralKind::BigInt(value), Type::Int(None, true))) } syntax::LiteralKind::Duration(value, time_unit) => { let unit = match time_unit { - syntax::TimeUnit::Dt => super::ast::TimeUnit::Dt, - syntax::TimeUnit::Ms => super::ast::TimeUnit::Ms, - syntax::TimeUnit::Ns => super::ast::TimeUnit::Ns, - syntax::TimeUnit::S => super::ast::TimeUnit::S, - syntax::TimeUnit::Us => super::ast::TimeUnit::Us, + syntax::TimeUnit::Dt => semantic::TimeUnit::Dt, + syntax::TimeUnit::Ms => semantic::TimeUnit::Ms, + syntax::TimeUnit::Ns => semantic::TimeUnit::Ns, + syntax::TimeUnit::S => semantic::TimeUnit::S, + syntax::TimeUnit::Us => semantic::TimeUnit::Us, }; Some(( - super::ast::LiteralKind::Duration(-value, unit), + semantic::LiteralKind::Duration(-value, unit), Type::Duration(true), )) } @@ -1672,9 +1881,9 @@ impl Lowerer { } })?; - let expr = super::ast::Expr { + let expr = semantic::Expr { span, - kind: Box::new(super::ast::ExprKind::Lit(kind.clone())), + kind: Box::new(semantic::ExprKind::Lit(kind.clone())), ty, }; if let Some(target_ty) = target_ty { @@ -1686,9 +1895,9 @@ impl Lowerer { fn cast_expr_to_type( &mut self, ty: &Type, - expr: &super::ast::Expr, + expr: &semantic::Expr, span: Span, - ) -> Option { + ) -> Option { let cast_expr = self.try_cast_expr_to_type(ty, expr, span); if cast_expr.is_none() { let rhs_ty_name = format!("{:?}", expr.ty); @@ -1702,9 +1911,9 @@ impl Lowerer { fn try_cast_expr_to_type( &mut self, ty: &Type, - expr: &super::ast::Expr, + expr: &semantic::Expr, span: Span, - ) -> Option { + ) -> Option { if *ty == expr.ty { // Base case, we shouldn't have gotten here // but if we did, we can just return the rhs @@ -1727,7 +1936,7 @@ impl Lowerer { | (Type::Float(w1, _), Type::Float(w2, _)) | (Type::Complex(w1, _), Type::Complex(w2, _)) => { if w1.is_none() && w2.is_some() { - return Some(super::ast::Expr { + return Some(semantic::Expr { span: expr.span, kind: expr.kind.clone(), ty: ty.clone(), @@ -1735,7 +1944,7 @@ impl Lowerer { } if *w1 >= *w2 { - return Some(super::ast::Expr { + return Some(semantic::Expr { span: expr.span, kind: expr.kind.clone(), ty: ty.clone(), @@ -1766,7 +1975,7 @@ impl Lowerer { /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ /// | angle | Yes | No | No | No | - | Yes | No | No | /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - fn cast_angle_expr_to_type(ty: &Type, rhs: &super::ast::Expr) -> Option { + fn cast_angle_expr_to_type(ty: &Type, rhs: &semantic::Expr) -> Option { assert!(matches!(rhs.ty, Type::Angle(..))); match ty { Type::Bit(..) | Type::Bool(..) => { @@ -1786,9 +1995,9 @@ impl Lowerer { fn cast_bit_expr_to_type( &mut self, ty: &Type, - rhs: &super::ast::Expr, + rhs: &semantic::Expr, span: Span, - ) -> Option { + ) -> 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. @@ -1821,7 +2030,7 @@ impl Lowerer { /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ /// /// Additional cast to complex - fn cast_float_expr_to_type(ty: &Type, rhs: &super::ast::Expr) -> Option { + fn cast_float_expr_to_type(ty: &Type, rhs: &semantic::Expr) -> Option { assert!(matches!(rhs.ty, Type::Float(..))); match ty { &Type::Angle(..) @@ -1843,7 +2052,7 @@ impl Lowerer { /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ /// | bool | - | Yes | Yes | Yes | No | Yes | No | No | /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - fn cast_bool_expr_to_type(ty: &Type, rhs: &super::ast::Expr) -> Option { + 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(_, _) => { @@ -1865,7 +2074,7 @@ impl Lowerer { /// /// Additional cast to ``BigInt`` #[allow(clippy::too_many_lines)] - fn cast_int_expr_to_type(ty: &Type, rhs: &super::ast::Expr) -> Option { + fn cast_int_expr_to_type(ty: &Type, rhs: &semantic::Expr) -> Option { assert!(matches!(rhs.ty, Type::Int(..) | Type::UInt(..))); match ty { @@ -1883,8 +2092,8 @@ impl Lowerer { fn cast_bitarray_expr_to_type( dims: &ArrayDimensions, ty: &Type, - rhs: &super::ast::Expr, - ) -> Option { + rhs: &semantic::Expr, + ) -> Option { let ArrayDimensions::One(array_width) = dims else { return None; }; @@ -1906,10 +2115,10 @@ impl Lowerer { fn lower_binary_op_expr( &mut self, op: syntax::BinOp, - lhs: super::ast::Expr, - rhs: super::ast::Expr, + lhs: semantic::Expr, + rhs: semantic::Expr, span: Span, - ) -> Option { + ) -> Option { if lhs.ty.is_quantum() { let kind = SemanticErrorKind::QuantumTypesInBinaryExpression(lhs.span); self.push_semantic_error(kind); @@ -1961,13 +2170,13 @@ impl Lowerer { rhs }; - let bin_expr = super::ast::BinaryOpExpr { + let bin_expr = semantic::BinaryOpExpr { lhs: new_lhs, rhs: new_rhs, op: op.into(), }; - let kind = super::ast::ExprKind::BinaryOp(bin_expr); - let expr = super::ast::Expr { + let kind = semantic::ExprKind::BinaryOp(bin_expr); + let expr = semantic::Expr { span, kind: Box::new(kind), ty, @@ -2000,7 +2209,7 @@ impl Lowerer { lhs } else { match &lhs.kind.as_ref() { - super::ast::ExprKind::Lit(kind) => { + semantic::ExprKind::Lit(kind) => { if can_cast_literal(&promoted_type, &left_type) || can_cast_literal_with_value_knowledge(&promoted_type, kind) { @@ -2016,7 +2225,7 @@ impl Lowerer { rhs } else { match &rhs.kind.as_ref() { - super::ast::ExprKind::Lit(kind) => { + semantic::ExprKind::Lit(kind) => { if can_cast_literal(&promoted_type, &right_type) || can_cast_literal_with_value_knowledge(&promoted_type, kind) { @@ -2059,13 +2268,13 @@ impl Lowerer { None } } else { - let bin_expr = super::ast::BinaryOpExpr { + let bin_expr = semantic::BinaryOpExpr { op: op.into(), lhs, rhs, }; - let kind = super::ast::ExprKind::BinaryOp(bin_expr); - let expr = super::ast::Expr { + let kind = semantic::ExprKind::BinaryOp(bin_expr); + let expr = semantic::Expr { span, kind: Box::new(kind), ty: ty.clone(), @@ -2074,14 +2283,14 @@ impl Lowerer { }; let ty = match op.into() { - super::ast::BinOp::AndL - | super::ast::BinOp::Eq - | super::ast::BinOp::Gt - | super::ast::BinOp::Gte - | super::ast::BinOp::Lt - | super::ast::BinOp::Lte - | super::ast::BinOp::Neq - | super::ast::BinOp::OrL => Type::Bool(false), + 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(false), _ => ty, }; let mut expr = expr?; @@ -2127,41 +2336,13 @@ impl Lowerer { fn lower_index_element( &mut self, index: &syntax::IndexElement, - ) -> Option { + ) -> Option { match index { - syntax::IndexElement::DiscreteSet(set) => { - let items = set - .values - .iter() - .filter_map(|expr| self.lower_expr(expr)) - .collect::>(); - if set.values.len() == items.len() { - return Some(super::ast::IndexElement::DiscreteSet( - super::ast::DiscreteSet { - span: set.span, - values: syntax::list_from_iter(items), - }, - )); - } - let kind = SemanticErrorKind::FailedToCompileExpressionList(set.span); - self.push_semantic_error(kind); - None - } + syntax::IndexElement::DiscreteSet(set) => Some(semantic::IndexElement::DiscreteSet( + self.lower_discrete_set(set)?, + )), syntax::IndexElement::IndexSet(set) => { - let items = set - .values - .iter() - .filter_map(|expr| self.lower_index_set_item(expr)) - .collect::>(); - if set.values.len() == items.len() { - return Some(super::ast::IndexElement::IndexSet(super::ast::IndexSet { - span: set.span, - values: syntax::list_from_iter(items), - })); - } - let kind = SemanticErrorKind::FailedToCompileExpressionList(set.span); - self.push_semantic_error(kind); - None + Some(semantic::IndexElement::IndexSet(self.lower_index_set(set)?)) } } } @@ -2169,15 +2350,15 @@ impl Lowerer { fn lower_index_set_item( &mut self, item: &syntax::IndexSetItem, - ) -> Option { + ) -> Option { let item = match item { syntax::IndexSetItem::RangeDefinition(range_definition) => { - super::ast::IndexSetItem::RangeDefinition( + semantic::IndexSetItem::RangeDefinition( self.lower_range_definition(range_definition)?, ) } syntax::IndexSetItem::Expr(expr) => { - super::ast::IndexSetItem::Expr(self.lower_expr(expr)?) + semantic::IndexSetItem::Expr(self.lower_expr(expr)?) } syntax::IndexSetItem::Err => { unreachable!("IndexSetItem::Err should have been handled") @@ -2186,44 +2367,83 @@ impl Lowerer { Some(item) } - fn lower_range_definition( + fn lower_enumerable_set( &mut self, - range_definition: &syntax::RangeDefinition, - ) -> Option { - let mut failed = false; - let start = range_definition.start.as_ref().map(|e| { - let expr = self.lower_expr(e); - if expr.is_none() { - failed = true; - } - expr - }); - let step = range_definition.step.as_ref().map(|e| { - let expr = self.lower_expr(e); - if expr.is_none() { - failed = true; - } - expr - }); - let end = range_definition.end.as_ref().map(|e| { - let expr = self.lower_expr(e); - if expr.is_none() { - failed = true; - } - expr - }); - if failed { + set: &syntax::EnumerableSet, + ) -> Option { + match set { + syntax::EnumerableSet::DiscreteSet(set) => Some(semantic::EnumerableSet::DiscreteSet( + self.lower_discrete_set(set)?, + )), + syntax::EnumerableSet::RangeDefinition(range_definition) => { + Some(semantic::EnumerableSet::RangeDefinition( + self.lower_range_definition(range_definition)?, + )) + } + syntax::EnumerableSet::Expr(expr) => { + Some(semantic::EnumerableSet::Expr(self.lower_expr(expr)?)) + } + } + } + + fn lower_index_set(&mut self, set: &syntax::IndexSet) -> Option { + let items = set + .values + .iter() + .filter_map(|expr| self.lower_index_set_item(expr)) + .collect::>(); + + if set.values.len() != items.len() { + let kind = SemanticErrorKind::FailedToCompileExpressionList(set.span); + self.push_semantic_error(kind); return None; } - Some(super::ast::RangeDefinition { + Some(semantic::IndexSet { + span: set.span, + values: syntax::list_from_iter(items), + }) + } + + fn lower_discrete_set(&mut self, set: &syntax::DiscreteSet) -> Option { + let items = set + .values + .iter() + .filter_map(|expr| self.lower_expr(expr)) + .collect::>(); + + if set.values.len() != items.len() { + let kind = SemanticErrorKind::FailedToCompileExpressionList(set.span); + self.push_semantic_error(kind); + return None; + } + + Some(semantic::DiscreteSet { + span: set.span, + values: list_from_iter(items), + }) + } + + fn lower_range_definition( + &mut self, + range_definition: &syntax::RangeDefinition, + ) -> Option { + 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)); + + let start = short_circuit_opt_item!(start); + let step = short_circuit_opt_item!(step); + let end = short_circuit_opt_item!(end); + + Some(semantic::RangeDefinition { span: range_definition.span, - start: start.map(|s| s.expect("start should be Some")), - step: step.map(|s| s.expect("step should be Some")), - end: end.map(|e| e.expect("end should be Some")), + start, + step, + end, }) } - fn lower_index_expr(&mut self, expr: &syntax::IndexExpr) -> Option { + fn lower_index_expr(&mut self, expr: &syntax::IndexExpr) -> Option { let collection = self.lower_expr(&expr.collection); let index = self.lower_index_element(&expr.index); let collection = collection?; @@ -2231,9 +2451,9 @@ impl Lowerer { let indexed_ty = self.get_indexed_type(&collection.ty, expr.span, 1)?; - Some(super::ast::Expr { + Some(semantic::Expr { span: expr.span, - kind: Box::new(super::ast::ExprKind::IndexExpr(super::ast::IndexExpr { + kind: Box::new(semantic::ExprKind::IndexExpr(semantic::IndexExpr { span: expr.span, collection, index, @@ -2277,7 +2497,7 @@ impl Lowerer { fn lower_indexed_ident_expr( &mut self, indexed_ident: &syntax::IndexedIdent, - ) -> Option { + ) -> Option { let ident = indexed_ident.name.clone(); let indices = indexed_ident @@ -2296,10 +2516,10 @@ impl Lowerer { return None; } - Some(super::ast::Expr { + Some(semantic::Expr { span: indexed_ident.span, - kind: Box::new(super::ast::ExprKind::IndexedIdentifier( - super::ast::IndexedIdent { + kind: Box::new(semantic::ExprKind::IndexedIdentifier( + semantic::IndexedIdent { span: indexed_ident.span, symbol_id, indices: syntax::list_from_iter(indices), @@ -2310,10 +2530,10 @@ impl Lowerer { } } -fn wrap_expr_in_implicit_cast_expr(ty: Type, rhs: super::ast::Expr) -> super::ast::Expr { - super::ast::Expr { +fn wrap_expr_in_implicit_cast_expr(ty: Type, rhs: semantic::Expr) -> semantic::Expr { + semantic::Expr { span: rhs.span, - kind: Box::new(super::ast::ExprKind::Cast(super::ast::Cast { + kind: Box::new(semantic::ExprKind::Cast(semantic::Cast { span: Span::default(), expr: rhs, ty: ty.clone(), @@ -2329,7 +2549,7 @@ fn wrap_expr_in_implicit_cast_expr(ty: Type, rhs: super::ast::Expr) -> super::as /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ /// | complex | ?? | ?? | ?? | ?? | No | ?? | No | No | /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ -fn cast_complex_expr_to_type(ty: &Type, rhs: &super::ast::Expr) -> Option { +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(..))) { diff --git a/compiler/qsc_qasm3/src/semantic/tests/statements.rs b/compiler/qsc_qasm3/src/semantic/tests/statements.rs index 8b24eb8bbc..eda5cb3db6 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements.rs @@ -1,4 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -pub mod box_stmt; +mod box_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 index ff478ed7bd..4b731ec2d5 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements/box_stmt.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/box_stmt.rs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use expect_test::expect; - use crate::semantic::tests::check_stmt_kinds; +use expect_test::expect; #[test] fn with_invalid_instruction_fails() { 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..28d52475a3 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/for_stmt.rs @@ -0,0 +1,92 @@ +// 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: + + [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: 6 + iterable: DiscreteSet [18-20]: + values: + body: Stmt [21-47]: + annotations: + kind: Block [21-47]: + Stmt [31-41]: + annotations: + kind: ClassicalDeclarationStmt [31-41]: + symbol_id: 7 + ty_span: [31-34] + init_expr: Expr [39-40]: + ty: Int(None, true) + 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: 6 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, true) + kind: Lit: Int(0) + ForStmt [20-177]: + loop_variable: 7 + iterable: DiscreteSet [33-35]: + values: + body: Stmt [167-177]: + annotations: + kind: ClassicalDeclarationStmt [167-177]: + symbol_id: 8 + ty_span: [167-170] + init_expr: Expr [175-176]: + ty: Int(None, true) + 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..4fb652259a --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/if_stmt.rs @@ -0,0 +1,175 @@ +// 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 = 0; + if (true) int a = 1; + ", + &expect![[r#" + Program: + version: + statements: + Stmt [5-15]: + annotations: + kind: ClassicalDeclarationStmt [5-15]: + symbol_id: 6 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, true) + kind: Lit: Int(0) + + [Qsc.Qasm3.Compile.RedefinedSymbol + + x Redefined symbol: a. + ,-[test:3:19] + 2 | int a = 0; + 3 | if (true) int a = 1; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn else_branch_doesnt_create_its_own_scope() { + check_stmt_kinds( + " + int a = 0; + if (true) {} + else int a = 1; + ", + &expect![[r#" + Program: + version: + statements: + Stmt [5-15]: + annotations: + kind: ClassicalDeclarationStmt [5-15]: + symbol_id: 6 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, true) + kind: Lit: Int(0) + + [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 = 0; + if (true) { int a = 1; } + ", + &expect![[r#" + ClassicalDeclarationStmt [5-15]: + symbol_id: 6 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, true) + kind: Lit: Int(0) + 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: 7 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Int(None, true) + kind: Lit: Int(1) + else_body: + "#]], + ); +} + +#[test] +fn if_scope_and_else_scope_are_different() { + check_stmt_kinds( + " + int a = 0; + if (true) { int a = 1; } + else { int a = 2; } + ", + &expect![[r#" + ClassicalDeclarationStmt [5-15]: + symbol_id: 6 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, true) + kind: Lit: Int(0) + 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: 7 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Int(None, true) + kind: Lit: Int(1) + else_body: Stmt [54-68]: + annotations: + kind: Block [54-68]: + Stmt [56-66]: + annotations: + kind: ClassicalDeclarationStmt [56-66]: + symbol_id: 8 + ty_span: [56-59] + init_expr: Expr [64-65]: + ty: Int(None, true) + 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(false) + kind: Cast [0-0]: + ty: Bool(false) + 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..2ed89fa9f4 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/switch_stmt.rs @@ -0,0 +1,117 @@ +// 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: + + [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 = 0; + switch (1) { + case 1 { int a = 1; } + case 2, 3 { int a = 2; } + } + "#, + &expect![[r#" + ClassicalDeclarationStmt [5-15]: + symbol_id: 6 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, true) + kind: Lit: Int(0) + 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: 7 + ty_span: [50-53] + init_expr: Expr [58-59]: + ty: Int(None, true) + 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: 8 + ty_span: [83-86] + init_expr: Expr [91-92]: + ty: Int(None, true) + 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, false) + kind: Cast [0-0]: + ty: Int(None, false) + expr: Expr [8-12]: + ty: Bool(true) + kind: Lit: Bool(true) + cases: + SwitchCase [16-29]: + labels: + Expr [21-26]: + ty: Int(None, false) + kind: Cast [0-0]: + ty: Int(None, false) + 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..c1e5a7f644 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs @@ -0,0 +1,94 @@ +// 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_doesnt_creates_its_own_scope() { + check_stmt_kinds( + " + int a = 0; + while(true) int a = 1; + ", + &expect![[r#" + Program: + version: + statements: + Stmt [5-15]: + annotations: + kind: ClassicalDeclarationStmt [5-15]: + symbol_id: 6 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, true) + kind: Lit: Int(0) + + [Qsc.Qasm3.Compile.RedefinedSymbol + + x Redefined symbol: a. + ,-[test:3:21] + 2 | int a = 0; + 3 | while(true) int a = 1; + : ^ + 4 | + `---- + ]"#]], + ); +} + +#[test] +fn block_body_creates_its_own_scope() { + check_stmt_kinds( + " + int a = 0; + while(true) { int a = 1; } + ", + &expect![[r#" + ClassicalDeclarationStmt [5-15]: + symbol_id: 6 + ty_span: [5-8] + init_expr: Expr [13-14]: + ty: Int(None, true) + kind: Lit: Int(0) + 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: 7 + ty_span: [34-37] + init_expr: Expr [42-43]: + ty: Int(None, true) + 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(false) + kind: Cast [0-0]: + ty: Bool(false) + 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) + "#]], + ); +} From 95a42490e1a6888a843b001688d1b7bee05bb7de Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Tue, 18 Mar 2025 14:48:12 -0700 Subject: [PATCH 62/98] Adding new compiler, lowerer is now infallible (#2239) --- compiler/qsc_qasm3/src/ast_builder.rs | 8 + compiler/qsc_qasm3/src/compile/tests.rs | 11 +- compiler/qsc_qasm3/src/compiler.rs | 1250 ++++++++++++++ compiler/qsc_qasm3/src/lib.rs | 1 + compiler/qsc_qasm3/src/parser/ast.rs | 5 +- compiler/qsc_qasm3/src/parser/expr.rs | 8 +- compiler/qsc_qasm3/src/parser/expr/tests.rs | 2 + compiler/qsc_qasm3/src/parser/mut_visit.rs | 2 +- .../src/parser/stmt/tests/barrier.rs | 2 + .../qsc_qasm3/src/parser/stmt/tests/block.rs | 43 +- .../src/parser/stmt/tests/box_stmt.rs | 4 + .../src/parser/stmt/tests/classical_decl.rs | 1 + .../qsc_qasm3/src/parser/stmt/tests/delay.rs | 2 + .../src/parser/stmt/tests/expr_stmt.rs | 8 + .../src/parser/stmt/tests/for_loops.rs | 10 + .../src/parser/stmt/tests/gate_call.rs | 13 + .../qsc_qasm3/src/parser/stmt/tests/gphase.rs | 5 + .../src/parser/stmt/tests/if_stmt.rs | 14 + .../src/parser/stmt/tests/measure.rs | 6 + .../qsc_qasm3/src/parser/stmt/tests/reset.rs | 2 + .../src/parser/stmt/tests/while_loops.rs | 7 + compiler/qsc_qasm3/src/semantic.rs | 8 +- compiler/qsc_qasm3/src/semantic/ast.rs | 58 +- compiler/qsc_qasm3/src/semantic/error.rs | 6 + compiler/qsc_qasm3/src/semantic/lowerer.rs | 1473 ++++++++--------- compiler/qsc_qasm3/src/semantic/symbols.rs | 109 +- compiler/qsc_qasm3/src/semantic/tests.rs | 92 +- .../qsc_qasm3/src/semantic/tests/decls.rs | 73 +- .../src/semantic/tests/decls/angle.rs | 29 +- .../src/semantic/tests/decls/duration.rs | 35 +- .../src/semantic/tests/decls/float.rs | 109 +- .../qsc_qasm3/src/semantic/tests/decls/int.rs | 46 +- .../src/semantic/tests/decls/stretch.rs | 17 +- .../src/semantic/tests/decls/uint.rs | 102 +- .../src/semantic/tests/expression.rs | 2 +- .../binary/comparison/bit_to_bit.rs | 16 +- .../binary/comparison/bool_to_bool.rs | 16 +- .../expression/implicit_cast_from_angle.rs | 92 +- .../expression/implicit_cast_from_bit.rs | 8 + .../expression/implicit_cast_from_float.rs | 72 +- .../src/semantic/tests/statements/box_stmt.rs | 25 +- .../src/semantic/tests/statements/for_stmt.rs | 16 +- .../src/semantic/tests/statements/if_stmt.rs | 32 + .../semantic/tests/statements/switch_stmt.rs | 16 +- .../semantic/tests/statements/while_stmt.rs | 14 + compiler/qsc_qasm3/src/tests.rs | 236 ++- compiler/qsc_qasm3/src/tests/assignment.rs | 14 +- .../qsc_qasm3/src/tests/assignment/alias.rs | 18 +- compiler/qsc_qasm3/src/tests/declaration.rs | 52 +- .../qsc_qasm3/src/tests/declaration/array.rs | 6 +- .../qsc_qasm3/src/tests/declaration/bool.rs | 16 - .../src/tests/declaration/complex.rs | 32 - .../qsc_qasm3/src/tests/declaration/float.rs | 34 +- .../src/tests/declaration/integer.rs | 46 +- .../tests/declaration/io/explicit_input.rs | 2 +- .../qsc_qasm3/src/tests/declaration/qubit.rs | 6 +- .../src/tests/declaration/unsigned_integer.rs | 62 - .../qsc_qasm3/src/tests/expression/binary.rs | 5 +- .../expression/implicit_cast_from_float.rs | 2 +- compiler/qsc_qasm3/src/tests/output.rs | 84 +- .../src/tests/sample_circuits/bell_pair.rs | 23 +- .../tests/sample_circuits/rgqft_multiplier.rs | 23 +- .../qsc_qasm3/src/tests/statement/include.rs | 22 +- .../qsc_qasm3/src/tests/statement/reset.rs | 22 +- 64 files changed, 3066 insertions(+), 1509 deletions(-) create mode 100644 compiler/qsc_qasm3/src/compiler.rs diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm3/src/ast_builder.rs index 5f63aef4bb..5f65251db3 100644 --- a/compiler/qsc_qasm3/src/ast_builder.rs +++ b/compiler/qsc_qasm3/src/ast_builder.rs @@ -859,6 +859,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(), 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..eb71d6398c --- /dev/null +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -0,0 +1,1250 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::rc::Rc; + +use num_bigint::BigInt; +use qsc_data_structures::span::Span; +use qsc_frontend::{compile::SourceMap, error::WithSource}; + +use crate::{ + ast_builder::{ + build_arg_pat, build_array_reverse_expr, build_assignment_statement, build_binary_expr, + build_cast_call, build_cast_call_two_params, build_classical_decl, build_complex_from_expr, + build_convert_call_expr, build_if_expr_then_expr_else_expr, build_implicit_return_stmt, + 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_math_call_from_exprs, build_math_call_no_params, build_operation_with_stmts, + build_path_ident_expr, build_stmt_semi_from_expr, build_stmt_semi_from_expr_with_span, + build_top_level_ns_with_item, build_tuple_expr, build_unary_op_expr, build_while_stmt, + build_wrapped_block_expr, map_qsharp_type_to_ast_ty, wrap_expr_in_parens, + }, + parser::ast::{list_from_iter, List}, + runtime::{get_runtime_function_decls, RuntimeFunctions}, + semantic::{ + ast::{ + BinaryOpExpr, Cast, DiscreteSet, Expr, FunctionCall, IndexElement, IndexExpr, IndexSet, + IndexedIdent, LiteralKind, MeasureExpr, TimeUnit, UnaryOpExpr, + }, + symbols::{IOKind, Symbol, SymbolId, SymbolTable}, + types::{ArrayDimensions, Type}, + SemanticErrorKind, + }, + CompilerConfig, OperationSignature, OutputSemantics, ProgramType, QasmCompileUnit, +}; + +use crate::semantic::ast as semast; +use qsc_ast::ast::{self as qsast, NodeId, Package}; + +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, + /// The runtime functions that need to be included at the end of + /// compilation + pub runtime: RuntimeFunctions, + 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 { + self.compile_stmts(&program.statements); + self.prepend_runtime_decls(); + let program_ty = self.config.program_ty.clone(); + let (package, signature) = match program_ty { + ProgramType::File => self.build_file(), + ProgramType::Operation => self.build_operation(), + ProgramType::Fragments => (self.build_fragments(), None), + }; + + QasmCompileUnit::new(self.source_map, self.errors, Some(package), signature) + } + + /// 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 top = build_top_level_ns_with_item(whole_span, ns, operation); + ( + Package { + nodes: Box::new([top]), + ..Default::default() + }, + Some(signature), + ) + } + + /// Creates an operation with the given name. + fn build_operation(&mut self) -> (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(); + let output = self.symbols.get_output(); + self.create_entry_item( + name, + stmts, + input, + output, + whole_span, + self.config.output_semantics, + ) + } + + 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 = 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| { + build_path_ident_expr(symbol.name.as_str(), symbol.span, symbol.span) + }) + .collect::>() + }; + // this is the output whether it is inferred or explicitly defined + // map the output symbols into a return statement, add it to the nodes list, + // and get the entry point return type + let output_types = if is_qiskit { + output + .iter() + .rev() + .filter(|symbol| { + matches!(symbol.ty, crate::semantic::types::Type::BitArray(..)) + }) + .map(|symbol| symbol.qsharp_ty.clone()) + .collect::>() + } else { + output + .iter() + .map(|symbol| symbol.qsharp_ty.clone()) + .collect::>() + }; + + let (output_ty, output_expr) = if output_types.len() == 1 { + (output_types[0].clone(), output_exprs[0].clone()) + } else { + let output_ty = crate::types::Type::Tuple(output_types); + let output_expr = build_tuple_expr(output_exprs); + (output_ty, output_expr) + }; + + let return_stmt = build_implicit_return_stmt(output_expr); + stmts.push(return_stmt); + output_ty + } else { + if is_qiskit { + let kind = SemanticErrorKind::QiskitEntryPointMissingOutput(whole_span); + self.push_semantic_error(kind); + } + crate::types::Type::Tuple(vec![]) + }; + + let ast_ty = map_qsharp_type_to_ast_ty(&output_ty); + signature.output = format!("{output_ty}"); + // TODO: This can create a collision on multiple compiles when interactive + // We also have issues with the new entry point inference logic + let input_desc = input + .iter() + .flat_map(|s| { + s.iter() + .map(|s| (s.name.to_string(), format!("{}", s.qsharp_ty))) + }) + .collect::>(); + signature.input = input_desc; + let input_pats = input + .into_iter() + .flat_map(|s| { + s.into_iter().map(|s| { + build_arg_pat( + s.name.clone(), + s.span, + map_qsharp_type_to_ast_ty(&s.qsharp_ty), + ) + }) + }) + .collect::>(); + + ( + build_operation_with_stmts(name, input_pats, ast_ty, stmts, whole_span), + signature, + ) + } + + /// Prepends the runtime declarations to the beginning of the statements. + /// Any runtime functions that are required by the compiled code are set + /// in the `self.runtime` field during compilation. + /// + /// We could declare these as top level functions when compiling to + /// `ProgramType::File`, but prepending them to the statements is the + /// most flexible approach. + fn prepend_runtime_decls(&mut self) { + let mut runtime = get_runtime_function_decls(self.runtime); + self.stmts.splice(0..0, runtime.drain(..)); + } + + 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 { + 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::CalibrationGrammar(stmt) => { + self.compile_calibration_grammar_stmt(stmt) + } + semast::StmtKind::ClassicalDecl(stmt) => self.compile_classical_decl(stmt), + semast::StmtKind::Def(stmt) => self.compile_def_stmt(stmt), + 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::GPhase(stmt) => self.compile_gphase_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::Measure(stmt) => self.compile_measure_stmt(stmt), + semast::StmtKind::Pragma(stmt) => self.compile_pragma_stmt(stmt), + semast::StmtKind::QuantumGateDefinition(stmt) => self.compile_gate_decl_stmt(stmt), + semast::StmtKind::QuantumDecl(stmt) => self.compile_quantum_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]; + let symbol = symbol.clone(); + let name = &symbol.name; + + let stmt_span = stmt.span; + let name_span = stmt.name_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 { + self.push_unimplemented_error_message("indexed assignment statements", stmt.span); + None + } + + fn compile_assign_op_stmt(&mut self, stmt: &semast::AssignOpStmt) -> Option { + self.push_unimplemented_error_message("assignment op statements", stmt.span); + None + } + + fn compile_barrier_stmt(&mut self, stmt: &semast::BarrierStmt) -> Option { + self.push_unimplemented_error_message("barrier statements", stmt.span); + None + } + + 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_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]; + let symbol = symbol.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 stmt = match expr { + semast::ValueExpression::Expr(expr) => { + let expr = self.compile_expr(expr)?; + build_classical_decl( + name, is_const, ty_span, decl_span, name_span, qsharp_ty, expr, + ) + } + semast::ValueExpression::Measurement(expr) => { + let expr = self.compile_measure_expr(expr)?; + build_stmt_semi_from_expr_with_span(expr, decl.span) + } + }; + + Some(stmt) + } + + fn compile_def_stmt(&mut self, stmt: &semast::DefStmt) -> Option { + self.push_unimplemented_error_message("def statements", stmt.span); + None + } + + 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("dealy statements", stmt.span); + None + } + + fn compile_end_stmt(&mut self, stmt: &semast::EndStmt) -> Option { + self.push_unimplemented_error_message("end statements", stmt.span); + None + } + + 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 { + self.push_unimplemented_error_message("for statements", stmt.span); + None + } + + fn compile_if_stmt(&mut self, stmt: &semast::IfStmt) -> Option { + self.push_unimplemented_error_message("if statements", stmt.span); + None + } + + fn compile_gate_call_stmt(&mut self, stmt: &semast::GateCall) -> Option { + self.push_unimplemented_error_message("gate call statements", stmt.span); + None + } + + fn compile_gphase_stmt(&mut self, stmt: &semast::GPhase) -> Option { + self.push_unimplemented_error_message("gphase statements", stmt.span); + None + } + + 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::MeasureStmt) -> 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, + ) -> Option { + self.push_unimplemented_error_message("gate decl statements", stmt.span); + None + } + + fn compile_quantum_decl_stmt( + &mut self, + stmt: &semast::QubitDeclaration, + ) -> Option { + self.push_unimplemented_error_message("quantum decl statements", stmt.span); + None + } + + fn compile_reset_stmt(&mut self, stmt: &semast::ResetStmt) -> Option { + self.push_unimplemented_error_message("reset statements", stmt.span); + None + } + + fn compile_return_stmt(&mut self, stmt: &semast::ReturnStmt) -> Option { + self.push_unimplemented_error_message("return statements", stmt.span); + None + } + + fn compile_switch_stmt(&mut self, stmt: &semast::SwitchStmt) -> Option { + self.push_unimplemented_error_message("switch statements", stmt.span); + None + } + + fn compile_while_stmt(&mut self, stmt: &semast::WhileLoop) -> Option { + let condition = self.compile_expr(&stmt.while_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) -> Option { + let span = expr.span; + match expr.kind.as_ref() { + semast::ExprKind::Err => { + // todo: determine if we should push an error here + // Are we going to allow trying to compile a program with semantic errors? + None + } + semast::ExprKind::Ident(symbol_id) => self.compile_ident_expr(*symbol_id, span), + semast::ExprKind::IndexedIdentifier(indexed_ident) => { + self.compile_indexed_ident_expr(indexed_ident, span) + } + 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_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), + } + } + + fn compile_ident_expr(&mut self, symbol_id: SymbolId, span: Span) -> Option { + let symbol = &self.symbols[symbol_id]; + let expr = 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), + }; + Some(expr) + } + + /// 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, + span: Span, + ) -> Option { + let index: Vec<_> = indexed_ident + .indices + .iter() + .filter_map(|elem| self.compile_index_element(elem)) + .collect(); + + if index.len() != 1 { + self.push_unimplemented_error_message( + "multi-dimensional array index expressions", + span, + ); + return None; + } + + let symbol = &self.symbols[indexed_ident.symbol_id]; + + let ident = + build_path_ident_expr(&symbol.name, indexed_ident.name_span, indexed_ident.span); + let expr = qsast::Expr { + id: qsast::NodeId::default(), + span, + kind: Box::new(qsast::ExprKind::Index( + Box::new(ident), + Box::new(index[0].clone()), + )), + }; + Some(expr) + } + + fn compile_unary_op_expr(&mut self, unary: &UnaryOpExpr) -> Option { + 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) -> Option { + let expr = self.compile_expr(expr)?; + Some(build_unary_op_expr(qsast::UnOp::Neg, expr, span)) + } + + fn compile_bitwise_not_expr(&mut self, expr: &Expr, span: Span) -> Option { + let expr = self.compile_expr(expr)?; + Some(build_unary_op_expr(qsast::UnOp::NotB, expr, span)) + } + + fn compile_logical_not_expr(&mut self, expr: &Expr, span: Span) -> Option { + let expr = self.compile_expr(expr)?; + Some(build_unary_op_expr(qsast::UnOp::NotL, expr, span)) + } + + fn compile_binary_op_expr(&mut self, binary: &BinaryOpExpr) -> Option { + let lhs = self.compile_expr(&binary.lhs); + let rhs = self.compile_expr(&binary.rhs); + let (lhs, rhs) = (lhs?, rhs?); + let op = Self::map_bin_op(binary.op); + let is_assignment = false; + Some(build_binary_expr( + is_assignment, + op, + lhs, + rhs, + binary.span(), + )) + } + + fn compile_literal_expr(&mut self, lit: &LiteralKind, span: Span) -> Option { + match lit { + LiteralKind::Array(value) => self.compile_array_literal(value, span), + LiteralKind::Bitstring(big_int, width) => { + Self::compile_bitstring_literal(big_int, *width, 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_call_expr(&mut self, call: &FunctionCall) -> Option { + self.push_unimplemented_error_message("function call expresssions", call.span); + None + } + + fn compile_cast_expr(&mut self, cast: &Cast) -> Option { + 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) + } + _ => None, + }; + if cast_expr.is_none() { + 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: &IndexExpr) -> Option { + self.push_unimplemented_error_message("index expressions", index.span); + None + } + + fn compile_paren_expr(&mut self, paren: &Expr, span: Span) -> Option { + let expr = self.compile_expr(paren)?; + Some(wrap_expr_in_parens(expr, span)) + } + + fn compile_measure_expr(&mut self, expr: &MeasureExpr) -> Option { + self.push_unimplemented_error_message("measure expressions", expr.span); + None + } + + fn compile_index_element(&mut self, elem: &IndexElement) -> Option { + 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) -> Option { + self.push_unimplemented_error_message("discrete set expressions", set.span); + None + } + + fn compile_index_set(&mut self, set: &IndexSet) -> Option { + self.push_unimplemented_error_message("index set expressions", set.span); + None + } + + fn compile_array_literal(&mut self, _value: &List, span: Span) -> Option { + self.push_unimplemented_error_message("array literals", span); + None + } + + fn compile_bool_literal(value: bool, span: Span) -> Option { + Some(build_lit_bool_expr(value, span)) + } + + fn compile_duration_literal( + &mut self, + _value: f64, + _unit: TimeUnit, + span: Span, + ) -> Option { + self.push_unsupported_error_message("timing literals", span); + None + } + + fn compile_bitstring_literal(value: &BigInt, width: u32, span: Span) -> Option { + let width = width as usize; + let bitstring = format!("Bitstring(\"{:0>width$}\")", value.to_str_radix(2)); + Some(build_lit_result_array_expr_from_bitstring(bitstring, span)) + } + + fn compile_complex_literal(real: f64, imag: f64, span: Span) -> Option { + Some(build_lit_complex_expr( + crate::types::Complex::new(real, imag), + span, + )) + } + + fn compile_float_literal(value: f64, span: Span) -> Option { + Some(build_lit_double_expr(value, span)) + } + + fn compile_int_literal(value: i64, span: Span) -> Option { + Some(build_lit_int_expr(value, span)) + } + + fn compile_bigint_literal(value: &BigInt, span: Span) -> Option { + Some(build_lit_bigint_expr(value.clone(), span)) + } + + fn compile_string_literal(&mut self, _value: &Rc, span: Span) -> Option { + self.push_unimplemented_error_message("string literal expressions", span); + None + } + + /// 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( + &mut self, + expr: &qsast::Expr, + expr_ty: &crate::semantic::types::Type, + ty: &crate::semantic::types::Type, + _span: Span, + ) -> Option { + assert!(matches!(expr_ty, Type::Angle(..))); + // https://openqasm.com/language/types.html#casting-from-angle + match ty { + Type::Angle(..) => { + let msg = "Cast angle to angle"; + self.push_unimplemented_error_message(msg, expr.span); + None + } + Type::Bit(..) => { + let msg = "Cast angle to bit"; + self.push_unimplemented_error_message(msg, expr.span); + None + } + Type::BitArray(..) => { + let msg = "Cast angle to bit array"; + self.push_unimplemented_error_message(msg, expr.span); + None + } + Type::Bool(..) => { + let msg = "Cast angle to bool"; + self.push_unimplemented_error_message(msg, expr.span); + None + } + + _ => None, + } + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | bit | Yes | Yes | Yes | No | Yes | - | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + fn cast_bit_expr_to_ty( + &mut self, + expr: qsast::Expr, + expr_ty: &crate::semantic::types::Type, + ty: &crate::semantic::types::Type, + span: Span, + ) -> Option { + 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(..) => { + let msg = "Cast bit to angle"; + self.push_unimplemented_error_message(msg, expr.span); + None + } + &Type::Bool(..) => { + self.runtime |= RuntimeFunctions::ResultAsBool; + Some(build_cast_call( + RuntimeFunctions::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. + None + } + &Type::Int(w, _) | &Type::UInt(w, _) => { + let function = if let Some(width) = w { + if width > 64 { + RuntimeFunctions::ResultAsBigInt + } else { + RuntimeFunctions::ResultAsInt + } + } else { + RuntimeFunctions::ResultAsInt + }; + self.runtime |= function; + let expr = build_cast_call(function, expr, name_span, operand_span); + Some(expr) + } + + _ => None, + } + } + + fn cast_bit_array_expr_to_ty( + &mut self, + expr: qsast::Expr, + expr_ty: &crate::semantic::types::Type, + ty: &crate::semantic::types::Type, + size: u32, + span: Span, + ) -> Option { + 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 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(size)) { + self.runtime |= RuntimeFunctions::ResultArrayAsIntBE; + let expr = build_cast_call( + RuntimeFunctions::ResultArrayAsIntBE, + expr, + name_span, + operand_span, + ); + Some(expr) + } else { + 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_ty( + &mut self, + expr: qsast::Expr, + expr_ty: &crate::semantic::types::Type, + ty: &crate::semantic::types::Type, + span: Span, + ) -> Option { + assert!(matches!(expr_ty, Type::Bool(..))); + + let name_span = expr.span; + let operand_span = span; + match ty { + Type::Bit(..) => { + self.runtime |= RuntimeFunctions::BoolAsResult; + let expr = build_cast_call( + RuntimeFunctions::BoolAsResult, + expr, + name_span, + operand_span, + ); + Some(expr) + } + Type::Float(..) => { + self.runtime |= RuntimeFunctions::BoolAsDouble; + let expr = build_cast_call( + RuntimeFunctions::BoolAsDouble, + expr, + name_span, + operand_span, + ); + Some(expr) + } + Type::Int(w, _) | Type::UInt(w, _) => { + let function = if let Some(width) = w { + if *width > 64 { + RuntimeFunctions::BoolAsBigInt + } else { + RuntimeFunctions::BoolAsInt + } + } else { + RuntimeFunctions::BoolAsInt + }; + self.runtime |= function; + let expr = build_cast_call(function, expr, name_span, operand_span); + Some(expr) + } + _ => None, + } + } + + fn cast_complex_expr_to_ty( + &mut self, + _expr: qsast::Expr, + _expr_ty: &crate::semantic::types::Type, + _ty: &crate::semantic::types::Type, + span: Span, + ) -> Option { + self.push_unimplemented_error_message("cast complex expressions", span); + None + } + + fn cast_duration_expr_to_ty( + &mut self, + _expr: qsast::Expr, + _expr_ty: &crate::semantic::types::Type, + _ty: &crate::semantic::types::Type, + span: Span, + ) -> Option { + self.push_unimplemented_error_message("cast duration expressions", span); + 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_ty( + &mut self, + expr: qsast::Expr, + expr_ty: &crate::semantic::types::Type, + ty: &crate::semantic::types::Type, + span: Span, + ) -> Option { + assert!(matches!(expr_ty, Type::Float(..))); + match ty { + &Type::Complex(..) => { + let expr = build_complex_from_expr(expr); + Some(expr) + } + &Type::Angle(..) => { + let msg = "Cast float to angle"; + self.push_unimplemented_error_message(msg, expr.span); + None + } + &Type::Int(w, _) | &Type::UInt(w, _) => { + let expr = build_math_call_from_exprs("Truncate", vec![expr], span); + let expr = if let Some(w) = w { + if w > 64 { + build_convert_call_expr(expr, "IntAsBigInt") + } else { + expr + } + } else { + expr + }; + + Some(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); + let coerce_expr = build_if_expr_then_expr_else_expr( + cond, + build_lit_bool_expr(false, span), + build_lit_bool_expr(true, span), + span, + ); + Some(coerce_expr) + } + _ => None, + } + } + + /// +----------------+-------------------------------------------------------------+ + /// | Allowed casts | Casting To | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | Casting From | bool | int | uint | float | angle | bit | duration | qubit | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | int | Yes | - | Yes | Yes | No | Yes | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// | uint | Yes | Yes | - | Yes | No | Yes | No | No | + /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ + /// + /// Additional cast to ``BigInt`` + /// 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( + &mut self, + expr: qsast::Expr, + expr_ty: &crate::semantic::types::Type, + ty: &crate::semantic::types::Type, + span: Span, + ) -> Option { + assert!(matches!(expr_ty, Type::Int(..) | Type::UInt(..))); + let name_span = expr.span; + let operand_span = span; + match ty { + Type::BitArray(dims, _) => { + self.runtime |= RuntimeFunctions::IntAsResultArrayBE; + let ArrayDimensions::One(size) = dims else { + return None; + }; + let size = i64::from(*size); + + let size_expr = build_lit_int_expr(size, Span::default()); + let expr = build_cast_call_two_params( + RuntimeFunctions::IntAsResultArrayBE, + expr, + size_expr, + name_span, + operand_span, + ); + Some(expr) + } + Type::Float(..) => { + let expr = build_convert_call_expr(expr, "IntAsDouble"); + Some(expr) + } + Type::Int(tw, _) | Type::UInt(tw, _) => { + // uint to int, or int/uint to BigInt + if let Some(tw) = tw { + if *tw > 64 { + let expr = build_convert_call_expr(expr, "IntAsBigInt"); + Some(expr) + } else { + Some(expr) + } + } else { + Some(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); + let coerce_expr = build_if_expr_then_expr_else_expr( + cond, + build_lit_bool_expr(false, expr_span), + build_lit_bool_expr(true, expr_span), + expr_span, + ); + Some(coerce_expr) + } + 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); + let coerce_expr = 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, + ); + Some(coerce_expr) + } + Type::Complex(..) => { + let expr = build_convert_call_expr(expr, "IntAsDouble"); + let expr = build_complex_from_expr(expr); + Some(expr) + } + _ => None, + } + } + + 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/lib.rs b/compiler/qsc_qasm3/src/lib.rs index f046839fb2..a9b4e607d0 100644 --- a/compiler/qsc_qasm3/src/lib.rs +++ b/compiler/qsc_qasm3/src/lib.rs @@ -7,6 +7,7 @@ mod angle; mod ast_builder; mod compile; +mod compiler; pub use compile::qasm_to_program; pub mod io; mod keyword; diff --git a/compiler/qsc_qasm3/src/parser/ast.rs b/compiler/qsc_qasm3/src/parser/ast.rs index 3dea4a2096..eafa672e70 100644 --- a/compiler/qsc_qasm3/src/parser/ast.rs +++ b/compiler/qsc_qasm3/src/parser/ast.rs @@ -326,8 +326,6 @@ pub enum StmtKind { Def(DefStmt), DefCal(DefCalStmt), Delay(DelayStmt), - /// An empty statement. - Empty, End(EndStmt), ExprStmt(ExprStmt), ExternDecl(ExternDecl), @@ -368,7 +366,6 @@ impl Display for StmtKind { StmtKind::Def(def) => write!(f, "{def}"), StmtKind::DefCal(defcal) => write!(f, "{defcal}"), StmtKind::Delay(delay) => write!(f, "{delay}"), - StmtKind::Empty => write!(f, "Empty"), StmtKind::End(end_stmt) => write!(f, "{end_stmt}"), StmtKind::ExprStmt(expr) => write!(f, "{expr}"), StmtKind::ExternDecl(decl) => write!(f, "{decl}"), @@ -529,6 +526,7 @@ impl WithSpan for Ident { #[derive(Clone, Debug)] pub struct IndexedIdent { pub span: Span, + pub index_span: Span, pub name: Ident, pub indices: List, } @@ -537,6 +535,7 @@ 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) } } diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index 6e775e807a..b26b9204f6 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -758,10 +758,16 @@ fn hardware_qubit(s: &mut ParserContext) -> Result { 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, }) diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index 893f9db6fa..8bb39cccfc 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -1129,6 +1129,7 @@ fn indexed_identifier() { &expect![[r#" IndexedIdent [0-9]: name: Ident [0-3] "arr" + index_span: [3-9] indices: IndexSet [4-5]: values: @@ -1159,6 +1160,7 @@ fn measure_indexed_identifier() { MeasureExpr [0-7]: operand: IndexedIdent [8-20]: name: Ident [8-14] "qubits" + index_span: [14-20] indices: IndexSet [15-16]: values: diff --git a/compiler/qsc_qasm3/src/parser/mut_visit.rs b/compiler/qsc_qasm3/src/parser/mut_visit.rs index e25c5985c2..9f76826f03 100644 --- a/compiler/qsc_qasm3/src/parser/mut_visit.rs +++ b/compiler/qsc_qasm3/src/parser/mut_visit.rs @@ -328,7 +328,7 @@ pub fn walk_stmt(vis: &mut impl MutVisitor, stmt: &mut Stmt) { .iter_mut() .for_each(|s| vis.visit_annotation(s)); match &mut *stmt.kind { - StmtKind::Empty | StmtKind::Err => {} + 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), diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs index 4bcd0c5776..7622ada484 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs @@ -17,9 +17,11 @@ fn barrier() { operands: IndexedIdent [8-9]: name: Ident [8-9] "r" + index_span: [0-0] indices: IndexedIdent [11-15]: name: Ident [11-12] "q" + index_span: [12-15] indices: IndexSet [13-14]: values: diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/block.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/block.rs index de70376f6b..06773eacd5 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/block.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/block.rs @@ -19,26 +19,27 @@ fn nested_blocks() { } }", &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" - indices: - rhs: Expr [74-75]: Lit: Int(2)"#]], + 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 index 6972c345af..ac659f9903 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs @@ -30,6 +30,7 @@ fn box_stmt() { qubits: IndexedIdent [21-23]: name: Ident [21-23] "q0" + index_span: [0-0] indices: Stmt [33-44]: annotations: @@ -42,6 +43,7 @@ fn box_stmt() { qubits: IndexedIdent [41-43]: name: Ident [41-43] "q1" + index_span: [0-0] indices: "#]], ); } @@ -71,6 +73,7 @@ fn box_stmt_with_designator() { qubits: IndexedIdent [26-28]: name: Ident [26-28] "q0" + index_span: [0-0] indices: Stmt [38-49]: annotations: @@ -83,6 +86,7 @@ fn box_stmt_with_designator() { qubits: IndexedIdent [46-48]: name: Ident [46-48] "q1" + index_span: [0-0] indices: "#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs index bdbe7580d9..c71849d8cd 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs @@ -1091,6 +1091,7 @@ fn measure_register_decl() { init_expr: MeasureExpr [10-17]: operand: IndexedIdent [18-30]: name: Ident [18-24] "qubits" + index_span: [24-30] indices: IndexSet [25-26]: values: diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs index 2fb62a4e1f..174a38ed06 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs @@ -18,12 +18,14 @@ fn delay() { qubits: IndexedIdent [9-13]: name: Ident [9-10] "q" + index_span: [10-13] indices: IndexSet [11-12]: values: Expr [11-12]: Lit: Int(0) IndexedIdent [15-19]: name: Ident [15-16] "q" + index_span: [16-19] indices: IndexSet [17-18]: values: diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs index 5fd49612b6..bfad136fb6 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/expr_stmt.rs @@ -45,6 +45,7 @@ fn assignment() { 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)"#]], ); @@ -61,6 +62,7 @@ fn index_assignment() { kind: AssignStmt [0-9]: lhs: IndexedIdent [0-4]: name: Ident [0-1] "a" + index_span: [1-4] indices: IndexSet [2-3]: values: @@ -80,6 +82,7 @@ fn multi_index_assignment() { kind: AssignStmt [0-12]: lhs: IndexedIdent [0-7]: name: Ident [0-1] "a" + index_span: [1-7] indices: IndexSet [2-3]: values: @@ -103,6 +106,7 @@ fn assignment_op() { op: Add lhs: IndexedIdent [0-1]: name: Ident [0-1] "a" + index_span: [0-0] indices: rhs: Expr [5-6]: Lit: Int(1)"#]], ); @@ -120,6 +124,7 @@ fn index_assignment_op() { op: Add lhs: IndexedIdent [0-4]: name: Ident [0-1] "a" + index_span: [1-4] indices: IndexSet [2-3]: values: @@ -140,6 +145,7 @@ fn multi_index_assignment_op() { op: Add lhs: IndexedIdent [0-7]: name: Ident [0-1] "a" + index_span: [1-7] indices: IndexSet [2-3]: values: @@ -162,6 +168,7 @@ fn assignment_and_unop() { 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 @@ -183,6 +190,7 @@ fn assignment_unop_and() { 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 diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs index c8d5517bcc..eabf39e37b 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs @@ -32,6 +32,7 @@ fn simple_for_stmt() { 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)"#]], ); @@ -82,6 +83,7 @@ fn simple_for_stmt_stmt_body() { 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)"#]], ); @@ -114,6 +116,7 @@ fn for_stmt_iterating_over_range() { 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)"#]], ); @@ -146,6 +149,7 @@ fn for_stmt_iterating_over_range_no_step() { 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)"#]], ); @@ -175,6 +179,7 @@ fn for_stmt_iterating_over_expr() { 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)"#]], ); @@ -209,6 +214,7 @@ fn for_stmt_with_continue_stmt() { 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]: @@ -246,6 +252,7 @@ fn for_loop_with_break_stmt() { 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]: @@ -278,6 +285,7 @@ fn single_stmt_for_stmt() { qubits: IndexedIdent [18-19]: name: Ident [18-19] "q" + index_span: [0-0] indices: "#]], ); } @@ -311,6 +319,7 @@ fn annotations_in_single_stmt_for_stmt() { 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)"#]], ); @@ -348,6 +357,7 @@ fn nested_single_stmt_for_stmt() { qubits: IndexedIdent [34-35]: name: Ident [34-35] "q" + index_span: [0-0] indices: "#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs index 78de9c02c0..e59d1e46e7 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs @@ -21,6 +21,7 @@ fn gate_call() { qubits: IndexedIdent [2-4]: name: Ident [2-4] "q0" + index_span: [0-0] indices: "#]], ); } @@ -41,6 +42,7 @@ fn gate_call_qubit_register() { qubits: IndexedIdent [2-6]: name: Ident [2-3] "q" + index_span: [3-6] indices: IndexSet [4-5]: values: @@ -64,9 +66,11 @@ fn gate_multiple_qubits() { qubits: IndexedIdent [5-7]: name: Ident [5-7] "q0" + index_span: [0-0] indices: IndexedIdent [9-13]: name: Ident [9-10] "q" + index_span: [10-13] indices: IndexSet [11-12]: values: @@ -123,6 +127,7 @@ fn gate_call_with_parameters() { qubits: IndexedIdent [11-13]: name: Ident [11-13] "q0" + index_span: [0-0] indices: "#]], ); } @@ -144,6 +149,7 @@ fn gate_call_inv_modifier() { qubits: IndexedIdent [8-10]: name: Ident [8-10] "q0" + index_span: [0-0] indices: "#]], ); } @@ -170,12 +176,15 @@ fn gate_call_ctrl_inv_modifiers() { qubits: IndexedIdent [27-29]: name: Ident [27-29] "c1" + index_span: [0-0] indices: IndexedIdent [31-33]: name: Ident [31-33] "c2" + index_span: [0-0] indices: IndexedIdent [35-37]: name: Ident [35-37] "q0" + index_span: [0-0] indices: "#]], ); } @@ -217,6 +226,7 @@ fn parametrized_gate_call() { qubits: IndexedIdent [11-12]: name: Ident [11-12] "q" + index_span: [0-0] indices: "#]], ); } @@ -239,6 +249,7 @@ fn parametrized_gate_call_with_designator() { qubits: IndexedIdent [14-15]: name: Ident [14-15] "q" + index_span: [0-0] indices: "#]], ); } @@ -277,6 +288,7 @@ fn gate_call_with_designator() { qubits: IndexedIdent [7-8]: name: Ident [7-8] "q" + index_span: [0-0] indices: "#]], ); } @@ -297,6 +309,7 @@ fn gate_call_with_invalid_designator() { qubits: IndexedIdent [10-11]: name: Ident [10-11] "q" + index_span: [0-0] indices: [ diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs index 6b3278918d..be32d2fc52 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs @@ -41,6 +41,7 @@ fn gphase_qubit_ident() { qubits: IndexedIdent [10-12]: name: Ident [10-12] "q0" + index_span: [0-0] indices: "#]], ); } @@ -61,6 +62,7 @@ fn gphase_qubit_register() { qubits: IndexedIdent [10-14]: name: Ident [10-11] "q" + index_span: [11-14] indices: IndexSet [12-13]: values: @@ -84,9 +86,11 @@ fn gphase_multiple_qubits() { qubits: IndexedIdent [10-12]: name: Ident [10-12] "q0" + index_span: [0-0] indices: IndexedIdent [14-18]: name: Ident [14-15] "q" + index_span: [15-18] indices: IndexSet [16-17]: values: @@ -180,6 +184,7 @@ fn gphase_ctrl_inv_modifiers() { qubits: 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 index f74dbc053b..e8ceb04a91 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs @@ -31,6 +31,7 @@ fn simple_if_stmt() { 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]: @@ -41,6 +42,7 @@ fn simple_if_stmt() { 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)"#]], ); @@ -71,6 +73,7 @@ fn if_stmt_missing_else() { 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: "#]], @@ -122,6 +125,7 @@ fn nested_if_stmts() { 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]: @@ -132,6 +136,7 @@ fn nested_if_stmts() { 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]: @@ -152,6 +157,7 @@ fn nested_if_stmts() { 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]: @@ -162,6 +168,7 @@ fn nested_if_stmts() { 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)"#]], ); @@ -187,6 +194,7 @@ fn single_stmt_if_stmt() { qubits: IndexedIdent [9-10]: name: Ident [9-10] "q" + index_span: [0-0] indices: else_body: "#]], ); @@ -217,6 +225,7 @@ fn annotations_in_single_stmt_if_stmt() { 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: "#]], @@ -247,6 +256,7 @@ fn nested_single_stmt_if_stmt() { qubits: IndexedIdent [16-17]: name: Ident [16-17] "q" + index_span: [0-0] indices: else_body: else_body: "#]], @@ -277,6 +287,7 @@ fn nested_single_stmt_if_else_stmt() { qubits: IndexedIdent [16-17]: name: Ident [16-17] "q" + index_span: [0-0] indices: else_body: Stmt [24-42]: annotations: @@ -296,6 +307,7 @@ fn nested_single_stmt_if_else_stmt() { qubits: IndexedIdent [40-41]: name: Ident [40-41] "q" + index_span: [0-0] indices: else_body: else_body: @@ -323,6 +335,7 @@ fn single_stmt_if_stmt_else_stmt() { qubits: IndexedIdent [9-10]: name: Ident [9-10] "q" + index_span: [0-0] indices: else_body: Stmt [17-21]: annotations: @@ -334,6 +347,7 @@ fn single_stmt_if_stmt_else_stmt() { qubits: IndexedIdent [19-20]: name: Ident [19-20] "q" + index_span: [0-0] indices: "#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs index dbf71b9d03..e58bf22e36 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs @@ -17,6 +17,7 @@ fn measure_identifier() { measurement: MeasureExpr [0-7]: operand: IndexedIdent [8-9]: name: Ident [8-9] "q" + index_span: [0-0] indices: target: "#]], ); @@ -34,6 +35,7 @@ fn measure_indented_ident() { measurement: MeasureExpr [0-7]: operand: IndexedIdent [8-12]: name: Ident [8-9] "q" + index_span: [9-12] indices: IndexSet [10-11]: values: @@ -69,9 +71,11 @@ fn measure_arrow_into_ident() { measurement: MeasureExpr [0-7]: operand: 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: "#]], ); } @@ -88,9 +92,11 @@ fn measure_arrow_into_indented_ident() { measurement: MeasureExpr [0-7]: operand: 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: diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs index 0b144e90b4..fd56798242 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs @@ -16,6 +16,7 @@ fn reset_ident() { kind: ResetStmt [0-8]: operand: IndexedIdent [6-7]: name: Ident [6-7] "a" + index_span: [0-0] indices: "#]], ); } @@ -31,6 +32,7 @@ fn reset_indexed_ident() { kind: ResetStmt [0-11]: operand: IndexedIdent [6-10]: name: Ident [6-7] "a" + index_span: [7-10] indices: IndexSet [8-9]: values: diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs index 2f54709b85..1f44485846 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs @@ -28,6 +28,7 @@ fn simple_while() { 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)"#]], ); @@ -69,6 +70,7 @@ fn while_stmt_body() { 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)"#]], ); @@ -99,6 +101,7 @@ fn while_loop_with_continue_stmt() { 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]: @@ -132,6 +135,7 @@ fn while_loop_with_break_stmt() { 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]: @@ -160,6 +164,7 @@ fn single_stmt_while_stmt() { qubits: IndexedIdent [12-13]: name: Ident [12-13] "q" + index_span: [0-0] indices: "#]], ); } @@ -189,6 +194,7 @@ fn annotations_in_single_stmt_while_stmt() { 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)"#]], ); @@ -218,6 +224,7 @@ fn nested_single_stmt_while_stmt() { qubits: IndexedIdent [22-23]: name: Ident [22-23] "q" + index_span: [0-0] indices: "#]], ); } diff --git a/compiler/qsc_qasm3/src/semantic.rs b/compiler/qsc_qasm3/src/semantic.rs index 1682b3a953..b63db4daa0 100644 --- a/compiler/qsc_qasm3/src/semantic.rs +++ b/compiler/qsc_qasm3/src/semantic.rs @@ -46,7 +46,7 @@ impl QasmSemanticParseResult { !self.errors.is_empty() } - pub fn parse_errors(&self) -> Vec> { + pub fn sytax_errors(&self) -> Vec> { let mut self_errors = self .source .errors() @@ -72,7 +72,7 @@ impl QasmSemanticParseResult { #[must_use] pub fn all_errors(&self) -> Vec> { - let mut parse_errors = self.parse_errors(); + let mut parse_errors = self.sytax_errors(); let sem_errors = self.semantic_errors(); parse_errors.extend(sem_errors); parse_errors @@ -106,7 +106,7 @@ where R: SourceResolver, { let res = crate::parser::parse_source(source, path, resolver)?; - + let errors = res.all_errors(); // If there are syntax errors, return early if res.source.has_errors() { return Ok(QasmSemanticParseResult { @@ -114,7 +114,7 @@ where source_map: res.source_map, symbols: SymbolTable::default(), program: None, - errors: vec![], + errors, }); } diff --git a/compiler/qsc_qasm3/src/semantic/ast.rs b/compiler/qsc_qasm3/src/semantic/ast.rs index 9e004b5715..0382d51406 100644 --- a/compiler/qsc_qasm3/src/semantic/ast.rs +++ b/compiler/qsc_qasm3/src/semantic/ast.rs @@ -343,8 +343,6 @@ pub enum StmtKind { Def(DefStmt), DefCal(DefCalStmt), Delay(DelayStmt), - /// An empty statement. - Empty, End(EndStmt), ExprStmt(ExprStmt), ExternDecl(ExternDecl), @@ -353,7 +351,8 @@ pub enum StmtKind { GateCall(GateCall), GPhase(GPhase), Include(IncludeStmt), - IODeclaration(IODeclaration), + InputDeclaration(InputDeclaration), + OutputDeclaration(OutputDeclaration), Measure(MeasureStmt), Pragma(Pragma), QuantumGateDefinition(QuantumGateDefinition), @@ -381,7 +380,6 @@ impl Display for StmtKind { StmtKind::Def(def) => write!(f, "{def}"), StmtKind::DefCal(defcal) => write!(f, "{defcal}"), StmtKind::Delay(delay) => write!(f, "{delay}"), - StmtKind::Empty => write!(f, "Empty"), StmtKind::End(end_stmt) => write!(f, "{end_stmt}"), StmtKind::ExprStmt(expr) => write!(f, "{expr}"), StmtKind::ExternDecl(decl) => write!(f, "{decl}"), @@ -391,7 +389,8 @@ impl Display for StmtKind { StmtKind::If(if_stmt) => write!(f, "{if_stmt}"), StmtKind::Include(include) => write!(f, "{include}"), StmtKind::IndexedAssign(assign) => write!(f, "{assign}"), - StmtKind::IODeclaration(io) => write!(f, "{io}"), + StmtKind::InputDeclaration(io) => write!(f, "{io}"), + StmtKind::OutputDeclaration(io) => write!(f, "{io}"), StmtKind::Measure(measure) => write!(f, "{measure}"), StmtKind::Pragma(pragma) => write!(f, "{pragma}"), StmtKind::QuantumGateDefinition(gate) => write!(f, "{gate}"), @@ -533,6 +532,8 @@ impl WithSpan for Ident { #[derive(Clone, Debug)] pub struct IndexedIdent { pub span: Span, + pub name_span: Span, + pub index_span: Span, pub symbol_id: SymbolId, pub indices: List, } @@ -541,6 +542,8 @@ 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) } } @@ -1164,16 +1167,31 @@ impl Display for ValueExpression { } #[derive(Clone, Debug)] -pub struct IODeclaration { +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 IODeclaration { +impl Display for OutputDeclaration { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - writeln_header(f, "IODeclaration", self.span)?; + 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) } } @@ -1432,14 +1450,16 @@ impl Display for ExprKind { #[derive(Clone, Debug)] pub struct AssignStmt { pub span: Span, - pub symbold_id: SymbolId, + pub name_span: Span, + pub symbol_id: SymbolId, 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.symbold_id)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + writeln_field(f, "name_span", &self.name_span)?; write_field(f, "rhs", &self.rhs) } } @@ -1447,7 +1467,7 @@ impl Display for AssignStmt { #[derive(Clone, Debug)] pub struct IndexedAssignStmt { pub span: Span, - pub symbold_id: SymbolId, + pub symbol_id: SymbolId, pub lhs: Expr, pub rhs: Expr, } @@ -1455,7 +1475,7 @@ pub struct IndexedAssignStmt { impl Display for IndexedAssignStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln_header(f, "AssignStmt", self.span)?; - writeln_field(f, "symbol_id", &self.symbold_id)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; writeln_field(f, "lhs", &self.rhs)?; write_field(f, "rhs", &self.rhs) } @@ -1464,7 +1484,8 @@ impl Display for IndexedAssignStmt { #[derive(Clone, Debug)] pub struct AssignOpStmt { pub span: Span, - pub symbold_id: SymbolId, + pub symbol_id: SymbolId, + pub op: BinOp, pub lhs: Expr, pub rhs: Expr, } @@ -1472,7 +1493,8 @@ pub struct AssignOpStmt { impl Display for AssignOpStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln_header(f, "AssignOpStmt", self.span)?; - writeln_field(f, "symbol_id", &self.symbold_id)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; + writeln_field(f, "op", &self.op)?; writeln_field(f, "lhs", &self.rhs)?; write_field(f, "rhs", &self.rhs) } @@ -1480,13 +1502,14 @@ impl Display for AssignOpStmt { #[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!(f, "UnaryOpExpr:")?; + writeln_header(f, "UnaryOpExpr", self.span)?; writeln_field(f, "op", &self.op)?; write_field(f, "expr", &self.expr) } @@ -1662,6 +1685,7 @@ impl Display for IndexElement { pub enum IndexSetItem { RangeDefinition(RangeDefinition), Expr(Expr), + Err, } /// This is needed to able to use `IndexSetItem` in the `seq` combinator. @@ -1672,6 +1696,7 @@ impl WithSpan for IndexSetItem { IndexSetItem::RangeDefinition(range.with_span(span)) } IndexSetItem::Expr(expr) => IndexSetItem::Expr(expr.with_span(span)), + IndexSetItem::Err => IndexSetItem::Err, } } } @@ -1681,6 +1706,7 @@ impl Display for IndexSetItem { match self { IndexSetItem::RangeDefinition(range) => write!(f, "{range}"), IndexSetItem::Expr(expr) => write!(f, "{expr}"), + IndexSetItem::Err => write!(f, "Err"), } } } @@ -1700,7 +1726,7 @@ impl Display for IOKeyword { } } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub enum TimeUnit { Dt, /// Nanoseconds. diff --git a/compiler/qsc_qasm3/src/semantic/error.rs b/compiler/qsc_qasm3/src/semantic/error.rs index b520e6cfe4..02df4074f1 100644 --- a/compiler/qsc_qasm3/src/semantic/error.rs +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -50,6 +50,9 @@ pub enum SemanticErrorKind { #[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), @@ -221,6 +224,9 @@ impl SemanticErrorKind { 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) } diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index 51fff75839..ed887adcca 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -36,53 +36,25 @@ use super::{ SemanticErrorKind, }; -/// This macro allow us to apply the `?` to the inner option -/// in a situation where we have two nested options. This -/// situation is common when lowering items that were originally -/// optional. -/// -/// Example usage: -/// ```ignore -/// let item: Option = ...; -/// let item: Option> = item.as_ref().map(|s| self.lower_stmt(s)); -/// -/// // lower some other items in between -/// // ... -/// -/// // We short-circuit after lowering all the items to -/// // catch as many errors as possible before returning. -/// -/// // Note that here we are applying the `?` operator to -/// // the inner option, and not to the outer one. That is, -/// // because the outer option being `None` is not necessarily -/// // an error, e.g.: the else-body of an if_stmt. -/// // But the inner option being `None` is always an error, -/// // which occured during lowering. -/// let item: Option = short_circuit_opt_item!(item); -/// ``` -macro_rules! short_circuit_opt_item { - ($nested_opt:expr) => { - if let Some(inner_opt) = $nested_opt { - Some(inner_opt?) - } else { - None +/// 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, } }; -} -/// This helper function evaluates the contents of `f` within a scope -/// of kind `kind`. It's purpose is to avoid making the mistake of -/// returning early from a function while a scope is pushed, for example, -/// by using the `?` operator. -#[must_use] -fn with_scope(lw: &mut Lowerer, kind: ScopeKind, f: F) -> Option -where - F: FnOnce(&mut Lowerer) -> Option, -{ - lw.symbols.push_scope(kind); - let res = f(lw); - lw.symbols.pop_scope(); - res + ($ty:expr, $span:expr) => { + semantic::Expr { + span: $span, + kind: Box::new(semantic::ExprKind::Err), + ty: $ty, + } + }; } pub(super) struct Lowerer { @@ -122,6 +94,11 @@ impl Lowerer { 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), @@ -169,121 +146,79 @@ impl Lowerer { let mut includes = source.includes().iter(); for stmt in &source.program().statements { - match &*stmt.kind { - syntax::StmtKind::Include(include) => { - // 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); + 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; } - _ => { - if let Some(stmt) = self.lower_stmt(stmt) { - self.stmts.push(stmt); - } + + // 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) -> Option { + fn lower_stmt(&mut self, stmt: &syntax::Stmt) -> semantic::Stmt { let kind = match &*stmt.kind { - syntax::StmtKind::Alias(stmt) => semantic::StmtKind::Alias(self.lower_alias(stmt)?), - syntax::StmtKind::Assign(stmt) => self.lower_assign(stmt)?, - syntax::StmtKind::AssignOp(stmt) => { - semantic::StmtKind::AssignOp(self.lower_assign_op(stmt)?) - } - syntax::StmtKind::Barrier(stmt) => { - semantic::StmtKind::Barrier(self.lower_barrier(stmt)?) - } - syntax::StmtKind::Box(stmt) => semantic::StmtKind::Box(self.lower_box(stmt)?), - syntax::StmtKind::Break(stmt) => self.lower_break(stmt)?, + 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), + 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) => { - semantic::StmtKind::CalibrationGrammar(self.lower_calibration_grammar(stmt)?) - } - syntax::StmtKind::ClassicalDecl(stmt) => { - semantic::StmtKind::ClassicalDecl(self.lower_classical_decl(stmt)?) - } - syntax::StmtKind::ConstDecl(stmt) => { - semantic::StmtKind::ClassicalDecl(self.lower_const_decl(stmt)?) - } - syntax::StmtKind::Continue(stmt) => self.lower_continue_stmt(stmt)?, - syntax::StmtKind::Def(stmt) => semantic::StmtKind::Def(self.lower_def(stmt)?), - syntax::StmtKind::DefCal(stmt) => semantic::StmtKind::DefCal(self.lower_def_cal(stmt)?), - syntax::StmtKind::Delay(stmt) => semantic::StmtKind::Delay(self.lower_delay(stmt)?), - syntax::StmtKind::Empty => { - // we ignore empty statements - None? - } - syntax::StmtKind::End(stmt) => semantic::StmtKind::End(self.lower_end_stmt(stmt)?), - syntax::StmtKind::ExprStmt(stmt) => { - semantic::StmtKind::ExprStmt(self.lower_expr_stmt(stmt)?) - } - syntax::StmtKind::ExternDecl(extern_decl) => { - semantic::StmtKind::ExternDecl(self.lower_extern(extern_decl)?) - } - syntax::StmtKind::For(stmt) => semantic::StmtKind::For(self.lower_for_stmt(stmt)?), - syntax::StmtKind::If(stmt) => semantic::StmtKind::If(self.lower_if_stmt(stmt)?), - syntax::StmtKind::GateCall(stmt) => { - semantic::StmtKind::GateCall(self.lower_gate_call(stmt)?) - } - syntax::StmtKind::GPhase(stmt) => semantic::StmtKind::GPhase(self.lower_gphase(stmt)?), - syntax::StmtKind::Include(stmt) => { - semantic::StmtKind::Include(self.lower_include(stmt)?) - } - syntax::StmtKind::IODeclaration(stmt) => { - semantic::StmtKind::IODeclaration(self.lower_io_decl(stmt)?) - } - syntax::StmtKind::Measure(stmt) => { - semantic::StmtKind::Measure(self.lower_measure(stmt)?) - } - syntax::StmtKind::Pragma(stmt) => semantic::StmtKind::Pragma(self.lower_pragma(stmt)?), - syntax::StmtKind::QuantumGateDefinition(stmt) => { - semantic::StmtKind::QuantumGateDefinition(self.lower_gate_def(stmt)?) - } - syntax::StmtKind::QuantumDecl(stmt) => { - semantic::StmtKind::QuantumDecl(self.lower_quantum_decl(stmt)?) - } - syntax::StmtKind::Reset(stmt) => semantic::StmtKind::Reset(self.lower_reset(stmt)?), - syntax::StmtKind::Return(stmt) => semantic::StmtKind::Return(self.lower_return(stmt)?), - syntax::StmtKind::Switch(stmt) => semantic::StmtKind::Switch(self.lower_switch(stmt)?), - syntax::StmtKind::WhileLoop(stmt) => { - semantic::StmtKind::WhileLoop(self.lower_while_stmt(stmt)?) - } - syntax::StmtKind::Err => { - self.push_semantic_error(SemanticErrorKind::UnexpectedParserError( - "Unexpected error".to_string(), - stmt.span, - )); - return None; - } + 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), + syntax::StmtKind::GPhase(stmt) => self.lower_gphase(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, &stmt.kind); - Some(semantic::Stmt { + semantic::Stmt { span: stmt.span, annotations: syntax::list_from_iter(annotations), kind: Box::new(kind), - }) + } } /// Define the standard gates in the symbol table. @@ -326,58 +261,47 @@ impl Lowerer { } } - /// 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>( + fn try_insert_or_get_existing_symbol_id( &mut self, - message: S, - minimum_supported_version: &Version, + name: S, + symbol: Symbol, 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); + ) -> super::symbols::SymbolId + where + S: AsRef, + { + 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(), span); + symbol_id + } + }; + symbol_id } - /// 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 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) -> Option { + 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. @@ -385,22 +309,19 @@ impl Lowerer { let rhs = alias .exprs .iter() - .filter_map(|expr| self.lower_expr(expr)) + .map(|expr| self.lower_expr(expr)) .collect::>(); let first = rhs.first().expect("missing rhs"); let symbol = Symbol { name: name.to_string(), ty: first.ty.clone(), - qsharp_ty: self.convert_semantic_type_to_qsharp_type(&first.ty, alias.ident.span())?, + qsharp_ty: self.convert_semantic_type_to_qsharp_type(&first.ty, alias.ident.span()), span: alias.ident.span(), io_kind: IOKind::Default, }; - let symbol_id = self.symbols.insert_symbol(symbol); - if symbol_id.is_err() { - self.push_redefined_symbol_error(name, alias.ident.span()); - } + let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol, alias.ident.span()); if rhs.iter().any(|expr| expr.ty != first.ty) { let tys = rhs @@ -410,33 +331,20 @@ impl Lowerer { .join(", "); let kind = SemanticErrorKind::InconsistentTypesInAlias(tys, alias.span); self.push_semantic_error(kind); - return None; } - let symbol_id = symbol_id.ok()?; - - if rhs.len() != alias.exprs.len() { - // we failed - return None; - } - Some(semantic::AliasDeclStmt { + semantic::StmtKind::Alias(semantic::AliasDeclStmt { span: alias.span, symbol_id, exprs: syntax::list_from_iter(rhs), }) } - fn lower_assign(&mut self, stmt: &syntax::AssignStmt) -> Option { + fn lower_assign(&mut self, stmt: &syntax::AssignStmt) -> semantic::StmtKind { if stmt.lhs.indices.is_empty() { - Some(semantic::StmtKind::Assign(self.lower_simple_assign_expr( - &stmt.lhs.name, - &stmt.rhs, - stmt.span, - )?)) + self.lower_simple_assign_expr(&stmt.lhs.name, &stmt.rhs, stmt.span) } else { - Some(semantic::StmtKind::IndexedAssign( - self.lower_indexed_assign_expr(&stmt.lhs, &stmt.rhs, stmt.span)?, - )) + self.lower_indexed_assign_expr(&stmt.lhs, &stmt.rhs, stmt.span) } } @@ -445,20 +353,22 @@ impl Lowerer { ident: &syntax::Ident, rhs: &syntax::Expr, span: Span, - ) -> Option { - let (lhs_symbol_id, lhs_symbol) = self.symbols.get_symbol_by_name(&ident.name)?; - let ty = lhs_symbol.ty.clone(); + ) -> 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 = self.lower_expr_with_target_type(Some(rhs), &ty, span); if ty.is_const() { let kind = SemanticErrorKind::CannotUpdateConstVariable(ident.name.to_string(), ident.span); self.push_semantic_error(kind); - return None; + return semantic::StmtKind::Err; } - let rhs = rhs?; - Some(semantic::AssignStmt { - symbold_id: lhs_symbol_id, + semantic::StmtKind::Assign(semantic::AssignStmt { + symbol_id, + name_span: ident.span, rhs, span, }) @@ -469,142 +379,137 @@ impl Lowerer { index_expr: &syntax::IndexedIdent, rhs: &syntax::Expr, span: Span, - ) -> Option { + ) -> semantic::StmtKind { let ident = index_expr.name.clone(); - let (lhs_symbol_id, lhs_symbol) = self.symbols.get_symbol_by_name(&ident.name)?; - let ty = lhs_symbol.ty.clone(); + let (symbol_id, symbol) = + self.try_get_existing_or_insert_err_symbol(&ident.name, ident.span); + + let ty = &symbol.ty; let lhs = self.lower_indexed_ident_expr(index_expr); - let rhs = self.lower_expr_with_target_type(Some(rhs), &ty, span); + let rhs = self.lower_expr_with_target_type(Some(rhs), ty, span); if ty.is_const() { let kind = SemanticErrorKind::CannotUpdateConstVariable(ident.name.to_string(), ident.span); self.push_semantic_error(kind); - return None; } - let (lhs, rhs) = (lhs?, rhs?); - - Some(semantic::IndexedAssignStmt { - symbold_id: lhs_symbol_id, + semantic::StmtKind::IndexedAssign(semantic::IndexedAssignStmt { + span, + symbol_id, lhs, rhs, - span, }) } - fn lower_assign_op(&mut self, stmt: &syntax::AssignOpStmt) -> Option { - let op = stmt.op; + 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 (lhs_symbol_id, lhs_symbol) = self.symbols.get_symbol_by_name(&ident.name)?; - let ty = lhs_symbol.ty.clone(); + + let (symbol_id, symbol) = + self.try_get_existing_or_insert_err_symbol(&ident.name, ident.span); + + let ty = &symbol.ty; if ty.is_const() { let kind = SemanticErrorKind::CannotUpdateConstVariable(ident.name.to_string(), ident.span); self.push_semantic_error(kind); - // usually we'd return None here, but we'll continue to compile the rhs - // looking for more errors. There is nothing in this type of error that - // would prevent us from compiling the rhs. - return None; } let lhs = self.lower_indexed_ident_expr(lhs); - let rhs = self.lower_expr(rhs); - let (lhs, rhs) = (lhs?, rhs?); - let rhs = self.lower_binary_op_expr(op, lhs.clone(), rhs, stmt.span)?; - let rhs = self.cast_expr_to_type(&ty, &rhs, stmt.span)?; - Some(semantic::AssignOpStmt { + let rhs = self.cast_expr_to_type(ty, &rhs, stmt.span); + + semantic::StmtKind::AssignOp(semantic::AssignOpStmt { span: stmt.span, - symbold_id: lhs_symbol_id, + symbol_id, + op, lhs, rhs, }) } - fn lower_expr(&mut self, expr: &syntax::Expr) -> Option { + 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); - // once we've compiled both sides, we can check if they failed - // and return None if they did so that the error is propagated - let (lhs, rhs) = (lhs?, 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); - None - } - syntax::ExprKind::Err => { - unreachable!("Err expr should not be lowered"); + err_expr!(Type::Err, expr.span) } + syntax::ExprKind::Err => err_expr!(Type::Err, expr.span), syntax::ExprKind::FunctionCall(_) => { self.push_unimplemented_error_message("function call expr", expr.span); - None + err_expr!(Type::Err, expr.span) } 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(expr) => self.lower_paren_expr(expr), + 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) -> Option { + fn lower_ident_expr(&mut self, ident: &syntax::Ident) -> semantic::Expr { let name = ident.name.clone(); - let Some((symbol_id, symbol)) = self.symbols.get_symbol_by_name(&name) else { - self.push_missing_symbol_error(&name, ident.span); - return None; - }; + + let (symbol_id, symbol) = self.try_get_existing_or_insert_err_symbol(&name, ident.span); let kind = semantic::ExprKind::Ident(symbol_id); - Some(semantic::Expr { + semantic::Expr { span: ident.span, kind: Box::new(kind), ty: symbol.ty.clone(), - }) + } } - fn lower_lit_expr(&mut self, expr: &syntax::Lit) -> Option { + fn lower_lit_expr(&mut self, expr: &syntax::Lit) -> semantic::Expr { let (kind, ty) = match &expr.kind { syntax::LiteralKind::BigInt(value) => { - // todo: this case is only valid when there is an integer literal + // 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::LiteralKind::BigInt(value.clone()), Type::Err) + ( + semantic::ExprKind::Lit(semantic::LiteralKind::BigInt(value.clone())), + Type::Err, + ) } syntax::LiteralKind::Bitstring(value, size) => ( - semantic::LiteralKind::Bitstring(value.clone(), *size), + semantic::ExprKind::Lit(semantic::LiteralKind::Bitstring(value.clone(), *size)), Type::BitArray(super::types::ArrayDimensions::One(*size), true), ), - syntax::LiteralKind::Bool(value) => { - (semantic::LiteralKind::Bool(*value), Type::Bool(true)) - } - syntax::LiteralKind::Int(value) => { - (semantic::LiteralKind::Int(*value), Type::Int(None, 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::LiteralKind::Float(*value), + semantic::ExprKind::Lit(semantic::LiteralKind::Float(*value)), Type::Float(None, true), ), syntax::LiteralKind::Imaginary(value) => ( - semantic::LiteralKind::Complex(0.0, *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); - return None; + (semantic::ExprKind::Err, Type::Err) } syntax::LiteralKind::Duration(_, _) => { self.push_unsupported_error_message("Duration literals", expr.span); - return None; + (semantic::ExprKind::Err, Type::Err) } syntax::LiteralKind::Array(exprs) => { // array literals are only valid in classical decals (const and mut) @@ -617,88 +522,58 @@ impl Lowerer { // add support for classical decls let texprs = exprs .iter() - .filter_map(|expr| self.lower_expr(expr)) + .map(|expr| self.lower_expr(expr)) .collect::>(); - if texprs.len() != exprs.len() { - // we failed to lower all the entries and an error was pushed - return None; - } ( - semantic::LiteralKind::Array(syntax::list_from_iter(texprs)), + semantic::ExprKind::Lit(semantic::LiteralKind::Array(syntax::list_from_iter( + texprs, + ))), Type::Err, ) } }; - Some(semantic::Expr { + semantic::Expr { span: expr.span, - kind: Box::new(semantic::ExprKind::Lit(kind)), + kind: Box::new(kind), ty, - }) + } } - fn lower_paren_expr(&mut self, expr: &syntax::Expr) -> Option { - let expr = self.lower_expr(expr)?; - let span = expr.span; + 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); - Some(semantic::Expr { + semantic::Expr { span, kind: Box::new(kind), ty, - }) + } } - fn lower_unary_op_expr(&mut self, expr: &syntax::UnaryOpExpr) -> Option { + fn lower_unary_op_expr(&mut self, expr: &syntax::UnaryOpExpr) -> semantic::Expr { match expr.op { - syntax::UnaryOp::Neg => { - if let syntax::ExprKind::Lit(lit) = expr.expr.kind.as_ref() { - self.lower_negated_literal_as_ty(lit, None, expr.expr.span) - } else { - let expr = self.lower_expr(&expr.expr)?; - let ty = expr.ty.clone(); - if unary_op_can_be_applied_to_type(syntax::UnaryOp::Neg, &ty) { - let span = expr.span; - let unary = semantic::UnaryOpExpr { - op: semantic::UnaryOp::Neg, - expr, - }; - Some(semantic::Expr { - span, - kind: Box::new(semantic::ExprKind::UnaryOp(unary)), - ty, - }) - } else { - let kind = SemanticErrorKind::TypeDoesNotSupportedUnaryNegation( - expr.ty.to_string(), - expr.span, - ); - self.push_semantic_error(kind); - None - } - } - } - syntax::UnaryOp::NotB => { - let expr = self.lower_expr(&expr.expr)?; + syntax::UnaryOp::Neg | 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(syntax::UnaryOp::NotB, &ty) { - let span = expr.span; - let unary = semantic::UnaryOpExpr { - op: semantic::UnaryOp::NotB, - expr, - }; - Some(semantic::Expr { - span, - kind: Box::new(semantic::ExprKind::UnaryOp(unary)), - ty, - }) - } else { + 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); - None + } + 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::NotL => { @@ -707,18 +582,19 @@ impl Lowerer { // it seems that the ! operator coerces to a bool if possible let target_ty = Type::Bool(false); let expr = - self.lower_expr_with_target_type(Some(&expr.expr), &target_ty, expr.expr.span)?; + self.lower_expr_with_target_type(Some(&expr.expr), &target_ty, expr.expr.span); let ty = expr.ty.clone(); - Some(semantic::Expr { + semantic::Expr { span: expr.span, kind: Box::new(semantic::ExprKind::UnaryOp(semantic::UnaryOpExpr { + span: expr.span, op: semantic::UnaryOp::NotL, expr, })), ty, - }) + } } } } @@ -770,88 +646,83 @@ impl Lowerer { &mut self, ty: &super::types::Type, span: Span, - ) -> Option { + ) -> crate::types::Type { let is_const = ty.is_const(); match ty { - Type::Bit(_) => Some(crate::types::Type::Result(is_const)), - Type::Qubit => Some(crate::types::Type::Qubit), + Type::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); - None + crate::types::Type::Err } Type::Int(width, _) | Type::UInt(width, _) => { if let Some(width) = width { if *width > 64 { - Some(crate::types::Type::BigInt(is_const)) + crate::types::Type::BigInt(is_const) } else { - Some(crate::types::Type::Int(is_const)) + crate::types::Type::Int(is_const) } } else { - Some(crate::types::Type::Int(is_const)) + crate::types::Type::Int(is_const) } } - Type::Float(_, _) | Type::Angle(_, _) => Some(crate::types::Type::Double(is_const)), - Type::Complex(_, _) => Some(crate::types::Type::Complex(is_const)), - Type::Bool(_) => Some(crate::types::Type::Bool(is_const)), + Type::Float(_, _) | Type::Angle(_, _) => crate::types::Type::Double(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); - None + crate::types::Type::Err } Type::Stretch(_) => { self.push_unsupported_error_message("Stretch type values", span); - None + crate::types::Type::Err } - Type::BitArray(dims, _) => Some(crate::types::Type::ResultArray(dims.into(), is_const)), - Type::QubitArray(dims) => Some(crate::types::Type::QubitArray(dims.into())), + 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 { - Some(crate::types::Type::BigIntArray(dims.into(), is_const)) + crate::types::Type::BigIntArray(dims.into(), is_const) } else { - Some(crate::types::Type::IntArray(dims.into(), is_const)) + crate::types::Type::IntArray(dims.into(), is_const) } } else { - Some(crate::types::Type::IntArray(dims.into(), is_const)) + crate::types::Type::IntArray(dims.into(), is_const) } } - Type::FloatArray(_, dims) => Some(crate::types::Type::DoubleArray(dims.into())), - Type::AngleArray(_, _) => todo!("AngleArray to Q# type"), - Type::ComplexArray(_, _) => todo!("ComplexArray to Q# type"), - Type::BoolArray(dims) => Some(crate::types::Type::BoolArray(dims.into(), is_const)), - Type::Gate(cargs, qargs) => Some(crate::types::Type::Callable( - crate::types::CallableKind::Operation, - *cargs, - *qargs, - )), - Type::Range => Some(crate::types::Type::Range), - Type::Set => todo!("Set to Q# type"), - Type::Void => Some(crate::types::Type::Tuple(vec![])), + 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![]), _ => { let msg = format!("Converting {ty:?} to Q# type"); self.push_unimplemented_error_message(msg, span); - None + crate::types::Type::Err } } } - fn lower_barrier(&mut self, stmt: &syntax::BarrierStmt) -> Option { + fn lower_barrier(&mut self, stmt: &syntax::BarrierStmt) -> semantic::StmtKind { self.push_unimplemented_error_message("barrier stmt", stmt.span); - None + semantic::StmtKind::Err } /// 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) -> Option { - let stmts = stmt + 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; + let mut _has_invalid_stmt_kinds = false; for stmt in &stmt.body { match &*stmt.kind { syntax::StmtKind::Barrier(_) @@ -864,72 +735,63 @@ impl Lowerer { } _ => { self.push_semantic_error(SemanticErrorKind::ClassicalStmtInBox(stmt.span)); - has_invalid_stmt_kinds = true; + _has_invalid_stmt_kinds = true; } } } if let Some(duration) = &stmt.duration { self.push_unsupported_error_message("Box with duration", duration.span); - return None; - } - - if has_invalid_stmt_kinds || stmts.len() != stmt.body.len() { - return None; } // 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); - None + semantic::StmtKind::Err } - fn lower_break(&mut self, stmt: &syntax::BreakStmt) -> Option { + fn lower_break(&mut self, stmt: &syntax::BreakStmt) -> semantic::StmtKind { self.push_unimplemented_error_message("break stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_block(&mut self, stmt: &syntax::Block) -> Option { + fn lower_block(&mut self, stmt: &syntax::Block) -> semantic::Block { self.symbols.push_scope(ScopeKind::Block); - let stmts = stmt.stmts.iter().filter_map(|stmt| self.lower_stmt(stmt)); + let stmts = stmt.stmts.iter().map(|stmt| self.lower_stmt(stmt)); let stmts = list_from_iter(stmts); self.symbols.pop_scope(); - if stmts.len() != stmt.stmts.len() { - return None; - } - - Some(semantic::Block { + semantic::Block { span: stmt.span, stmts, - }) + } } - fn lower_calibration(&mut self, stmt: &syntax::CalibrationStmt) -> Option { + fn lower_calibration(&mut self, stmt: &syntax::CalibrationStmt) -> semantic::StmtKind { self.push_unimplemented_error_message("calibration stmt", stmt.span); - None + semantic::StmtKind::Err } fn lower_calibration_grammar( &mut self, stmt: &syntax::CalibrationGrammarStmt, - ) -> Option { - self.push_unimplemented_error_message("calibration stmt", stmt.span); - None + ) -> semantic::StmtKind { + self.push_unimplemented_error_message("calibration grammar stmt", stmt.span); + semantic::StmtKind::Err } fn lower_classical_decl( &mut self, stmt: &syntax::ClassicalDeclarationStmt, - ) -> Option { + ) -> semantic::StmtKind { let is_const = false; // const decls are handled separately - let ty = self.get_semantic_type_from_tydef(&stmt.ty, is_const)?; + 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 qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty.clone(), ty_span); let symbol = Symbol { name: name.to_string(), ty: ty.clone(), @@ -941,28 +803,24 @@ impl Lowerer { // process the symbol and init_expr gathering any errors let init_expr = match init_expr { Some(expr) => match expr { - syntax::ValueExpression::Expr(expr) => self - .lower_expr_with_target_type(Some(expr), &ty, stmt_span) - .map(semantic::ValueExpression::Expr), - syntax::ValueExpression::Measurement(measure_expr) => self - .lower_measure_expr_with_target_type(measure_expr, &ty, stmt_span) - .map(semantic::ValueExpression::Measurement), + syntax::ValueExpression::Expr(expr) => semantic::ValueExpression::Expr( + self.lower_expr_with_target_type(Some(expr), &ty, stmt_span), + ), + syntax::ValueExpression::Measurement(measure_expr) => { + semantic::ValueExpression::Measurement( + self.lower_measure_expr(measure_expr, stmt_span), + ) + } }, - None => self - .lower_expr_with_target_type(None, &ty, stmt_span) - .map(semantic::ValueExpression::Expr), - }; - - let Ok(symbol_id) = self.symbols.insert_symbol(symbol) else { - self.push_redefined_symbol_error(&name, stmt.identifier.span); - return None; + None => semantic::ValueExpression::Expr( + self.lower_expr_with_target_type(None, &ty, stmt_span), + ), }; - // even if init_expr was None, Q# semantics require that we have an initial value - // for classical declarations. So if None is returned we hit an error with the expression. - let init_expr = init_expr?; + let symbol_id = + self.try_insert_or_get_existing_symbol_id(name, symbol, stmt.identifier.span); - Some(semantic::ClassicalDeclarationStmt { + semantic::StmtKind::ClassicalDecl(semantic::ClassicalDeclarationStmt { span: stmt_span, ty_span, symbol_id, @@ -970,15 +828,12 @@ impl Lowerer { }) } - fn lower_const_decl( - &mut self, - stmt: &syntax::ConstantDeclStmt, - ) -> Option { + 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 = 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 qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty.clone(), stmt.ty.span()); let symbol = Symbol { name: name.to_string(), ty: ty.clone(), @@ -990,16 +845,10 @@ impl Lowerer { // process the symbol and init_expr gathering any errors let init_expr = self.lower_expr_with_target_type(Some(&stmt.init_expr), &ty, stmt.span); - let Ok(symbol_id) = self.symbols.insert_symbol(symbol) else { - self.push_redefined_symbol_error(&name, stmt.identifier.span); - return None; - }; + let symbol_id = + self.try_insert_or_get_existing_symbol_id(name, symbol, stmt.identifier.span); - // even if init_expr was None, Q# semantics require that we have an initial value - // for classical declarations. So if None is returned we hit an error with the expression. - let init_expr = init_expr?; - - Some(semantic::ClassicalDeclarationStmt { + semantic::StmtKind::ClassicalDecl(semantic::ClassicalDeclarationStmt { span: stmt.span, ty_span, symbol_id, @@ -1007,86 +856,85 @@ impl Lowerer { }) } - fn lower_continue_stmt(&mut self, stmt: &syntax::ContinueStmt) -> Option { + fn lower_continue_stmt(&mut self, stmt: &syntax::ContinueStmt) -> semantic::StmtKind { self.push_unimplemented_error_message("continue stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_def(&mut self, stmt: &syntax::DefStmt) -> Option { + fn lower_def(&mut self, stmt: &syntax::DefStmt) -> semantic::StmtKind { self.push_unimplemented_error_message("def stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_def_cal(&mut self, stmt: &syntax::DefCalStmt) -> Option { + fn lower_def_cal(&mut self, stmt: &syntax::DefCalStmt) -> semantic::StmtKind { self.push_unimplemented_error_message("def cal stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_delay(&mut self, stmt: &syntax::DelayStmt) -> Option { + fn lower_delay(&mut self, stmt: &syntax::DelayStmt) -> semantic::StmtKind { self.push_unimplemented_error_message("delay stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_end_stmt(&mut self, stmt: &syntax::EndStmt) -> Option { + fn lower_end_stmt(&mut self, stmt: &syntax::EndStmt) -> semantic::StmtKind { self.push_unimplemented_error_message("end stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_expr_stmt(&mut self, stmt: &syntax::ExprStmt) -> Option { - let expr = self.lower_expr(&stmt.expr)?; - Some(semantic::ExprStmt { + 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) -> Option { + fn lower_extern(&mut self, stmt: &syntax::ExternDecl) -> semantic::StmtKind { self.push_unimplemented_error_message("extern stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_for_stmt(&mut self, stmt: &syntax::ForStmt) -> Option { + 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. - let (loop_variable, body) = with_scope(self, ScopeKind::Block, |lw| { - let ty = lw.get_semantic_type_from_scalar_ty(&stmt.ty, false)?; - let qsharp_ty = lw.convert_semantic_type_to_qsharp_type(&ty.clone(), stmt.ty.span)?; - let symbol = Symbol { - name: stmt.ident.name.to_string(), - span: stmt.ident.span, - ty: ty.clone(), - qsharp_ty, - io_kind: IOKind::Default, - }; + self.symbols.push_scope(ScopeKind::Block); + + 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 { + name: stmt.ident.name.to_string(), + span: stmt.ident.span, + ty: ty.clone(), + qsharp_ty, + io_kind: IOKind::Default, + }; - // This is the first variable in this scope, so - // we don't need to check for redefined symbols. - let symbol_id = lw - .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 = lw.lower_stmt(&stmt.body); - - Some((symbol_id, body?)) - })?; - - // We use the `?` operator after lowering all the fields - // to report as many errors as possible before exiting the function. - Some(semantic::ForStmt { + // 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, - set_declaration: Box::new(set_declaration?), + loop_variable: symbol_id, + set_declaration: Box::new(set_declaration), body, }) } - fn lower_if_stmt(&mut self, stmt: &syntax::IfStmt) -> Option { + 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)); @@ -1094,54 +942,56 @@ impl Lowerer { // 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(false); - let condition = condition?; let condition = self.cast_expr_to_type(&cond_ty, &condition, condition.span); - // We use the `?` operator after lowering all the fields - // to report as many errors as possible before exiting the function. - Some(semantic::IfStmt { + semantic::StmtKind::If(semantic::IfStmt { span: stmt.span, - condition: condition?, - if_body: if_body?, - else_body: short_circuit_opt_item!(else_body), + condition, + if_body, + else_body, }) } - fn lower_gate_call(&mut self, stmt: &syntax::GateCall) -> Option { + fn lower_gate_call(&mut self, stmt: &syntax::GateCall) -> semantic::StmtKind { self.push_unimplemented_error_message("gate call stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_gphase(&mut self, stmt: &syntax::GPhase) -> Option { + fn lower_gphase(&mut self, stmt: &syntax::GPhase) -> semantic::StmtKind { self.push_unimplemented_error_message("gphase stmt", stmt.span); - None + semantic::StmtKind::Err } /// 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) -> Option { + 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 None; + 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) -> Option { + 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 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 qsharp_ty = self.convert_semantic_type_to_qsharp_type(&ty, ty_span); let symbol = Symbol { name: name.to_string(), ty: ty.clone(), @@ -1150,63 +1000,58 @@ impl Lowerer { io_kind, }; - let Ok(symbol_id) = self.symbols.insert_symbol(symbol) else { - self.push_redefined_symbol_error(&name, stmt.ident.span); - return None; - }; + let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol, stmt.ident.span); - // if we have output, we need to assign a default value to declare the variable - // if we have input, we can keep return none as we would promote the variable - // to a parameter in the function signature once we generate the function - if io_kind == IOKind::Output { - let init_expr = self.get_default_value(&ty, stmt_span)?; - Some(semantic::IODeclaration { + if io_kind == IOKind::Input { + return semantic::StmtKind::InputDeclaration(semantic::InputDeclaration { span: stmt_span, symbol_id, - init_expr: Box::new(init_expr), - }) - } else { - None + }); } + + // if we have output, we need to assign a default value to declare the variable + // if we have input, we can keep return none as we would promote the variable + // to a parameter in the function signature once we generate the function + 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::MeasureStmt) -> Option { + fn lower_measure(&mut self, stmt: &syntax::MeasureStmt) -> semantic::StmtKind { self.push_unimplemented_error_message("measure stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_pragma(&mut self, stmt: &syntax::Pragma) -> Option { + fn lower_pragma(&mut self, stmt: &syntax::Pragma) -> semantic::StmtKind { self.push_unimplemented_error_message("pragma stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_gate_def( - &mut self, - stmt: &syntax::QuantumGateDefinition, - ) -> Option { + fn lower_gate_def(&mut self, stmt: &syntax::QuantumGateDefinition) -> semantic::StmtKind { self.push_unimplemented_error_message("gate def stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_quantum_decl( - &mut self, - stmt: &syntax::QubitDeclaration, - ) -> Option { + fn lower_quantum_decl(&mut self, stmt: &syntax::QubitDeclaration) -> semantic::StmtKind { self.push_unimplemented_error_message("qubit decl stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_reset(&mut self, stmt: &syntax::ResetStmt) -> Option { + fn lower_reset(&mut self, stmt: &syntax::ResetStmt) -> semantic::StmtKind { self.push_unimplemented_error_message("reset stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_return(&mut self, stmt: &syntax::ReturnStmt) -> Option { + fn lower_return(&mut self, stmt: &syntax::ReturnStmt) -> semantic::StmtKind { self.push_unimplemented_error_message("return stmt", stmt.span); - None + semantic::StmtKind::Err } - fn lower_switch(&mut self, stmt: &syntax::SwitchStmt) -> Option { + 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. @@ -1219,24 +1064,16 @@ impl Lowerer { let cases = stmt .cases .iter() - .filter_map(|case| self.lower_switch_case(case)) + .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)?; + 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, false); - let target = self.cast_expr_to_type(&target_ty, &target, target.span)?; - - // We use the `?` operator after casting the condition to int - // to report as many errors as possible before exiting the function. - let default = short_circuit_opt_item!(default); - - if cases.len() != stmt.cases.len() { - return None; - } + let target = self.cast_expr_to_type(&target_ty, &target, target.span); // It is a parse error to have a switch statement with no cases, // even if the default block is present. Getting here means the @@ -1260,11 +1097,10 @@ impl Lowerer { &SWITCH_MINIMUM_SUPPORTED_VERSION, stmt.span, ); - return None; } } - Some(semantic::SwitchStmt { + semantic::StmtKind::Switch(semantic::SwitchStmt { span: stmt.span, target, cases: list_from_iter(cases), @@ -1272,51 +1108,43 @@ impl Lowerer { }) } - fn lower_switch_case( - &mut self, - switch_case: &syntax::SwitchCase, - ) -> Option { + fn lower_switch_case(&mut self, switch_case: &syntax::SwitchCase) -> semantic::SwitchCase { let label_ty = Type::Int(None, false); let labels = switch_case .labels .iter() - .filter_map(|label| { + .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)?; + let label = self.lower_expr(label); self.cast_expr_to_type(&label_ty, &label, label.span) }) .collect::>(); - let block = self.lower_block(&switch_case.block)?; + let block = self.lower_block(&switch_case.block); - if labels.len() != switch_case.labels.len() { - return None; - } - - Some(semantic::SwitchCase { + semantic::SwitchCase { span: switch_case.span, labels: list_from_iter(labels), block, - }) + } } - fn lower_while_stmt(&mut self, stmt: &syntax::WhileLoop) -> Option { + fn lower_while_stmt(&mut self, stmt: &syntax::WhileLoop) -> semantic::StmtKind { let while_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(false); - let while_condition = while_condition?; let while_condition = - self.cast_expr_to_type(&cond_ty, &while_condition, while_condition.span)?; + self.cast_expr_to_type(&cond_ty, &while_condition, while_condition.span); - Some(semantic::WhileLoop { + semantic::StmtKind::WhileLoop(semantic::WhileLoop { span: stmt.span, while_condition, - body: body?, + body, }) } @@ -1324,7 +1152,7 @@ impl Lowerer { &mut self, scalar_ty: &syntax::TypeDef, is_const: bool, - ) -> Option { + ) -> crate::semantic::types::Type { match scalar_ty { syntax::TypeDef::Scalar(scalar_type) => { self.get_semantic_type_from_scalar_ty(scalar_type, is_const) @@ -1374,61 +1202,72 @@ impl Lowerer { &mut self, scalar_ty: &syntax::ScalarType, is_const: bool, - ) -> Option { + ) -> crate::semantic::types::Type { match &scalar_ty.kind { syntax::ScalarTypeKind::Bit(bit_type) => match &bit_type.size { - Some(size) => Some(crate::semantic::types::Type::BitArray( - super::types::ArrayDimensions::One(self.get_size_designator_from_expr(size)?), - is_const, - )), - None => Some(crate::semantic::types::Type::Bit(is_const)), + Some(size) => { + let Some(size) = self.get_size_designator_from_expr(size) else { + return crate::semantic::types::Type::Err; + }; + crate::semantic::types::Type::BitArray( + super::types::ArrayDimensions::One(size), + is_const, + ) + } + None => crate::semantic::types::Type::Bit(is_const), }, syntax::ScalarTypeKind::Int(int_type) => match &int_type.size { - Some(size) => Some(crate::semantic::types::Type::Int( - Some(self.get_size_designator_from_expr(size)?), - is_const, - )), - None => Some(crate::semantic::types::Type::Int(None, is_const)), + Some(size) => { + let Some(size) = self.get_size_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) => Some(crate::semantic::types::Type::UInt( - Some(self.get_size_designator_from_expr(size)?), - is_const, - )), - None => Some(crate::semantic::types::Type::UInt(None, is_const)), + Some(size) => { + let Some(size) = self.get_size_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) => Some(crate::semantic::types::Type::Float( - Some(self.get_size_designator_from_expr(size)?), - is_const, - )), - None => Some(crate::semantic::types::Type::Float(None, is_const)), + Some(size) => { + let Some(size) = self.get_size_designator_from_expr(size) else { + return crate::semantic::types::Type::Err; + }; + 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) => Some(crate::semantic::types::Type::Complex( - Some(self.get_size_designator_from_expr(size)?), - is_const, - )), - None => Some(crate::semantic::types::Type::Complex(None, is_const)), + Some(size) => { + let Some(size) = self.get_size_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 => Some(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) => Some(crate::semantic::types::Type::Angle( - Some(self.get_size_designator_from_expr(size)?), - is_const, - )), - None => Some(crate::semantic::types::Type::Angle(None, is_const)), + Some(size) => { + let Some(size) = self.get_size_designator_from_expr(size) else { + return crate::semantic::types::Type::Err; + }; + crate::semantic::types::Type::Angle(Some(size), is_const) + } + None => crate::semantic::types::Type::Angle(None, is_const), }, - syntax::ScalarTypeKind::BoolType => Some(crate::semantic::types::Type::Bool(is_const)), - syntax::ScalarTypeKind::Duration => { - Some(crate::semantic::types::Type::Duration(is_const)) - } - syntax::ScalarTypeKind::Stretch => { - Some(crate::semantic::types::Type::Stretch(is_const)) - } - syntax::ScalarTypeKind::Err => Some(crate::semantic::types::Type::Err), + 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, } } @@ -1436,39 +1275,39 @@ impl Lowerer { &mut self, array_ty: &syntax::ArrayType, _is_const: bool, - ) -> Option { + ) -> crate::semantic::types::Type { self.push_unimplemented_error_message("semantic type from array type", array_ty.span); - None + crate::semantic::types::Type::Err } fn get_semantic_type_from_array_reference_ty( &mut self, array_ref_ty: &syntax::ArrayReferenceType, _is_const: bool, - ) -> Option { + ) -> crate::semantic::types::Type { self.push_unimplemented_error_message( "semantic type from array refence type", array_ref_ty.span, ); - None + crate::semantic::types::Type::Err } fn lower_expr_with_target_type( &mut self, expr: Option<&syntax::Expr>, ty: &Type, span: Span, - ) -> Option { + ) -> semantic::Expr { let Some(expr) = expr else { // In OpenQASM, classical variables may be uninitialized, but in Q#, // they must be initialized. We will use the default value for the type // to initialize the variable. return self.get_default_value(ty, span); }; - let rhs = self.lower_expr(expr)?; + let rhs = self.lower_expr(expr); let rhs_ty = rhs.ty.clone(); // if we have an exact type match, we can use the rhs as is if types_equal_except_const(ty, &rhs_ty) { - return Some(rhs); + return rhs; } // if the rhs is a literal, we can try to cast it to the target type @@ -1487,7 +1326,7 @@ impl Lowerer { span, ); self.push_semantic_error(kind); - return None; + 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 @@ -1496,17 +1335,18 @@ impl Lowerer { self.cast_expr_to_type(ty, &rhs, span) } - fn lower_measure_expr_with_target_type( + fn lower_measure_expr( &mut self, - _expr: &syntax::MeasureExpr, - _ty: &Type, + expr: &syntax::MeasureExpr, span: Span, - ) -> Option { - self.push_unimplemented_error_message("measure expr with target type", span); - None + ) -> semantic::MeasureExpr { + semantic::MeasureExpr { + span, + operand: self.lower_gate_operand(&expr.operand), + } } - fn get_default_value(&mut self, ty: &Type, span: Span) -> Option { + fn get_default_value(&mut self, ty: &Type, span: Span) -> semantic::Expr { use semantic::Expr; use semantic::ExprKind; use semantic::LiteralKind; @@ -1517,7 +1357,7 @@ impl Lowerer { ty: ty.as_const(), } }; - match ty { + let expr = match ty { Type::Bit(_) | Type::Int(_, _) | Type::UInt(_, _) => { Some(from_lit_kind(LiteralKind::Int(0))) } @@ -1586,30 +1426,44 @@ impl Lowerer { self.push_unsupported_error_message(message, span); None } - } + }; + let Some(expr) = expr else { + return err_expr!(ty.as_const()); + }; + expr } - #[allow(clippy::too_many_lines)] 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) { - // literals are always const, so we can safely return - // the const ty - if rhs.ty.is_const() { - return Some(rhs.clone()); - } - // the lsh is supposed to be const but is being initialized - // to a non-const value, we can't allow this - return None; + // 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(); @@ -1651,14 +1505,12 @@ impl Lowerer { if is_int_to_bit_array { if let semantic::LiteralKind::Int(value) = kind { if *value < 0 || *value >= (1 << size) { - // todo: error message 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 { - // todo: error message return None; }; @@ -1698,7 +1550,7 @@ impl Lowerer { lhs_ty.to_string(), span, )); - return None?; + return None; } None } @@ -1757,7 +1609,7 @@ impl Lowerer { span, ); self.push_semantic_error(kind); - return None?; + return None; } None } @@ -1825,86 +1677,16 @@ impl Lowerer { } } - // Rules for negating literals are different than that of expressions - // What those rules are is not clear from the spec, so this is a best guess - // based on other qasm implementations. - fn lower_negated_literal_as_ty( - &mut self, - lit: &syntax::Lit, - target_ty: Option, - span: Span, - ) -> Option { - let (kind, ty) = (match &lit.kind { - syntax::LiteralKind::Float(value) => Some(( - semantic::LiteralKind::Float(-value), - Type::Float(None, true), - )), - syntax::LiteralKind::Imaginary(value) => Some(( - semantic::LiteralKind::Complex(0.0, -value), - Type::Complex(None, true), - )), - syntax::LiteralKind::Int(value) => { - Some((semantic::LiteralKind::Int(-value), Type::Int(None, true))) - } - syntax::LiteralKind::BigInt(value) => { - let value = BigInt::from(-1) * value; - Some((semantic::LiteralKind::BigInt(value), Type::Int(None, true))) - } - syntax::LiteralKind::Duration(value, time_unit) => { - let unit = match time_unit { - syntax::TimeUnit::Dt => semantic::TimeUnit::Dt, - syntax::TimeUnit::Ms => semantic::TimeUnit::Ms, - syntax::TimeUnit::Ns => semantic::TimeUnit::Ns, - syntax::TimeUnit::S => semantic::TimeUnit::S, - syntax::TimeUnit::Us => semantic::TimeUnit::Us, - }; - Some(( - semantic::LiteralKind::Duration(-value, unit), - Type::Duration(true), - )) - } - syntax::LiteralKind::Array(_) => { - self.push_unsupported_error_message("negated array literal expressions", span); - None - } - syntax::LiteralKind::Bitstring(_, _) => { - self.push_unsupported_error_message("negated bitstring literal expressions", span); - None - } - syntax::LiteralKind::Bool(_) => { - self.push_unsupported_error_message("negated bool literal expressions", span); - None - } - syntax::LiteralKind::String(_) => { - self.push_unsupported_error_message("negated string literal expressions", span); - None - } - })?; - - let expr = semantic::Expr { - span, - kind: Box::new(semantic::ExprKind::Lit(kind.clone())), - ty, - }; - if let Some(target_ty) = target_ty { - return self.coerce_literal_expr_to_type(&target_ty, &expr, &kind); - } - Some(expr) - } - fn cast_expr_to_type( &mut self, ty: &Type, expr: &semantic::Expr, span: Span, - ) -> Option { - let cast_expr = self.try_cast_expr_to_type(ty, expr, span); - if cast_expr.is_none() { - let rhs_ty_name = format!("{:?}", expr.ty); - let lhs_ty_name = format!("{ty:?}"); - let kind = SemanticErrorKind::CannotCast(rhs_ty_name, lhs_ty_name, span); - self.push_semantic_error(kind); - } + ) -> semantic::Expr { + let Some(cast_expr) = self.try_cast_expr_to_type(ty, expr, span) else { + self.push_invalid_cast_error(ty, &expr.ty, span); + return expr.clone(); + }; cast_expr } @@ -1978,7 +1760,7 @@ impl Lowerer { fn cast_angle_expr_to_type(ty: &Type, rhs: &semantic::Expr) -> Option { assert!(matches!(rhs.ty, Type::Angle(..))); match ty { - Type::Bit(..) | Type::Bool(..) => { + Type::Angle(..) | Type::Bit(..) | Type::BitArray(..) | Type::Bool(..) => { Some(wrap_expr_in_implicit_cast_expr(ty.clone(), rhs.clone())) } _ => None, @@ -2118,7 +1900,7 @@ impl Lowerer { lhs: semantic::Expr, rhs: semantic::Expr, span: Span, - ) -> Option { + ) -> semantic::Expr { if lhs.ty.is_quantum() { let kind = SemanticErrorKind::QuantumTypesInBinaryExpression(lhs.span); self.push_semantic_error(kind); @@ -2158,14 +1940,25 @@ impl Lowerer { ); self.push_semantic_error(kind); } - return None; + 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, lhs.span)?; + let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs.span); // 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, rhs.span)? + self.cast_expr_to_type(&ty, &rhs, rhs.span) } else { rhs }; @@ -2182,8 +1975,8 @@ impl Lowerer { ty, }; - let final_expr = self.cast_expr_to_type(&left_type, &expr, span)?; - return Some(final_expr); + let final_expr = self.cast_expr_to_type(&left_type, &expr, span); + return final_expr; } // for int, uint, float, and complex, the lesser of the two types is cast to @@ -2195,13 +1988,13 @@ impl Lowerer { let (lhs, rhs, ty) = if matches!(op, syntax::BinOp::AndL | syntax::BinOp::OrL) { let ty = Type::Bool(false); - let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs.span)?; - let new_rhs = self.cast_expr_to_type(&ty, &rhs, rhs.span)?; + let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs.span); + let new_rhs = self.cast_expr_to_type(&ty, &rhs, rhs.span); (new_lhs, new_rhs, ty) } else if binop_requires_int_conversion_for_type(op, &left_type, &rhs.ty) { let ty = Type::Int(None, false); - let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs.span)?; - let new_rhs = self.cast_expr_to_type(&ty, &rhs, lhs.span)?; + let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs.span); + let new_rhs = self.cast_expr_to_type(&ty, &rhs, lhs.span); (new_lhs, new_rhs, ty) } else if requires_symmetric_conversion(op) { let promoted_type = try_promote_with_casting(&left_type, &right_type); @@ -2213,12 +2006,12 @@ impl Lowerer { 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)? + self.coerce_literal_expr_to_type(&promoted_type, &lhs, kind) } else { - self.cast_expr_to_type(&promoted_type, &lhs, lhs.span)? + self.cast_expr_to_type(&promoted_type, &lhs, lhs.span) } } - _ => self.cast_expr_to_type(&promoted_type, &lhs, lhs.span)?, + _ => self.cast_expr_to_type(&promoted_type, &lhs, lhs.span), } }; let new_right = if promoted_type == right_type { @@ -2229,18 +2022,18 @@ impl Lowerer { 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)? + self.coerce_literal_expr_to_type(&promoted_type, &rhs, kind) } else { - self.cast_expr_to_type(&promoted_type, &rhs, rhs.span)? + self.cast_expr_to_type(&promoted_type, &rhs, rhs.span) } } - _ => self.cast_expr_to_type(&promoted_type, &rhs, rhs.span)?, + _ => self.cast_expr_to_type(&promoted_type, &rhs, rhs.span), } }; (new_left, new_right, promoted_type) } else if binop_requires_symmetric_int_conversion(op) { let ty = Type::Int(None, false); - let new_rhs = self.cast_expr_to_type(&ty, &rhs, rhs.span)?; + let new_rhs = self.cast_expr_to_type(&ty, &rhs, rhs.span); (lhs, new_rhs, left_type) } else { (lhs, rhs, left_type) @@ -2256,7 +2049,7 @@ impl Lowerer { // this is going to be a call to a built-in function // that doesn't map to qasm def semantics self.push_unimplemented_error_message("complex binary exprs", span); - None + err_expr!(ty.clone(), span) } else { let kind = SemanticErrorKind::OperatorNotSupportedForTypes( format!("{op:?}"), @@ -2265,7 +2058,7 @@ impl Lowerer { span, ); self.push_semantic_error(kind); - None + err_expr!(ty.clone()) } } else { let bin_expr = semantic::BinaryOpExpr { @@ -2274,12 +2067,11 @@ impl Lowerer { rhs, }; let kind = semantic::ExprKind::BinaryOp(bin_expr); - let expr = semantic::Expr { + semantic::Expr { span, kind: Box::new(kind), ty: ty.clone(), - }; - Some(expr) + } }; let ty = match op.into() { @@ -2293,9 +2085,9 @@ impl Lowerer { | semantic::BinOp::OrL => Type::Bool(false), _ => ty, }; - let mut expr = expr?; + let mut expr = expr; expr.ty = ty; - Some(expr) + expr } fn binop_requires_bitwise_conversion(op: syntax::BinOp, left_type: &Type) -> bool { @@ -2333,125 +2125,94 @@ impl Lowerer { } // TODO: which these are parsed as different types, they are effectively the same - fn lower_index_element( - &mut self, - index: &syntax::IndexElement, - ) -> Option { + fn lower_index_element(&mut self, index: &syntax::IndexElement) -> semantic::IndexElement { match index { - syntax::IndexElement::DiscreteSet(set) => Some(semantic::IndexElement::DiscreteSet( - self.lower_discrete_set(set)?, - )), + syntax::IndexElement::DiscreteSet(set) => { + semantic::IndexElement::DiscreteSet(self.lower_discrete_set(set)) + } syntax::IndexElement::IndexSet(set) => { - Some(semantic::IndexElement::IndexSet(self.lower_index_set(set)?)) + semantic::IndexElement::IndexSet(self.lower_index_set(set)) } } } - fn lower_index_set_item( - &mut self, - item: &syntax::IndexSetItem, - ) -> Option { - let item = match item { + 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)?, + self.lower_range_definition(range_definition), ) } - syntax::IndexSetItem::Expr(expr) => { - semantic::IndexSetItem::Expr(self.lower_expr(expr)?) - } - syntax::IndexSetItem::Err => { - unreachable!("IndexSetItem::Err should have been handled") - } - }; - Some(item) + 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, - ) -> Option { + fn lower_enumerable_set(&mut self, set: &syntax::EnumerableSet) -> semantic::EnumerableSet { match set { - syntax::EnumerableSet::DiscreteSet(set) => Some(semantic::EnumerableSet::DiscreteSet( - self.lower_discrete_set(set)?, - )), + syntax::EnumerableSet::DiscreteSet(set) => { + semantic::EnumerableSet::DiscreteSet(self.lower_discrete_set(set)) + } syntax::EnumerableSet::RangeDefinition(range_definition) => { - Some(semantic::EnumerableSet::RangeDefinition( - self.lower_range_definition(range_definition)?, - )) + semantic::EnumerableSet::RangeDefinition( + self.lower_range_definition(range_definition), + ) } syntax::EnumerableSet::Expr(expr) => { - Some(semantic::EnumerableSet::Expr(self.lower_expr(expr)?)) + semantic::EnumerableSet::Expr(self.lower_expr(expr)) } } } - fn lower_index_set(&mut self, set: &syntax::IndexSet) -> Option { + fn lower_index_set(&mut self, set: &syntax::IndexSet) -> semantic::IndexSet { let items = set .values .iter() - .filter_map(|expr| self.lower_index_set_item(expr)) + .map(|expr| self.lower_index_set_item(expr)) .collect::>(); - if set.values.len() != items.len() { - let kind = SemanticErrorKind::FailedToCompileExpressionList(set.span); - self.push_semantic_error(kind); - return None; - } - Some(semantic::IndexSet { + semantic::IndexSet { span: set.span, values: syntax::list_from_iter(items), - }) + } } - fn lower_discrete_set(&mut self, set: &syntax::DiscreteSet) -> Option { + fn lower_discrete_set(&mut self, set: &syntax::DiscreteSet) -> semantic::DiscreteSet { let items = set .values .iter() - .filter_map(|expr| self.lower_expr(expr)) + .map(|expr| self.lower_expr(expr)) .collect::>(); - if set.values.len() != items.len() { - let kind = SemanticErrorKind::FailedToCompileExpressionList(set.span); - self.push_semantic_error(kind); - return None; - } - - Some(semantic::DiscreteSet { + semantic::DiscreteSet { span: set.span, values: list_from_iter(items), - }) + } } fn lower_range_definition( &mut self, range_definition: &syntax::RangeDefinition, - ) -> Option { + ) -> 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)); - let start = short_circuit_opt_item!(start); - let step = short_circuit_opt_item!(step); - let end = short_circuit_opt_item!(end); - - Some(semantic::RangeDefinition { + semantic::RangeDefinition { span: range_definition.span, start, step, end, - }) + } } - fn lower_index_expr(&mut self, expr: &syntax::IndexExpr) -> Option { + 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 collection = collection?; - let index = index?; - let indexed_ty = self.get_indexed_type(&collection.ty, expr.span, 1)?; + let indexed_ty = self.get_indexed_type(&collection.ty, expr.span, 1); - Some(semantic::Expr { + semantic::Expr { span: expr.span, kind: Box::new(semantic::ExprKind::IndexExpr(semantic::IndexExpr { span: expr.span, @@ -2459,7 +2220,7 @@ impl Lowerer { index, })), ty: indexed_ty, - }) + } } fn get_indexed_type( @@ -2467,17 +2228,17 @@ impl Lowerer { ty: &Type, span: Span, num_indices: usize, - ) -> Option { + ) -> super::types::Type { if !ty.is_array() { let kind = SemanticErrorKind::CannotIndexType(format!("{ty:?}"), span); self.push_semantic_error(kind); - return None; + return super::types::Type::Err; } if num_indices > ty.num_dims() { let kind = SemanticErrorKind::TooManyIndices(span); self.push_semantic_error(kind); - return None; + return super::types::Type::Err; } let mut indexed_ty = ty.clone(); @@ -2487,46 +2248,130 @@ impl Lowerer { // we should have caught this earlier with the two checks above let kind = SemanticErrorKind::CannotIndexType(format!("{ty:?}"), span); self.push_semantic_error(kind); - return None; + return super::types::Type::Err; }; indexed_ty = ty; } - Some(indexed_ty) + indexed_ty } - fn lower_indexed_ident_expr( - &mut self, - indexed_ident: &syntax::IndexedIdent, - ) -> Option { + /// 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() - .filter_map(|index| self.lower_index_element(index)) - .collect::>(); + .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 (symbol_id, lhs_symbol) = self.symbols.get_symbol_by_name(&ident.name)?; 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())?; - - if indices.len() != indexed_ident.indices.len() { - // we failed to lower all the indices, error was already pushed - return None; - } + let ty = self.get_indexed_type(&ty, indexed_ident.span, indexed_ident.indices.len()); - Some(semantic::Expr { + 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: syntax::list_from_iter(indices), + indices, }, )), ty, - }) + } + } + + #[allow(clippy::unused_self)] + fn lower_gate_operand(&mut self, operand: &syntax::GateOperand) -> semantic::GateOperand { + match operand { + syntax::GateOperand::IndexedIdent(_) + | syntax::GateOperand::HardwareQubit(_) + | syntax::GateOperand::Err => semantic::GateOperand::Err, + } + } + + 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); + } + + /// 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) } } diff --git a/compiler/qsc_qasm3/src/semantic/symbols.rs b/compiler/qsc_qasm3/src/semantic/symbols.rs index da03471c63..5479c8ada4 100644 --- a/compiler/qsc_qasm3/src/semantic/symbols.rs +++ b/compiler/qsc_qasm3/src/semantic/symbols.rs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use qsc_data_structures::span::Span; +use std::rc::Rc; + +use qsc_data_structures::{index_map::IndexMap, span::Span}; use rustc_hash::FxHashMap; use super::types::Type; @@ -155,7 +157,7 @@ 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, + 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, @@ -180,7 +182,7 @@ impl Scope { /// /// This function will return an error if a symbol of the same name has already /// been declared in this scope. - pub fn insert_symbol(&mut self, id: SymbolId, symbol: Symbol) -> Result<(), SymbolError> { + 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); } @@ -190,13 +192,13 @@ impl Scope { Ok(()) } - pub fn get_symbol_by_name(&self, name: &str) -> Option<(SymbolId, &Symbol)> { + 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))) + .and_then(|id| self.id_to_symbol.get(id).map(|s| (*id, s.clone()))) } - fn get_ordered_symbols(&self) -> Vec { + fn get_ordered_symbols(&self) -> Vec> { self.order .iter() .map(|id| self.id_to_symbol.get(id).expect("ID should exist").clone()) @@ -207,6 +209,7 @@ impl Scope { /// A symbol table is a collection of scopes and manages the symbol ids. pub struct SymbolTable { scopes: Vec, + symbols: IndexMap>, current_id: SymbolId, } @@ -228,6 +231,7 @@ impl Default for SymbolTable { let mut slf = Self { scopes: vec![global], + symbols: IndexMap::default(), current_id: SymbolId::default(), }; @@ -259,28 +263,73 @@ impl SymbolTable { } pub fn insert_symbol(&mut self, symbol: Symbol) -> Result { + let symbol = Rc::new(symbol); let id = self.current_id; - self.current_id = self.current_id.successor(); - self.scopes + match self + .scopes .last_mut() .expect("At least one scope should be available") - .insert_symbol(id, symbol)?; + .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), + } + } - Ok(id) + 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, + }); + let id = self.current_id; + self.current_id = self.current_id.successor(); + self.symbols.insert(id, symbol.clone()); + (id, symbol) } - #[must_use] - pub fn get_symbol_by_id(&self, id: SymbolId) -> Option<(SymbolId, &Symbol)> { - for scope in self.scopes.iter().rev() { - if let Some(symbol) = scope.id_to_symbol.get(&id) { - return Some((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) } - None } + /// 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: &str) -> Option<(SymbolId, &Symbol)> { + 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| { x.kind == ScopeKind::Block || x.kind == ScopeKind::Function || x.kind == ScopeKind::Gate @@ -303,13 +352,13 @@ impl SymbolTable { .take_while(|arg0: &&Scope| predicate(arg0)) .next() { - if let Some((id, symbol)) = scope.get_symbol_by_name(name) { + 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) { + 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) || self.is_scope_rooted_in_global() @@ -320,7 +369,7 @@ impl SymbolTable { } // 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) { + 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) { return Some((id, symbol)); } @@ -357,7 +406,7 @@ impl SymbolTable { } /// Get the input symbols in the program. - pub(crate) fn get_input(&self) -> Option> { + pub(crate) fn get_input(&self) -> Option>> { let io_input = self.get_io_input(); if io_input.is_empty() { None @@ -370,7 +419,7 @@ impl SymbolTable { /// 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> { + pub(crate) fn get_output(&self) -> Option>> { let io_ouput = self.get_io_output(); if io_ouput.is_some() { io_ouput @@ -382,7 +431,7 @@ impl SymbolTable { /// 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> { + fn get_inferred_output(&self) -> Option>> { let mut symbols = vec![]; self.scopes .iter() @@ -407,7 +456,7 @@ impl SymbolTable { } /// Get all symbols in the global scope that are output symbols. - fn get_io_output(&self) -> Option> { + fn get_io_output(&self) -> Option>> { let mut symbols = vec![]; for scope in self .scopes @@ -428,7 +477,7 @@ impl SymbolTable { } /// Get all symbols in the global scope that are input symbols. - fn get_io_input(&self) -> Vec { + fn get_io_input(&self) -> Vec> { let mut symbols = vec![]; for scope in self .scopes @@ -444,3 +493,11 @@ impl SymbolTable { 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 index cfa028a5b4..1de94b14c7 100644 --- a/compiler/qsc_qasm3/src/semantic/tests.rs +++ b/compiler/qsc_qasm3/src/semantic/tests.rs @@ -66,8 +66,8 @@ pub(super) fn check(input: &str, expect: &Expect) { } pub(super) fn check_classical_decl(input: &str, expect: &Expect) { - check_map(input, expect, |p, s| { - let kind = p + check_map(input, expect, |program, symbol_table| { + let kind = program .statements .first() .expect("reading first statement") @@ -78,17 +78,15 @@ pub(super) fn check_classical_decl(input: &str, expect: &Expect) { }; let mut value = decl.to_string(); value.push('\n'); - let symbol = s - .get_symbol_by_id(decl.symbol_id) - .expect("getting symbol by id"); - value.push_str(&format!("[{}] {}", symbol.0, symbol.1)); + 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, |p, s| { - let kinds = p + check_map(input, expect, |program, symbol_table| { + let kinds = program .statements .iter() .map(|stmt| stmt.kind.as_ref().clone()) @@ -97,16 +95,16 @@ pub(super) fn check_classical_decls(input: &str, expect: &Expect) { for kind in &kinds { let (symbol_id, str) = match kind { super::ast::StmtKind::ClassicalDecl(decl) => (decl.symbol_id, decl.to_string()), - super::ast::StmtKind::IODeclaration(decl) => (decl.symbol_id, decl.to_string()), - super::ast::StmtKind::Assign(stmt) => (stmt.symbold_id, stmt.to_string()), - super::ast::StmtKind::AssignOp(stmt) => (stmt.symbold_id, stmt.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 = s.get_symbol_by_id(symbol_id).expect("getting symbol by id"); - value.push_str(&format!("[{}] {}", symbol.0, symbol.1)); + let symbol = &symbol_table[symbol_id]; + value.push_str(&format!("[{symbol_id}] {symbol}")); value.push('\n'); } @@ -149,7 +147,7 @@ fn check_map( assert!( !res.has_syntax_errors(), "syntax errors: {:?}", - res.parse_errors() + res.sytax_errors() ); let program = res.program.expect("no program"); @@ -201,7 +199,7 @@ fn check_map_all

( assert!( !res.has_syntax_errors(), "syntax errors: {:?}", - res.parse_errors() + res.sytax_errors() ); let program = res.program.expect("no program"); @@ -220,6 +218,7 @@ fn check_map_all

( } #[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"; @@ -252,6 +251,25 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { init_expr: Expr [204-205]: ty: Bit(true) kind: Lit: Int(1) + Stmt [211-227]: + annotations: + kind: ClassicalDeclarationStmt [211-227]: + symbol_id: 24 + ty_span: [211-215] + init_expr: Expr [220-226]: + ty: Bool(false) + kind: BinaryOpExpr: + op: AndL + lhs: Expr [220-221]: + ty: Err + kind: SymbolId(25) + rhs: Expr [225-226]: + ty: Bool(false) + kind: Cast [0-0]: + ty: Bool(false) + expr: Expr [225-226]: + ty: Bit(false) + kind: SymbolId(24) Stmt [140-154]: annotations: kind: ClassicalDeclarationStmt [140-154]: @@ -260,6 +278,33 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { init_expr: Expr [150-153]: ty: Angle(None, true) kind: Lit: Float(7.0) + Stmt [159-179]: + annotations: + kind: ClassicalDeclarationStmt [159-179]: + symbol_id: 27 + 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(26) + 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: 29 + ty_span: [74-77] + init_expr: Expr [82-83]: + ty: Err + kind: SymbolId(28) [Qsc.Qasm3.Compile.UndefinedSymbol @@ -269,6 +314,14 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { 2 | bool x = y && x; // undefined y, redefine x : ^ `---- + , Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Err to type Bool(false) + ,-[source2.qasm:2:14] + 1 | bit x = 1; + 2 | bool x = y && x; // undefined y, redefine x + : ^ + `---- , Qsc.Qasm3.Compile.RedefinedSymbol x Redefined symbol: x. @@ -295,6 +348,15 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { : ^ 5 | `---- + , Qsc.Qasm3.Compile.CannotCast + + x Cannot cast expression of type Err to type Bit(false) + ,-[source0.qasm:4:5] + 3 | include "source1.qasm"; + 4 | bit c = r; // undefined symbol r + : ^^^^^^^^^^ + 5 | + `---- ]"#]], ); } diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls.rs b/compiler/qsc_qasm3/src/semantic/tests/decls.rs index b9ed2cc462..ca1c08904f 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls.rs @@ -57,7 +57,15 @@ fn scalar_ty_designator_must_be_positive() { &expect![[r#" Program: version: - statements: + statements: + Stmt [0-10]: + annotations: + kind: ClassicalDeclarationStmt [0-10]: + symbol_id: 6 + ty_span: [0-7] + init_expr: Expr [0-0]: + ty: Err + kind: Err [Qsc.Qasm3.Compile.DesignatorMustBePositiveIntLiteral @@ -66,6 +74,21 @@ fn scalar_ty_designator_must_be_positive() { 1 | int[-5] i; : ^^ `---- + , Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: Converting Err + | to Q# type + ,-[test:1:1] + 1 | int[-5] i; + : ^^^^^^^ + `---- + , Qsc.Qasm3.Compile.NotSupported + + x Default values for Err are unsupported. are not supported. + ,-[test:1:1] + 1 | int[-5] i; + : ^^^^^^^^^^ + `---- ]"#]], ); } @@ -77,7 +100,23 @@ fn scalar_ty_designator_must_be_int_literal() { &expect![[r#" Program: version: - statements: + statements: + Stmt [0-12]: + annotations: + kind: ClassicalDeclarationStmt [0-12]: + symbol_id: 6 + ty_span: [0-9] + init_expr: Expr [0-0]: + ty: Err + kind: Err + Stmt [13-26]: + annotations: + kind: ClassicalDeclarationStmt [13-26]: + symbol_id: 7 + ty_span: [13-23] + init_expr: Expr [0-0]: + ty: Err + kind: Err [Qsc.Qasm3.Compile.DesignatorMustBePositiveIntLiteral @@ -86,6 +125,21 @@ fn scalar_ty_designator_must_be_int_literal() { 1 | int[size] i; float[0.0] j; : ^^^^ `---- + , Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: Converting Err + | to Q# type + ,-[test:1:1] + 1 | int[size] i; float[0.0] j; + : ^^^^^^^^^ + `---- + , Qsc.Qasm3.Compile.NotSupported + + x Default values for Err are unsupported. are not supported. + ,-[test:1:1] + 1 | int[size] i; float[0.0] j; + : ^^^^^^^^^^^^ + `---- , Qsc.Qasm3.Compile.DesignatorMustBePositiveIntLiteral x Designator must be a positive literal integer. @@ -93,6 +147,21 @@ fn scalar_ty_designator_must_be_int_literal() { 1 | int[size] i; float[0.0] j; : ^^^ `---- + , Qsc.Qasm3.Compile.Unimplemented + + x this statement is not yet handled during OpenQASM 3 import: Converting Err + | to Q# type + ,-[test:1:14] + 1 | int[size] i; float[0.0] j; + : ^^^^^^^^^^ + `---- + , Qsc.Qasm3.Compile.NotSupported + + x Default values for Err are unsupported. are not supported. + ,-[test:1:14] + 1 | int[size] i; float[0.0] j; + : ^^^^^^^^^^^^^ + `---- ]"#]], ); } diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs index 04bc074d19..8643afdd38 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs @@ -357,7 +357,15 @@ fn const_lit_decl_signed_float_lit_cast_neg() { ty_span: [6-11] init_expr: Expr [17-19]: ty: Angle(None, true) - kind: Lit: Float(-7.0) + 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) [6] Symbol [12-13]: name: x type: Angle(None, true) @@ -373,12 +381,23 @@ fn const_lit_decl_signed_int_lit_cast_neg_fails() { &expect![[r#" Program: version: - statements: + statements: + Stmt [0-19]: + annotations: + kind: ClassicalDeclarationStmt [0-19]: + symbol_id: 6 + 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.CannotAssignToType + [Qsc.Qasm3.Compile.CannotCast - x Cannot assign a value of Int(None, true) type to a classical variable of - | Angle(None, true) type. + x Cannot cast expression of type Int(None, true) to type Angle(None, true) ,-[test:1:1] 1 | const angle x = -7; : ^^^^^^^^^^^^^^^^^^^ diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/duration.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/duration.rs index a28b2a4110..870ea73d21 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/duration.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/duration.rs @@ -10,17 +10,32 @@ fn with_no_init_expr_has_generated_lit_expr() { check_classical_decl( "duration a;", &expect![[r#" - Program: - version: - statements: + Program: + version: + statements: + Stmt [0-11]: + annotations: + kind: ClassicalDeclarationStmt [0-11]: + symbol_id: 6 + ty_span: [0-8] + init_expr: Expr [0-0]: + ty: Duration(true) + kind: Err - [Qsc.Qasm3.Compile.NotSupported + [Qsc.Qasm3.Compile.NotSupported - x Duration type values are not supported. - ,-[test:1:1] - 1 | duration a; - : ^^^^^^^^ - `---- - ]"#]], + x Duration type values are not supported. + ,-[test:1:1] + 1 | duration a; + : ^^^^^^^^ + `---- + , Qsc.Qasm3.Compile.NotSupported + + x Default values for Duration(false) are unsupported. are not supported. + ,-[test:1:1] + 1 | duration a; + : ^^^^^^^^^^^ + `---- + ]"#]], ); } diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs index c04f74943f..9af5a1933e 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs @@ -352,17 +352,21 @@ fn const_lit_decl_signed_float_lit_cast_neg() { check_classical_decl( "const float x = -7.;", &expect![[r#" - ClassicalDeclarationStmt [0-20]: - symbol_id: 6 - ty_span: [6-11] - init_expr: Expr [17-19]: - ty: Float(None, true) - kind: Lit: Float(-7.0) - [6] Symbol [12-13]: - name: x - type: Float(None, true) - qsharp_type: Double - io_kind: Default"#]], + ClassicalDeclarationStmt [0-20]: + symbol_id: 6 + 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) + [6] Symbol [12-13]: + name: x + type: Float(None, true) + qsharp_type: Double + io_kind: Default"#]], ); } @@ -371,17 +375,25 @@ fn const_lit_decl_signed_int_lit_cast_neg() { check_classical_decl( "const float x = -7;", &expect![[r#" - ClassicalDeclarationStmt [0-19]: - symbol_id: 6 - ty_span: [6-11] - init_expr: Expr [17-18]: - ty: Float(None, true) - kind: Lit: Float(-7.0) - [6] Symbol [12-13]: - name: x - type: Float(None, true) - qsharp_type: Double - io_kind: Default"#]], + ClassicalDeclarationStmt [0-19]: + symbol_id: 6 + 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) + [6] Symbol [12-13]: + name: x + type: Float(None, true) + qsharp_type: Double + io_kind: Default"#]], ); } @@ -414,7 +426,15 @@ fn init_float_with_int_value_greater_than_safely_representable_values() { &expect![[r#" Program: version: - statements: + statements: + Stmt [0-27]: + annotations: + kind: ClassicalDeclarationStmt [0-27]: + symbol_id: 6 + ty_span: [0-5] + init_expr: Expr [10-26]: + ty: Int(None, true) + kind: Lit: Int(9007199254740993) [Qsc.Qasm3.Compile.InvalidCastValueRange @@ -424,6 +444,14 @@ fn init_float_with_int_value_greater_than_safely_representable_values() { 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; + : ^^^^^^^^^^^^^^^^ + `---- ]"#]], ); } @@ -438,8 +466,16 @@ fn init_float_with_int_value_equal_min_safely_representable_values() { symbol_id: 6 ty_span: [0-5] init_expr: Expr [11-27]: - ty: Float(None, true) - kind: Lit: Float(-9007199254740992.0) + 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) [6] Symbol [6-7]: name: a type: Float(None, false) @@ -447,26 +483,3 @@ fn init_float_with_int_value_equal_min_safely_representable_values() { io_kind: Default"#]], ); } - -#[test] -fn init_float_with_int_value_less_than_safely_representable_values() { - let min_exact_int = -(2i64.pow(f64::MANTISSA_DIGITS)); - let next = min_exact_int - 1; - check_classical_decl( - &format!("float a = {next};"), - &expect![[r#" - Program: - version: - statements: - - [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:12] - 1 | float a = -9007199254740993; - : ^^^^^^^^^^^^^^^^ - `---- - ]"#]], - ); -} diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs index be6373acae..2e7d7ed18d 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs @@ -15,7 +15,11 @@ fn implicit_bitness_int_negative() { ty_span: [0-3] init_expr: Expr [9-11]: ty: Int(None, true) - kind: Lit: Int(-42) + kind: UnaryOpExpr [9-11]: + op: Neg + expr: Expr [9-11]: + ty: Int(None, true) + kind: Lit: Int(42) [6] Symbol [4-5]: name: x type: Int(None, false) @@ -34,7 +38,11 @@ fn implicit_bitness_int_const_negative() { ty_span: [6-9] init_expr: Expr [15-17]: ty: Int(None, true) - kind: Lit: Int(-42) + kind: UnaryOpExpr [15-17]: + op: Neg + expr: Expr [15-17]: + ty: Int(None, true) + kind: Lit: Int(42) [6] Symbol [10-11]: name: x type: Int(None, true) @@ -348,22 +356,28 @@ fn const_explicit_bitness_int() { } #[test] -fn implicit_bitness_int_negative_float_decl_causes_semantic_error() { +fn implicit_bitness_int_negative_float_decl_is_runtime_conversion() { check_classical_decl( "int x = -42.;", &expect![[r#" - Program: - version: - statements: - - [Qsc.Qasm3.Compile.CannotAssignToType - - x Cannot assign a value of Float(None, true) type to a classical variable of - | Int(None, false) type. - ,-[test:1:1] - 1 | int x = -42.; - : ^^^^^^^^^^^^^ - `---- - ]"#]], + ClassicalDeclarationStmt [0-13]: + symbol_id: 6 + 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) + [6] 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/stretch.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/stretch.rs index 488931a1d5..8c9848f665 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/stretch.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/stretch.rs @@ -12,7 +12,15 @@ fn with_no_init_expr_has_generated_lit_expr() { &expect![[r#" Program: version: - statements: + statements: + Stmt [0-10]: + annotations: + kind: ClassicalDeclarationStmt [0-10]: + symbol_id: 6 + ty_span: [0-7] + init_expr: Expr [0-0]: + ty: Stretch(true) + kind: Err [Qsc.Qasm3.Compile.NotSupported @@ -21,6 +29,13 @@ fn with_no_init_expr_has_generated_lit_expr() { 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 index 0e7ecb3f53..f53b975b87 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs @@ -310,64 +310,82 @@ fn const_explicit_bitness_int() { } #[test] -fn assigning_uint_to_negative_lit_results_in_semantic_error() { +fn assigning_uint_to_negative_lit() { check_classical_decl( "const uint[10] x = -42;", &expect![[r#" - Program: - version: - statements: - - [Qsc.Qasm3.Compile.CannotAssignToType - - x Cannot assign a value of Int(None, true) type to a classical variable of - | UInt(Some(10), true) type. - ,-[test:1:1] - 1 | const uint[10] x = -42; - : ^^^^^^^^^^^^^^^^^^^^^^^ - `---- - ]"#]], + ClassicalDeclarationStmt [0-23]: + symbol_id: 6 + 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) + [6] Symbol [15-16]: + name: x + type: UInt(Some(10), true) + qsharp_type: Int + io_kind: Default"#]], ); } #[test] -fn implicit_bitness_uint_const_negative_decl_raises_semantic_error() { +fn implicit_bitness_uint_const_negative_decl() { check_classical_decl( "const uint x = -42;", &expect![[r#" - Program: - version: - statements: - - [Qsc.Qasm3.Compile.CannotAssignToType - - x Cannot assign a value of Int(None, true) type to a classical variable of - | UInt(None, true) type. - ,-[test:1:1] - 1 | const uint x = -42; - : ^^^^^^^^^^^^^^^^^^^ - `---- - ]"#]], + ClassicalDeclarationStmt [0-19]: + symbol_id: 6 + 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) + [6] Symbol [11-12]: + name: x + type: UInt(None, true) + qsharp_type: Int + io_kind: Default"#]], ); } #[test] -fn explicit_bitness_uint_const_negative_decl_raises_semantic_error() { +fn explicit_bitness_uint_const_negative_decl() { check_classical_decl( "const uint[32] x = -42;", &expect![[r#" - Program: - version: - statements: - - [Qsc.Qasm3.Compile.CannotAssignToType - - x Cannot assign a value of Int(None, true) type to a classical variable of - | UInt(Some(32), true) type. - ,-[test:1:1] - 1 | const uint[32] x = -42; - : ^^^^^^^^^^^^^^^^^^^^^^^ - `---- - ]"#]], + ClassicalDeclarationStmt [0-23]: + symbol_id: 6 + 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) + [6] 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 index 783217bc1d..6102868912 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/expression.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/expression.rs @@ -47,7 +47,7 @@ fn a() { ExprStmt [55-61]: expr: Expr [56-60]: ty: Bool(true) - kind: UnaryOpExpr: + kind: UnaryOpExpr [56-60]: op: NotL expr: Expr [56-60]: ty: Bool(true) 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 index 2aa0c1eec2..2f62c3202b 100644 --- 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 @@ -173,7 +173,7 @@ fn unop_not_logical_and_unop_not() { op: AndL lhs: Expr [57-58]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [57-58]: op: NotL expr: Expr [57-58]: ty: Bool(false) @@ -184,7 +184,7 @@ fn unop_not_logical_and_unop_not() { kind: SymbolId(6) rhs: Expr [63-64]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [63-64]: op: NotL expr: Expr [63-64]: ty: Bool(false) @@ -244,7 +244,7 @@ fn unop_not_logical_or_unop_not() { op: OrL lhs: Expr [57-58]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [57-58]: op: NotL expr: Expr [57-58]: ty: Bool(false) @@ -255,7 +255,7 @@ fn unop_not_logical_or_unop_not() { kind: SymbolId(6) rhs: Expr [63-64]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [63-64]: op: NotL expr: Expr [63-64]: ty: Bool(false) @@ -315,7 +315,7 @@ fn unop_not_logical_and() { op: AndL lhs: Expr [57-58]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [57-58]: op: NotL expr: Expr [57-58]: ty: Bool(false) @@ -382,7 +382,7 @@ fn unop_not_logical_or() { op: OrL lhs: Expr [57-58]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [57-58]: op: NotL expr: Expr [57-58]: ty: Bool(false) @@ -456,7 +456,7 @@ fn logical_and_unop_not() { kind: SymbolId(6) rhs: Expr [62-63]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [62-63]: op: NotL expr: Expr [62-63]: ty: Bool(false) @@ -523,7 +523,7 @@ fn logical_or_unop_not() { kind: SymbolId(6) rhs: Expr [62-63]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [62-63]: op: NotL expr: Expr [62-63]: ty: Bool(false) 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 index df79f99889..56579fd91c 100644 --- 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 @@ -157,14 +157,14 @@ fn unop_not_logical_and_unop_not() { op: AndL lhs: Expr [66-67]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [66-67]: op: NotL expr: Expr [66-67]: ty: Bool(false) kind: SymbolId(6) rhs: Expr [72-73]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [72-73]: op: NotL expr: Expr [72-73]: ty: Bool(false) @@ -220,14 +220,14 @@ fn unop_not_logical_or_unop_not() { op: OrL lhs: Expr [66-67]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [66-67]: op: NotL expr: Expr [66-67]: ty: Bool(false) kind: SymbolId(6) rhs: Expr [72-73]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [72-73]: op: NotL expr: Expr [72-73]: ty: Bool(false) @@ -283,7 +283,7 @@ fn unop_not_logical_and() { op: AndL lhs: Expr [66-67]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [66-67]: op: NotL expr: Expr [66-67]: ty: Bool(false) @@ -342,7 +342,7 @@ fn unop_not_logical_or() { op: OrL lhs: Expr [66-67]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [66-67]: op: NotL expr: Expr [66-67]: ty: Bool(false) @@ -404,7 +404,7 @@ fn logical_and_unop_not() { kind: SymbolId(6) rhs: Expr [71-72]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [71-72]: op: NotL expr: Expr [71-72]: ty: Bool(false) @@ -463,7 +463,7 @@ fn logical_or_unop_not() { kind: SymbolId(6) rhs: Expr [71-72]: ty: Bool(false) - kind: UnaryOpExpr: + kind: UnaryOpExpr [71-72]: op: NotL expr: Expr [71-72]: ty: Bool(false) 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 index b09516234a..02c6cb12cc 100644 --- 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 @@ -145,6 +145,14 @@ fn to_implicit_int_implicitly_fails() { init_expr: Expr [19-22]: ty: Angle(None, true) kind: Lit: Float(42.0) + Stmt [32-42]: + annotations: + kind: ClassicalDeclarationStmt [32-42]: + symbol_id: 7 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Angle(None, false) + kind: SymbolId(6) [Qsc.Qasm3.Compile.CannotCast @@ -180,6 +188,14 @@ fn to_explicit_int_implicitly_fails() { init_expr: Expr [19-22]: ty: Angle(None, true) kind: Lit: Float(42.0) + Stmt [32-46]: + annotations: + kind: ClassicalDeclarationStmt [32-46]: + symbol_id: 7 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Angle(None, false) + kind: SymbolId(6) [Qsc.Qasm3.Compile.CannotCast @@ -216,6 +232,14 @@ fn to_implicit_uint_implicitly_fails() { init_expr: Expr [19-22]: ty: Angle(None, true) kind: Lit: Float(42.0) + Stmt [32-43]: + annotations: + kind: ClassicalDeclarationStmt [32-43]: + symbol_id: 7 + ty_span: [32-36] + init_expr: Expr [41-42]: + ty: Angle(None, false) + kind: SymbolId(6) [Qsc.Qasm3.Compile.CannotCast @@ -250,8 +274,24 @@ fn negative_lit_to_implicit_uint_implicitly_fails() { symbol_id: 6 ty_span: [9-14] init_expr: Expr [20-23]: - ty: Angle(None, true) - kind: Lit: Float(-42.0) + 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: 7 + ty_span: [33-37] + init_expr: Expr [42-43]: + ty: Angle(None, false) + kind: SymbolId(6) [Qsc.Qasm3.Compile.CannotCast @@ -288,6 +328,14 @@ fn to_explicit_uint_implicitly_fails() { init_expr: Expr [19-22]: ty: Angle(None, true) kind: Lit: Float(42.0) + Stmt [32-47]: + annotations: + kind: ClassicalDeclarationStmt [32-47]: + symbol_id: 7 + ty_span: [32-40] + init_expr: Expr [45-46]: + ty: Angle(None, false) + kind: SymbolId(6) [Qsc.Qasm3.Compile.CannotCast @@ -324,6 +372,14 @@ fn to_explicit_bigint_implicitly_fails() { init_expr: Expr [19-22]: ty: Angle(None, true) kind: Lit: Float(42.0) + Stmt [32-46]: + annotations: + kind: ClassicalDeclarationStmt [32-46]: + symbol_id: 7 + ty_span: [32-39] + init_expr: Expr [44-45]: + ty: Angle(None, false) + kind: SymbolId(6) [Qsc.Qasm3.Compile.CannotCast @@ -360,6 +416,14 @@ fn to_implicit_float_implicitly_fails() { init_expr: Expr [19-22]: ty: Angle(None, true) kind: Lit: Float(42.0) + Stmt [32-44]: + annotations: + kind: ClassicalDeclarationStmt [32-44]: + symbol_id: 7 + ty_span: [32-37] + init_expr: Expr [42-43]: + ty: Angle(None, false) + kind: SymbolId(6) [Qsc.Qasm3.Compile.CannotCast @@ -396,6 +460,14 @@ fn to_explicit_float_implicitly_fails() { init_expr: Expr [19-22]: ty: Angle(None, true) kind: Lit: Float(42.0) + Stmt [32-48]: + annotations: + kind: ClassicalDeclarationStmt [32-48]: + symbol_id: 7 + ty_span: [32-41] + init_expr: Expr [46-47]: + ty: Angle(None, false) + kind: SymbolId(6) [Qsc.Qasm3.Compile.CannotCast @@ -432,6 +504,14 @@ fn to_implicit_complex_implicitly_fails() { init_expr: Expr [19-22]: ty: Angle(None, true) kind: Lit: Float(42.0) + Stmt [32-53]: + annotations: + kind: ClassicalDeclarationStmt [32-53]: + symbol_id: 7 + ty_span: [32-46] + init_expr: Expr [51-52]: + ty: Angle(None, false) + kind: SymbolId(6) [Qsc.Qasm3.Compile.CannotCast @@ -468,6 +548,14 @@ fn to_explicit_complex_implicitly_fails() { init_expr: Expr [19-22]: ty: Angle(None, true) kind: Lit: Float(42.0) + Stmt [32-57]: + annotations: + kind: ClassicalDeclarationStmt [32-57]: + symbol_id: 7 + ty_span: [32-50] + init_expr: Expr [55-56]: + ty: Angle(None, false) + kind: SymbolId(6) [Qsc.Qasm3.Compile.CannotCast 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 index 0c701f3601..7798b240e6 100644 --- 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 @@ -288,6 +288,14 @@ fn to_implicit_float_implicitly_fails() { init_expr: Expr [17-18]: ty: Bit(true) kind: Lit: Int(1) + Stmt [28-40]: + annotations: + kind: ClassicalDeclarationStmt [28-40]: + symbol_id: 7 + ty_span: [28-33] + init_expr: Expr [38-39]: + ty: Bit(false) + kind: SymbolId(6) [Qsc.Qasm3.Compile.CannotCast 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 index bd1de21f74..1af9e85783 100644 --- 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 @@ -26,6 +26,14 @@ fn to_bit_implicitly_fails() { init_expr: Expr [19-22]: ty: Float(None, true) kind: Lit: Float(42.0) + Stmt [32-42]: + annotations: + kind: ClassicalDeclarationStmt [32-42]: + symbol_id: 7 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Float(None, false) + kind: SymbolId(6) [Qsc.Qasm3.Compile.CannotCast @@ -61,6 +69,14 @@ fn explicit_width_to_bit_implicitly_fails() { 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: 7 + ty_span: [36-39] + init_expr: Expr [44-45]: + ty: Float(Some(64), false) + kind: SymbolId(6) [Qsc.Qasm3.Compile.CannotCast @@ -244,33 +260,37 @@ fn negative_lit_to_implicit_uint_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-24]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [20-23]: - ty: Float(None, true) - kind: Lit: Float(-42.0) - [6] Symbol [15-16]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [33-44]: - symbol_id: 7 - ty_span: [33-37] - init_expr: Expr [42-43]: - ty: UInt(None, false) - kind: Cast [0-0]: + ClassicalDeclarationStmt [9-24]: + symbol_id: 6 + ty_span: [9-14] + init_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) + [6] Symbol [15-16]: + name: x + type: Float(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [33-44]: + symbol_id: 7 + ty_span: [33-37] + init_expr: Expr [42-43]: ty: UInt(None, false) - expr: Expr [42-43]: - ty: Float(None, false) - kind: SymbolId(6) - [7] Symbol [38-39]: - name: y - type: UInt(None, false) - qsharp_type: Int - io_kind: Default - "#]], + kind: Cast [0-0]: + ty: UInt(None, false) + expr: Expr [42-43]: + ty: Float(None, false) + kind: SymbolId(6) + [7] Symbol [38-39]: + name: y + type: UInt(None, false) + qsharp_type: Int + io_kind: Default + "#]], ); } diff --git a/compiler/qsc_qasm3/src/semantic/tests/statements/box_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/box_stmt.rs index 4b731ec2d5..5089ce953f 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements/box_stmt.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/box_stmt.rs @@ -13,7 +13,10 @@ fn with_invalid_instruction_fails() { &expect![[r#" Program: version: - statements: + statements: + Stmt [0-26]: + annotations: + kind: Err [Qsc.Qasm3.Compile.ClassicalStmtInBox @@ -24,6 +27,14 @@ fn with_invalid_instruction_fails() { : ^^^^^^ 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 | `-> } + `---- ]"#]], ); } @@ -35,7 +46,10 @@ fn with_duration_fails() { &expect![[r#" Program: version: - statements: + statements: + Stmt [0-13]: + annotations: + kind: Err [Qsc.Qasm3.Compile.NotSupported @@ -44,6 +58,13 @@ fn with_duration_fails() { 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/for_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/for_stmt.rs index 28d52475a3..620d87a20e 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements/for_stmt.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/for_stmt.rs @@ -14,7 +14,21 @@ fn shadowing_loop_variable_in_single_stmt_body_fails() { &expect![[r#" Program: version: - statements: + statements: + Stmt [5-39]: + annotations: + kind: ForStmt [5-39]: + loop_variable: 6 + iterable: DiscreteSet [18-20]: + values: + body: Stmt [29-39]: + annotations: + kind: ClassicalDeclarationStmt [29-39]: + symbol_id: 6 + ty_span: [29-32] + init_expr: Expr [37-38]: + ty: Int(None, true) + kind: Lit: Int(2) [Qsc.Qasm3.Compile.RedefinedSymbol diff --git a/compiler/qsc_qasm3/src/semantic/tests/statements/if_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/if_stmt.rs index 4fb652259a..1083cbb167 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements/if_stmt.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/if_stmt.rs @@ -23,6 +23,21 @@ fn if_branch_doesnt_create_its_own_scope() { init_expr: Expr [13-14]: ty: Int(None, true) kind: Lit: Int(0) + 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: 6 + ty_span: [30-33] + init_expr: Expr [38-39]: + ty: Int(None, true) + kind: Lit: Int(1) + else_body: [Qsc.Qasm3.Compile.RedefinedSymbol @@ -57,6 +72,23 @@ fn else_branch_doesnt_create_its_own_scope() { init_expr: Expr [13-14]: ty: Int(None, true) kind: Lit: Int(0) + 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: 6 + ty_span: [42-45] + init_expr: Expr [50-51]: + ty: Int(None, true) + kind: Lit: Int(1) [Qsc.Qasm3.Compile.RedefinedSymbol diff --git a/compiler/qsc_qasm3/src/semantic/tests/statements/switch_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/switch_stmt.rs index 2ed89fa9f4..6fd0950ff9 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements/switch_stmt.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/switch_stmt.rs @@ -14,7 +14,21 @@ fn not_supported_before_version_3_1() { &expect![[r#" Program: version: 3.0 - statements: + 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 diff --git a/compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs index c1e5a7f644..91b9fdfb3e 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs @@ -23,6 +23,20 @@ fn single_stmt_body_doesnt_creates_its_own_scope() { init_expr: Expr [13-14]: ty: Int(None, true) kind: Lit: Int(0) + Stmt [20-42]: + annotations: + kind: 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: 6 + ty_span: [32-35] + init_expr: Expr [40-41]: + ty: Int(None, true) + kind: Lit: Int(1) [Qsc.Qasm3.Compile.RedefinedSymbol diff --git a/compiler/qsc_qasm3/src/tests.rs b/compiler/qsc_qasm3/src/tests.rs index 66c8c67e60..6b0a9df1dd 100644 --- a/compiler/qsc_qasm3/src/tests.rs +++ b/compiler/qsc_qasm3/src/tests.rs @@ -1,10 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::{ - parse::{QasmParseResult, QasmSource}, - qasm_to_program, CompilerConfig, OutputSemantics, ProgramType, QasmCompileUnit, QubitSemantics, -}; +use crate::runtime::RuntimeFunctions; +use crate::semantic::symbols::SymbolTable; +use crate::{CompilerConfig, OutputSemantics, ProgramType, QasmCompileUnit, QubitSemantics}; use miette::Report; use qsc::interpret::Error; use qsc::{ @@ -14,10 +13,8 @@ use qsc::{ }; 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; @@ -71,21 +68,124 @@ 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.expect("no program found"); + + let compiler = crate::compiler::QasmCompiler { + source_map: res.source_map, + config, + stmts: vec![], + runtime: RuntimeFunctions::empty(), + 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.expect("no program found"); + + let compiler = crate::compiler::QasmCompiler { + source_map: res.source_map, + config, + stmts: vec![], + runtime: RuntimeFunctions::empty(), + symbols: SymbolTable::default(), + 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,7 +209,7 @@ 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, { @@ -129,7 +229,7 @@ where pub(crate) fn parse_all

( path: P, sources: impl IntoIterator, Arc)>, -) -> miette::Result> +) -> miette::Result> where P: AsRef, { @@ -148,34 +248,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 +269,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 +296,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 +327,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); 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..6d742ed4e3 100644 --- a/compiler/qsc_qasm3/src/tests/declaration.rs +++ b/compiler/qsc_qasm3/src/tests/declaration.rs @@ -13,14 +13,13 @@ 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; @@ -43,9 +42,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,21 +57,16 @@ 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, ); + let unit = compile_with_config(source, config).expect("parse failed"); println!("{:?}", unit.errors); - assert!(unit.errors.len() == 5); + assert_eq!(unit.errors.len(), 5); for error in &unit.errors { assert!( error @@ -95,23 +87,21 @@ 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); + 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/float.rs b/compiler/qsc_qasm3/src/tests/declaration/float.rs index 5b4eced981..e8eb351e99 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 = " @@ -338,7 +322,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/integer.rs b/compiler/qsc_qasm3/src/tests/declaration/integer.rs index 529b9b1231..187431b62d 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 = " @@ -282,22 +266,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,16 +299,18 @@ fn const_explicit_bitness_int_decl() -> miette::Result<(), Vec> { } #[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..38cd18c777 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/io/explicit_input.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/io/explicit_input.rs @@ -195,7 +195,7 @@ 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] 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 736ccb6bda..0d4f1ba594 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 = " @@ -250,22 +234,6 @@ fn explicit_bitness_int_decl() -> miette::Result<(), Vec> { Ok(()) } -#[test] -fn const_explicit_bitness_int_decl() -> miette::Result<(), Vec> { - let source = " - const uint[10] x; - "; - - let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" - let x = 0; - "# - ] - .assert_eq(&qsharp); - Ok(()) -} - #[test] fn assigning_uint_to_negative_lit_results_in_semantic_error() { let source = " @@ -280,33 +248,3 @@ fn assigning_uint_to_negative_lit_results_in_semantic_error() { ] .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()); -} diff --git a/compiler/qsc_qasm3/src/tests/expression/binary.rs b/compiler/qsc_qasm3/src/tests/expression/binary.rs index f15be6380e..533e9323c5 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary.rs +++ b/compiler/qsc_qasm3/src/tests/expression/binary.rs @@ -33,8 +33,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 +47,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/implicit_cast_from_float.rs b/compiler/qsc_qasm3/src/tests/expression/implicit_cast_from_float.rs index 99d28f98dc..d9280621ed 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 @@ -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()); } diff --git a/compiler/qsc_qasm3/src/tests/output.rs b/compiler/qsc_qasm3/src/tests/output.rs index 711b64e9ad..4f6fdbd3fd 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,20 +28,14 @@ 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#" @@ -83,19 +76,14 @@ 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#" @@ -136,20 +124,14 @@ 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#" @@ -197,20 +179,14 @@ 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()); 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..7e0ee021ee 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 connfig = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::File, + Some("Test".into()), + None, ); + let unit = compile_with_config(source, connfig).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/statement/include.rs b/compiler/qsc_qasm3/src/tests/statement/include.rs index 7d096298a4..fc21457b00 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,19 +29,14 @@ 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 { diff --git a/compiler/qsc_qasm3/src/tests/statement/reset.rs b/compiler/qsc_qasm3/src/tests/statement/reset.rs index c6d9a7b86a..97698f9e2d 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,19 +23,14 @@ 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![ From 844be1b8ccde3f37a1b31b36501c2579bd01ab4f Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Fri, 21 Mar 2025 07:30:53 -0700 Subject: [PATCH 63/98] Unify compilation and error propagation. (#2247) --- compiler/qsc_qasm3/src/compile.rs | 5 -- compiler/qsc_qasm3/src/compile/tests.rs | 4 +- compiler/qsc_qasm3/src/compiler.rs | 59 +++++++++++++- compiler/qsc_qasm3/src/parse.rs | 4 +- .../src/parser/completion/word_kinds.rs | 3 - compiler/qsc_qasm3/src/semantic.rs | 18 +---- compiler/qsc_qasm3/src/semantic/lowerer.rs | 2 +- compiler/qsc_qasm3/src/semantic/tests.rs | 15 ++-- compiler/qsc_qasm3/src/tests.rs | 81 +++---------------- compiler/qsc_qasm3/src/tests/declaration.rs | 7 +- compiler/qsc_qasm3/src/tests/output.rs | 11 +-- .../src/tests/sample_circuits/bell_pair.rs | 7 +- .../tests/sample_circuits/rgqft_multiplier.rs | 5 +- .../qsc_qasm3/src/tests/statement/include.rs | 6 +- .../qsc_qasm3/src/tests/statement/reset.rs | 5 +- 15 files changed, 107 insertions(+), 125 deletions(-) diff --git a/compiler/qsc_qasm3/src/compile.rs b/compiler/qsc_qasm3/src/compile.rs index f36897d19e..f9d53b0714 100644 --- a/compiler/qsc_qasm3/src/compile.rs +++ b/compiler/qsc_qasm3/src/compile.rs @@ -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, diff --git a/compiler/qsc_qasm3/src/compile/tests.rs b/compiler/qsc_qasm3/src/compile/tests.rs index 72b0210d14..8c5fb59d12 100644 --- a/compiler/qsc_qasm3/src/compile/tests.rs +++ b/compiler/qsc_qasm3/src/compile/tests.rs @@ -18,7 +18,7 @@ fn programs_with_includes_with_includes_can_be_compiled() -> miette::Result<(), ("source2.qasm".into(), source2.into()), ]; - let unit = compile_all_fragments("source0.qasm", all_sources)?; + let unit = compile_all_fragments("source0.qasm", all_sources).map_err(|e| vec![e])?; print_compilation_errors(&unit); assert!(!unit.has_errors()); Ok(()) @@ -39,7 +39,7 @@ fn including_stdgates_multiple_times_causes_symbol_redifintion_errors( ("source2.qasm".into(), source2.into()), ]; - let unit = compile_all_fragments("source0.qasm", all_sources)?; + let unit = compile_all_fragments("source0.qasm", all_sources).map_err(|e| vec![e])?; 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 index eb71d6398c..fd1f51ac53 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::rc::Rc; +use std::{path::Path, rc::Rc, sync::Arc}; use num_bigint::BigInt; use qsc_data_structures::span::Span; @@ -19,6 +19,7 @@ use crate::{ build_top_level_ns_with_item, build_tuple_expr, build_unary_op_expr, build_while_stmt, build_wrapped_block_expr, map_qsharp_type_to_ast_ty, wrap_expr_in_parens, }, + io::{InMemorySourceResolver, SourceResolver}, parser::ast::{list_from_iter, List}, runtime::{get_runtime_function_decls, RuntimeFunctions}, semantic::{ @@ -36,6 +37,62 @@ use crate::{ use crate::semantic::ast as semast; use qsc_ast::ast::{self as qsast, NodeId, Package}; +pub fn compile_anon_with_config( + source: S, + config: CompilerConfig, +) -> miette::Result +where + S: AsRef, +{ + let path = std::path::PathBuf::from("Test.qasm"); + let sources = [( + Arc::from(path.display().to_string().as_str()), + Arc::from(source.as_ref()), + )]; + let resolver = InMemorySourceResolver::from_iter(sources); + let source = resolver.resolve(&path)?.1; + compile_with_config(source, &path, &resolver, config) +} + +pub fn compile_all_with_config

( + path: P, + sources: impl IntoIterator, Arc)>, + config: CompilerConfig, +) -> miette::Result +where + P: AsRef, +{ + let resolver = InMemorySourceResolver::from_iter(sources); + let source = resolver.resolve(path.as_ref())?.1; + compile_with_config(source, path, &resolver, config) +} + +pub fn compile_with_config( + source: S, + path: P, + resolver: &R, + config: CompilerConfig, +) -> miette::Result +where + S: AsRef, + P: AsRef, + R: SourceResolver, +{ + let res = crate::semantic::parse_source(source, path, resolver)?; + let program = res.program; + + let compiler = crate::compiler::QasmCompiler { + source_map: res.source_map, + config, + stmts: vec![], + runtime: RuntimeFunctions::empty(), + symbols: res.symbols, + errors: res.errors, + }; + + Ok(compiler.compile(&program)) +} + pub struct QasmCompiler { /// The source map of QASM sources for error reporting. pub source_map: SourceMap, 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/completion/word_kinds.rs b/compiler/qsc_qasm3/src/parser/completion/word_kinds.rs index f300e4d26c..8ad18f770d 100644 --- a/compiler/qsc_qasm3/src/parser/completion/word_kinds.rs +++ b/compiler/qsc_qasm3/src/parser/completion/word_kinds.rs @@ -125,7 +125,6 @@ impl WordKinds { self.iter().filter_map(|p| match p { WordKinds::PathExpr => Some(NameKind::Path(PathKind::Expr)), WordKinds::PathSegment => Some(NameKind::PathSegment), - WordKinds::PrimitiveClass => Some(NameKind::PrimitiveClass), _ => None, }) } @@ -163,8 +162,6 @@ pub enum NameKind { /// A path segment that follows a `.` /// A more specific name kind can only be inferred from a recovered AST. PathSegment, - /// A primitive class, like Eq, Exp, or Add. - PrimitiveClass, } /// A path (see: [`Predictions`]) diff --git a/compiler/qsc_qasm3/src/semantic.rs b/compiler/qsc_qasm3/src/semantic.rs index b63db4daa0..6b470711b6 100644 --- a/compiler/qsc_qasm3/src/semantic.rs +++ b/compiler/qsc_qasm3/src/semantic.rs @@ -7,7 +7,6 @@ use crate::parser::QasmSource; use lowerer::Lowerer; use qsc_frontend::compile::SourceMap; use qsc_frontend::error::WithSource; -use symbols::SymbolTable; use std::path::Path; @@ -26,7 +25,7 @@ pub struct QasmSemanticParseResult { pub source: QasmSource, pub source_map: SourceMap, pub symbols: self::symbols::SymbolTable, - pub program: Option, + pub program: self::ast::Program, pub errors: Vec>, } @@ -106,25 +105,14 @@ where R: SourceResolver, { let res = crate::parser::parse_source(source, path, resolver)?; - let errors = res.all_errors(); - // If there are syntax errors, return early - if res.source.has_errors() { - return Ok(QasmSemanticParseResult { - source: res.source, - source_map: res.source_map, - symbols: SymbolTable::default(), - program: None, - errors, - }); - } - let analyzer = Lowerer::new(res.source, res.source_map); let sem_res = analyzer.lower(); + let errors = sem_res.all_errors(); Ok(QasmSemanticParseResult { source: sem_res.source, source_map: sem_res.source_map, symbols: sem_res.symbols, program: sem_res.program, - errors: sem_res.errors, + errors, }) } diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index ed887adcca..4c47394b48 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -108,7 +108,7 @@ impl Lowerer { source: self.source, source_map: self.source_map, symbols: self.symbols, - program: Some(program), + program, errors: self.errors, } } diff --git a/compiler/qsc_qasm3/src/semantic/tests.rs b/compiler/qsc_qasm3/src/semantic/tests.rs index 1de94b14c7..268ace49fb 100644 --- a/compiler/qsc_qasm3/src/semantic/tests.rs +++ b/compiler/qsc_qasm3/src/semantic/tests.rs @@ -133,7 +133,7 @@ pub(super) fn check_stmt_kinds(input: &str, expect: &Expect) { fn check_map( input: S, expect: &Expect, - selector: impl FnOnce(&super::ast::Program, &super::SymbolTable) -> String, + selector: impl FnOnce(&super::ast::Program, &super::symbols::SymbolTable) -> String, ) where S: AsRef, { @@ -150,14 +150,12 @@ fn check_map( res.sytax_errors() ); - let program = res.program.expect("no program"); - if errors.is_empty() { - expect.assert_eq(&selector(&program, &res.symbols)); + expect.assert_eq(&selector(&res.program, &res.symbols)); } else { expect.assert_eq(&format!( "{}\n\n{:?}", - program, + res.program, errors .iter() .map(|e| Report::new(e.clone())) @@ -180,7 +178,7 @@ fn check_map_all

( path: P, sources: impl IntoIterator, Arc)>, expect: &Expect, - selector: impl FnOnce(&super::ast::Program, &super::SymbolTable) -> String, + selector: impl FnOnce(&super::ast::Program, &super::symbols::SymbolTable) -> String, ) where P: AsRef, { @@ -201,14 +199,13 @@ fn check_map_all

( "syntax errors: {:?}", res.sytax_errors() ); - let program = res.program.expect("no program"); if errors.is_empty() { - expect.assert_eq(&selector(&program, &res.symbols)); + expect.assert_eq(&selector(&res.program, &res.symbols)); } else { expect.assert_eq(&format!( "{}\n\n{:?}", - program, + res.program, errors .iter() .map(|e| Report::new(e.clone())) diff --git a/compiler/qsc_qasm3/src/tests.rs b/compiler/qsc_qasm3/src/tests.rs index 6b0a9df1dd..1c2a10bee5 100644 --- a/compiler/qsc_qasm3/src/tests.rs +++ b/compiler/qsc_qasm3/src/tests.rs @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::runtime::RuntimeFunctions; -use crate::semantic::symbols::SymbolTable; +use crate::compiler::compile_anon_with_config; use crate::{CompilerConfig, OutputSemantics, ProgramType, QasmCompileUnit, QubitSemantics}; use miette::Report; use qsc::interpret::Error; @@ -68,7 +67,7 @@ pub(crate) fn generate_qir_from_ast( ) } -fn compile(source: S) -> miette::Result> +fn compile(source: S) -> miette::Result where S: AsRef, { @@ -79,42 +78,13 @@ where Some("Test".into()), None, ); - compile_with_config(source, config) -} - -fn compile_with_config( - source: S, - config: CompilerConfig, -) -> miette::Result> -where - S: AsRef, -{ - let res = parse(source)?; - if res.has_syntax_errors() { - for e in res.sytax_errors() { - println!("{:?}", Report::new(e.clone())); - } - } - assert!(!res.has_syntax_errors()); - let program = res.program.expect("no program found"); - - let compiler = crate::compiler::QasmCompiler { - source_map: res.source_map, - config, - stmts: vec![], - runtime: RuntimeFunctions::empty(), - symbols: res.symbols, - errors: res.errors, - }; - - let unit = compiler.compile(&program); - Ok(unit) + compile_anon_with_config(source, config) } pub fn compile_all

( path: P, sources: impl IntoIterator, Arc)>, -) -> miette::Result> +) -> miette::Result where P: AsRef, { @@ -125,13 +95,13 @@ where Some("Test".into()), None, ); - compile_all_with_config(path, sources, config) + crate::compiler::compile_all_with_config(path, sources, config) } pub fn compile_all_fragments

( path: P, sources: impl IntoIterator, Arc)>, -) -> miette::Result> +) -> miette::Result where P: AsRef, { @@ -142,7 +112,7 @@ where None, None, ); - compile_all_with_config(path, sources, config) + crate::compiler::compile_all_with_config(path, sources, config) } fn compile_fragments(source: S) -> miette::Result> @@ -156,36 +126,11 @@ where 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.expect("no program found"); - - let compiler = crate::compiler::QasmCompiler { - source_map: res.source_map, - config, - stmts: vec![], - runtime: RuntimeFunctions::empty(), - symbols: SymbolTable::default(), - errors: res.errors, - }; - - let unit = compiler.compile(&program); - Ok(unit) + compile_anon_with_config(source, config).map_err(|e| vec![e]) } fn compile_qasm_to_qir(source: &str, profile: Profile) -> Result> { - let unit = compile(source)?; + let unit = compile(source).map_err(|e| vec![e])?; 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| { @@ -256,7 +201,7 @@ pub fn compile_qasm_to_qsharp_file(source: &str) -> miette::Result miette::Result miette::Result<(), Vec> { None, None, ); - let unit = compile_with_config(source, config).expect("parse failed"); + let unit = compile_anon_with_config(source, config).expect("parse failed"); println!("{:?}", unit.errors); assert_eq!(unit.errors.len(), 5); for error in &unit.errors { @@ -94,7 +95,7 @@ fn stretch() { None, None, ); - let unit = compile_with_config(source, config).expect("parse failed"); + let unit = compile_anon_with_config(source, config).expect("parse failed"); assert!(unit.has_errors()); println!("{:?}", unit.errors); assert!(unit.errors.len() == 2); diff --git a/compiler/qsc_qasm3/src/tests/output.rs b/compiler/qsc_qasm3/src/tests/output.rs index 4f6fdbd3fd..b991b88e51 100644 --- a/compiler/qsc_qasm3/src/tests/output.rs +++ b/compiler/qsc_qasm3/src/tests/output.rs @@ -2,6 +2,7 @@ // Licensed under the MIT License. use crate::{ + compiler::compile_anon_with_config, tests::{fail_on_compilation_errors, gen_qsharp}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; @@ -9,7 +10,7 @@ use expect_test::expect; use miette::Report; use qsc::target::Profile; -use super::{compile_qasm_to_qir, compile_with_config}; +use super::compile_qasm_to_qir; #[test] fn using_re_semantics_removes_output() -> miette::Result<(), Vec> { @@ -35,7 +36,7 @@ fn using_re_semantics_removes_output() -> miette::Result<(), Vec> { Some("Test".into()), None, ); - let unit = compile_with_config(source, config).expect("parse failed"); + let unit = compile_anon_with_config(source, config).expect("parse failed"); fail_on_compilation_errors(&unit); let qsharp = gen_qsharp(&unit.package.expect("no package found")); expect![[r#" @@ -83,7 +84,7 @@ fn using_qasm_semantics_captures_all_classical_decls_as_output() -> miette::Resu Some("Test".into()), None, ); - let unit = compile_with_config(source, config).expect("parse failed"); + let unit = compile_anon_with_config(source, config).expect("parse failed"); fail_on_compilation_errors(&unit); let qsharp = gen_qsharp(&unit.package.expect("no package found")); expect![[r#" @@ -131,7 +132,7 @@ fn using_qiskit_semantics_only_bit_array_is_captured_and_reversed( Some("Test".into()), None, ); - let unit = compile_with_config(source, config).expect("parse failed"); + let unit = compile_anon_with_config(source, config).expect("parse failed"); fail_on_compilation_errors(&unit); let qsharp = gen_qsharp(&unit.package.expect("no package found")); expect![[r#" @@ -186,7 +187,7 @@ c2[2] = measure q[4]; Some("Test".into()), None, ); - let unit = compile_with_config(source, config).expect("parse failed"); + let unit = compile_anon_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()); 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 7e0ee021ee..e0643fb9c2 100644 --- a/compiler/qsc_qasm3/src/tests/sample_circuits/bell_pair.rs +++ b/compiler/qsc_qasm3/src/tests/sample_circuits/bell_pair.rs @@ -2,7 +2,8 @@ // Licensed under the MIT License. use crate::{ - tests::{compile_with_config, gen_qsharp, print_compilation_errors}, + compiler::compile_anon_with_config, + tests::{gen_qsharp, print_compilation_errors}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; @@ -22,14 +23,14 @@ c[1] = measure q[1]; fn it_compiles() { let source = SOURCE; - let connfig = CompilerConfig::new( + let config = CompilerConfig::new( QubitSemantics::Qiskit, OutputSemantics::OpenQasm, ProgramType::File, Some("Test".into()), None, ); - let unit = compile_with_config(source, connfig).expect("parse failed"); + let unit = compile_anon_with_config(source, config).expect("parse failed"); print_compilation_errors(&unit); assert!(!unit.has_errors()); 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 ee0f9387a7..e4ca4ace56 100644 --- a/compiler/qsc_qasm3/src/tests/sample_circuits/rgqft_multiplier.rs +++ b/compiler/qsc_qasm3/src/tests/sample_circuits/rgqft_multiplier.rs @@ -2,7 +2,8 @@ // Licensed under the MIT License. use crate::{ - tests::{compile_with_config, gen_qsharp, print_compilation_errors}, + compiler::compile_anon_with_config, + tests::{gen_qsharp, print_compilation_errors}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; @@ -17,7 +18,7 @@ fn it_compiles() { Some("Test".into()), None, ); - let unit = compile_with_config(source, config).expect("parse failed"); + let unit = compile_anon_with_config(source, config).expect("parse failed"); print_compilation_errors(&unit); assert!(!unit.has_errors()); diff --git a/compiler/qsc_qasm3/src/tests/statement/include.rs b/compiler/qsc_qasm3/src/tests/statement/include.rs index fc21457b00..6d3879f262 100644 --- a/compiler/qsc_qasm3/src/tests/statement/include.rs +++ b/compiler/qsc_qasm3/src/tests/statement/include.rs @@ -2,8 +2,8 @@ // Licensed under the MIT License. use crate::{ - tests::{compile_all_with_config, qsharp_from_qasm_compilation}, - CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, + compiler::compile_all_with_config, tests::qsharp_from_qasm_compilation, CompilerConfig, + OutputSemantics, ProgramType, QubitSemantics, }; use expect_test::expect; use miette::Report; @@ -36,7 +36,7 @@ fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { Some("Test".into()), None, ); - let r = compile_all_with_config("source0.qasm", all_sources, config)?; + let r = compile_all_with_config("source0.qasm", all_sources, config).map_err(|e| vec![e])?; let qsharp = qsharp_from_qasm_compilation(r)?; expect![[r#" namespace qasm3_import { diff --git a/compiler/qsc_qasm3/src/tests/statement/reset.rs b/compiler/qsc_qasm3/src/tests/statement/reset.rs index 97698f9e2d..48ffa34f49 100644 --- a/compiler/qsc_qasm3/src/tests/statement/reset.rs +++ b/compiler/qsc_qasm3/src/tests/statement/reset.rs @@ -2,7 +2,8 @@ // Licensed under the MIT License. use crate::{ - tests::{compile_with_config, fail_on_compilation_errors, gen_qsharp}, + compiler::compile_anon_with_config, + tests::{fail_on_compilation_errors, gen_qsharp}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; use expect_test::expect; @@ -30,7 +31,7 @@ fn reset_calls_are_generated_from_qasm() -> miette::Result<(), Vec> { Some("Test".into()), None, ); - let unit = compile_with_config(source, config)?; + let unit = compile_anon_with_config(source, config).map_err(|e| vec![e])?; fail_on_compilation_errors(&unit); let qsharp = gen_qsharp(&unit.package.expect("no package found")); expect![ From ae8e060902e4c427dbdce9b0ece4a083fac4dc83 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Mon, 24 Mar 2025 16:11:16 -0700 Subject: [PATCH 64/98] Add gate calls and the AST items it depends on to the new QASM3 compiler (#2246) This PR adds gate calls and the AST items it depends on to the new QASM3 compiler: - [x] Constant evaluation (needed for array sizes and type widths). - [x] Unit tests. - [x] QuantumDeclStmt - [x] IndexedAssignStmt. - [x] Bitarrays. - [x] MeasureExpr / MeasureStmt. - [x] Compilation to Result type (introduces `LiteralKind::Bit(bool)`) variant. - [x] GatecallStmt - [x] barrier - [x] reset --- compiler/qsc_qasm3/src/compiler.rs | 312 ++- compiler/qsc_qasm3/src/parser/ast.rs | 71 +- .../qsc_qasm3/src/parser/ast/display_utils.rs | 4 +- compiler/qsc_qasm3/src/parser/expr.rs | 44 +- compiler/qsc_qasm3/src/parser/expr/tests.rs | 28 +- compiler/qsc_qasm3/src/parser/mut_visit.rs | 38 +- compiler/qsc_qasm3/src/parser/stmt.rs | 21 +- .../src/parser/stmt/tests/barrier.rs | 27 +- .../src/parser/stmt/tests/box_stmt.rs | 36 +- .../src/parser/stmt/tests/classical_decl.rs | 28 +- .../qsc_qasm3/src/parser/stmt/tests/delay.rs | 30 +- .../src/parser/stmt/tests/for_loops.rs | 18 +- .../src/parser/stmt/tests/gate_call.rs | 129 +- .../qsc_qasm3/src/parser/stmt/tests/gphase.rs | 57 +- .../src/parser/stmt/tests/if_stmt.rs | 54 +- .../stmt/tests/invalid_stmts/gate_calls.rs | 191 +- .../stmt/tests/invalid_stmts/measure.rs | 109 +- .../src/parser/stmt/tests/measure.rs | 87 +- .../src/parser/stmt/tests/old_style_decl.rs | 2 + .../src/parser/stmt/tests/quantum_decl.rs | 4 + .../qsc_qasm3/src/parser/stmt/tests/reset.rs | 30 +- .../src/parser/stmt/tests/while_loops.rs | 18 +- compiler/qsc_qasm3/src/semantic/ast.rs | 132 +- .../qsc_qasm3/src/semantic/ast/const_eval.rs | 628 ++++++ compiler/qsc_qasm3/src/semantic/error.rs | 26 + compiler/qsc_qasm3/src/semantic/lowerer.rs | 789 ++++++-- compiler/qsc_qasm3/src/semantic/symbols.rs | 105 +- compiler/qsc_qasm3/src/semantic/tests.rs | 24 +- .../qsc_qasm3/src/semantic/tests/decls.rs | 97 +- .../src/semantic/tests/decls/angle.rs | 82 +- .../qsc_qasm3/src/semantic/tests/decls/bit.rs | 55 +- .../src/semantic/tests/decls/bool.rs | 24 +- .../src/semantic/tests/decls/complex.rs | 156 +- .../src/semantic/tests/decls/creg.rs | 31 +- .../src/semantic/tests/decls/duration.rs | 2 +- .../src/semantic/tests/decls/float.rs | 360 ++-- .../qsc_qasm3/src/semantic/tests/decls/int.rs | 88 +- .../src/semantic/tests/decls/qreg.rs | 28 +- .../src/semantic/tests/decls/stretch.rs | 2 +- .../src/semantic/tests/decls/uint.rs | 76 +- .../binary/arithmetic_conversions.rs | 336 ++-- .../binary/comparison/bit_to_bit.rs | 160 +- .../binary/comparison/bool_to_bool.rs | 160 +- .../binary/comparison/float_to_float.rs | 120 +- .../binary/comparison/int_to_int.rs | 120 +- .../binary/comparison/uint_to_uint.rs | 96 +- .../semantic/tests/expression/binary/ident.rs | 198 +- .../expression/implicit_cast_from_angle.rs | 276 +-- .../expression/implicit_cast_from_bit.rs | 84 +- .../expression/implicit_cast_from_bool.rs | 416 ++-- .../expression/implicit_cast_from_float.rs | 542 +++--- .../expression/implicit_cast_from_int.rs | 566 +++--- .../src/semantic/tests/statements/for_stmt.rs | 22 +- .../src/semantic/tests/statements/if_stmt.rs | 84 +- .../semantic/tests/statements/switch_stmt.rs | 24 +- .../semantic/tests/statements/while_stmt.rs | 30 +- compiler/qsc_qasm3/src/semantic/types.rs | 267 +-- .../expression/implicit_cast_from_bit.rs | 2 +- .../expression/implicit_cast_from_bitarray.rs | 8 +- .../expression/implicit_cast_from_float.rs | 2 +- compiler/qsc_qasm3/src/tests/statement.rs | 1 + .../src/tests/statement/const_eval.rs | 1732 +++++++++++++++++ .../src/tests/statement/gate_call.rs | 40 + .../qsc_qasm3/src/tests/statement/if_stmt.rs | 2 +- .../qsc_qasm3/src/tests/statement/measure.rs | 32 +- .../qsc_qasm3/src/tests/statement/switch.rs | 16 +- .../src/tests/statement/while_loop.rs | 2 +- 67 files changed, 6279 insertions(+), 3102 deletions(-) create mode 100644 compiler/qsc_qasm3/src/semantic/ast/const_eval.rs create mode 100644 compiler/qsc_qasm3/src/tests/statement/const_eval.rs diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index fd1f51ac53..b23563aa43 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -9,29 +9,37 @@ use qsc_frontend::{compile::SourceMap, error::WithSource}; use crate::{ ast_builder::{ - build_arg_pat, build_array_reverse_expr, build_assignment_statement, build_binary_expr, - build_cast_call, build_cast_call_two_params, build_classical_decl, build_complex_from_expr, - build_convert_call_expr, build_if_expr_then_expr_else_expr, build_implicit_return_stmt, - 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_math_call_from_exprs, build_math_call_no_params, build_operation_with_stmts, - build_path_ident_expr, build_stmt_semi_from_expr, build_stmt_semi_from_expr_with_span, - build_top_level_ns_with_item, build_tuple_expr, build_unary_op_expr, build_while_stmt, - build_wrapped_block_expr, map_qsharp_type_to_ast_ty, wrap_expr_in_parens, + build_arg_pat, build_array_reverse_expr, build_assignment_statement, build_barrier_call, + build_binary_expr, build_cast_call, build_cast_call_two_params, build_classical_decl, + build_complex_from_expr, build_convert_call_expr, build_expr_array_expr, + build_gate_call_param_expr, build_gate_call_with_params_and_callee, + build_if_expr_then_expr_else_expr, build_implicit_return_stmt, + build_indexed_assignment_statement, build_lit_bigint_expr, build_lit_bool_expr, + build_lit_complex_expr, build_lit_double_expr, build_lit_int_expr, + build_lit_result_array_expr_from_bitstring, build_lit_result_expr, + build_managed_qubit_alloc, build_math_call_from_exprs, build_math_call_no_params, + build_measure_call, build_operation_with_stmts, build_path_ident_expr, build_reset_call, + build_stmt_semi_from_expr, build_stmt_semi_from_expr_with_span, + build_top_level_ns_with_item, build_tuple_expr, build_unary_op_expr, + build_unmanaged_qubit_alloc, build_unmanaged_qubit_alloc_array, build_while_stmt, + build_wrapped_block_expr, managed_qubit_alloc_array, map_qsharp_type_to_ast_ty, + wrap_expr_in_parens, }, io::{InMemorySourceResolver, SourceResolver}, parser::ast::{list_from_iter, List}, runtime::{get_runtime_function_decls, RuntimeFunctions}, semantic::{ ast::{ - BinaryOpExpr, Cast, DiscreteSet, Expr, FunctionCall, IndexElement, IndexExpr, IndexSet, - IndexedIdent, LiteralKind, MeasureExpr, TimeUnit, UnaryOpExpr, + BinaryOpExpr, Cast, DiscreteSet, Expr, FunctionCall, GateOperand, GateOperandKind, + IndexElement, IndexExpr, IndexSet, IndexedIdent, LiteralKind, MeasureExpr, TimeUnit, + UnaryOpExpr, }, symbols::{IOKind, Symbol, SymbolId, SymbolTable}, types::{ArrayDimensions, Type}, SemanticErrorKind, }, CompilerConfig, OperationSignature, OutputSemantics, ProgramType, QasmCompileUnit, + QubitSemantics, }; use crate::semantic::ast as semast; @@ -355,10 +363,11 @@ impl QasmCompiler { 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::Measure(stmt) => self.compile_measure_stmt(stmt), + semast::StmtKind::MeasureArrow(stmt) => self.compile_measure_stmt(stmt), semast::StmtKind::Pragma(stmt) => self.compile_pragma_stmt(stmt), semast::StmtKind::QuantumGateDefinition(stmt) => self.compile_gate_decl_stmt(stmt), - semast::StmtKind::QuantumDecl(stmt) => self.compile_quantum_decl_stmt(stmt), + 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), @@ -377,8 +386,7 @@ impl QasmCompiler { } fn compile_assign_stmt(&mut self, stmt: &semast::AssignStmt) -> Option { - let symbol = &self.symbols[stmt.symbol_id]; - let symbol = symbol.clone(); + let symbol = self.symbols[stmt.symbol_id].clone(); let name = &symbol.name; let stmt_span = stmt.span; @@ -394,8 +402,40 @@ impl QasmCompiler { &mut self, stmt: &semast::IndexedAssignStmt, ) -> Option { - self.push_unimplemented_error_message("indexed assignment statements", stmt.span); - None + let symbol = self.symbols[stmt.symbol_id].clone(); + + let indices: Vec<_> = stmt + .indices + .iter() + .filter_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; + } + + if indices.len() != stmt.indices.len() { + return None; + } + + // Use the `?` operator after compiling checking all other errors. + let (rhs, index_expr) = (rhs?, 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 { @@ -404,8 +444,20 @@ impl QasmCompiler { } fn compile_barrier_stmt(&mut self, stmt: &semast::BarrierStmt) -> Option { - self.push_unimplemented_error_message("barrier statements", stmt.span); - None + let qubits: Vec<_> = stmt + .qubits + .iter() + .filter_map(|q| self.compile_gate_operand(q)) + .collect(); + + if stmt.qubits.len() != qubits.len() { + // if any of the qubit arguments failed to compile we can't proceed. + // This can happen if the qubit is not defined. + return None; + } + + self.runtime.insert(RuntimeFunctions::Barrier); + Some(build_barrier_call(stmt.span)) } fn compile_box_stmt(&mut self, stmt: &semast::BoxStmt) -> Option { @@ -443,8 +495,7 @@ impl QasmCompiler { &mut self, decl: &semast::ClassicalDeclarationStmt, ) -> Option { - let symbol = &self.symbols[decl.symbol_id]; - let symbol = symbol.clone(); + 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; @@ -453,18 +504,10 @@ impl QasmCompiler { let qsharp_ty = &symbol.qsharp_ty; let expr = decl.init_expr.as_ref(); - let stmt = match expr { - semast::ValueExpression::Expr(expr) => { - let expr = self.compile_expr(expr)?; - build_classical_decl( - name, is_const, ty_span, decl_span, name_span, qsharp_ty, expr, - ) - } - semast::ValueExpression::Measurement(expr) => { - let expr = self.compile_measure_expr(expr)?; - build_stmt_semi_from_expr_with_span(expr, decl.span) - } - }; + 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) } @@ -480,7 +523,7 @@ impl QasmCompiler { } fn compile_delay_stmt(&mut self, stmt: &semast::DelayStmt) -> Option { - self.push_unimplemented_error_message("dealy statements", stmt.span); + self.push_unimplemented_error_message("delay statements", stmt.span); None } @@ -510,11 +553,101 @@ impl QasmCompiler { } fn compile_gate_call_stmt(&mut self, stmt: &semast::GateCall) -> Option { - self.push_unimplemented_error_message("gate call statements", stmt.span); - None + let symbol = self.symbols[stmt.symbol_id].clone(); + if symbol.name == "U" { + self.runtime |= RuntimeFunctions::U; + } + let mut qubits: Vec<_> = stmt + .qubits + .iter() + .filter_map(|q| self.compile_gate_operand(q)) + .collect(); + let args: Vec<_> = stmt + .args + .iter() + .filter_map(|arg| self.compile_expr(arg)) + .collect(); + + if qubits.len() != stmt.qubits.len() || args.len() != stmt.args.len() { + return None; + } + + // Take the number of qubit args that the gates expects from the source qubits. + let gate_qubits = qubits.split_off(qubits.len() - 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)?; + self.runtime |= RuntimeFunctions::Pow; + 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() - *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() - *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); + } + } + } + + // This should never be reached, since semantic analysis during lowering + // makes sure the arities match. + if !qubits.is_empty() { + return None; + } + + let expr = build_gate_call_with_params_and_callee(args, callee, stmt.span); + Some(build_stmt_semi_from_expr(expr)) } fn compile_gphase_stmt(&mut self, stmt: &semast::GPhase) -> Option { + self.runtime |= RuntimeFunctions::Gphase; self.push_unimplemented_error_message("gphase statements", stmt.span); None } @@ -562,7 +695,7 @@ impl QasmCompiler { Some(stmt) } - fn compile_measure_stmt(&mut self, stmt: &semast::MeasureStmt) -> Option { + fn compile_measure_stmt(&mut self, stmt: &semast::MeasureArrowStmt) -> Option { self.push_unimplemented_error_message("measure statements", stmt.span); None } @@ -580,17 +713,46 @@ impl QasmCompiler { None } - fn compile_quantum_decl_stmt( + 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::QubitDeclaration, + stmt: &semast::QubitArrayDeclaration, ) -> Option { - self.push_unimplemented_error_message("quantum decl statements", stmt.span); - None + 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 { - self.push_unimplemented_error_message("reset statements", stmt.span); - None + 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 { @@ -604,7 +766,7 @@ impl QasmCompiler { } fn compile_while_stmt(&mut self, stmt: &semast::WhileLoop) -> Option { - let condition = self.compile_expr(&stmt.while_condition)?; + let condition = self.compile_expr(&stmt.condition)?; match &*stmt.body.kind { semast::StmtKind::Block(block) => { let block = self.compile_block(block); @@ -628,16 +790,15 @@ impl QasmCompiler { } fn compile_expr(&mut self, expr: &semast::Expr) -> Option { - let span = expr.span; match expr.kind.as_ref() { semast::ExprKind::Err => { // todo: determine if we should push an error here // Are we going to allow trying to compile a program with semantic errors? None } - semast::ExprKind::Ident(symbol_id) => self.compile_ident_expr(*symbol_id, span), + semast::ExprKind::Ident(symbol_id) => self.compile_ident_expr(*symbol_id), semast::ExprKind::IndexedIdentifier(indexed_ident) => { - self.compile_indexed_ident_expr(indexed_ident, span) + 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) => { @@ -650,11 +811,13 @@ impl QasmCompiler { semast::ExprKind::Cast(cast) => self.compile_cast_expr(cast), semast::ExprKind::IndexExpr(index_expr) => self.compile_index_expr(index_expr), semast::ExprKind::Paren(pexpr) => self.compile_paren_expr(pexpr, expr.span), + semast::ExprKind::Measure(expr) => self.compile_measure_expr(expr), } } - fn compile_ident_expr(&mut self, symbol_id: SymbolId, span: Span) -> Option { + fn compile_ident_expr(&mut self, symbol_id: SymbolId) -> Option { let symbol = &self.symbols[symbol_id]; + let span = symbol.span; let expr = match symbol.name.as_str() { "euler" | "ℇ" => build_math_call_no_params("E", span), "pi" | "π" => build_math_call_no_params("PI", span), @@ -677,11 +840,8 @@ impl QasmCompiler { /// 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, - span: Span, - ) -> Option { + fn compile_indexed_ident_expr(&mut self, indexed_ident: &IndexedIdent) -> Option { + let span = indexed_ident.span; let index: Vec<_> = indexed_ident .indices .iter() @@ -754,6 +914,7 @@ impl QasmCompiler { 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) @@ -820,8 +981,29 @@ impl QasmCompiler { } fn compile_measure_expr(&mut self, expr: &MeasureExpr) -> Option { - self.push_unimplemented_error_message("measure expressions", expr.span); - None + 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; + let expr = build_measure_call(arg, name_span, operand_span, call_span); + Some(expr) + } + + fn compile_gate_operand(&mut self, op: &GateOperand) -> Option { + 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); + let ident = build_path_ident_expr(hw.name.clone(), hw.span, op.span); + Some(ident) + } + GateOperandKind::Expr(expr) => self.compile_expr(expr), + GateOperandKind::Err => None, + } } fn compile_index_element(&mut self, elem: &IndexElement) -> Option { @@ -837,7 +1019,15 @@ impl QasmCompiler { } fn compile_index_set(&mut self, set: &IndexSet) -> Option { - self.push_unimplemented_error_message("index set expressions", set.span); + // 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); None } @@ -846,6 +1036,10 @@ impl QasmCompiler { None } + fn compile_bit_literal(value: bool, span: Span) -> Option { + Some(build_lit_result_expr(value.into(), span)) + } + fn compile_bool_literal(value: bool, span: Span) -> Option { Some(build_lit_bool_expr(value, span)) } @@ -862,7 +1056,11 @@ impl QasmCompiler { fn compile_bitstring_literal(value: &BigInt, width: u32, span: Span) -> Option { let width = width as usize; - let bitstring = format!("Bitstring(\"{:0>width$}\")", value.to_str_radix(2)); + let bitstring = if value == &BigInt::ZERO && width == 0 { + "Bitstring(\"\")".to_string() + } else { + format!("Bitstring(\"{:0>width$}\")", value.to_str_radix(2)) + }; Some(build_lit_result_array_expr_from_bitstring(bitstring, span)) } diff --git a/compiler/qsc_qasm3/src/parser/ast.rs b/compiler/qsc_qasm3/src/parser/ast.rs index eafa672e70..cdcd499e30 100644 --- a/compiler/qsc_qasm3/src/parser/ast.rs +++ b/compiler/qsc_qasm3/src/parser/ast.rs @@ -147,6 +147,7 @@ impl WithSpan for Path { #[derive(Clone, Debug)] pub struct MeasureExpr { pub span: Span, + pub measure_token_span: Span, pub operand: GateOperand, } @@ -248,29 +249,51 @@ impl Display for UnaryOp { } #[derive(Clone, Debug, Default)] -pub enum GateOperand { +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 GateOperand { +impl Display for GateOperandKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - GateOperand::IndexedIdent(ident) => write!(f, "{ident}"), - GateOperand::HardwareQubit(qubit) => write!(f, "{qubit}"), - GateOperand::Err => write!(f, "Error"), + Self::IndexedIdent(ident) => write!(f, "{ident}"), + Self::HardwareQubit(qubit) => write!(f, "{qubit}"), + Self::Err => write!(f, "Error"), } } } -impl WithSpan for GateOperand { +impl WithSpan for GateOperandKind { fn with_span(self, span: Span) -> Self { match self { - GateOperand::IndexedIdent(ident) => GateOperand::IndexedIdent(ident.with_span(span)), - GateOperand::HardwareQubit(qubit) => GateOperand::HardwareQubit(qubit.with_span(span)), - GateOperand::Err => GateOperand::Err, + Self::IndexedIdent(ident) => Self::IndexedIdent(ident.with_span(span)), + Self::HardwareQubit(qubit) => Self::HardwareQubit(qubit.with_span(span)), + Self::Err => Self::Err, } } } @@ -335,7 +358,7 @@ pub enum StmtKind { GPhase(GPhase), Include(IncludeStmt), IODeclaration(IODeclaration), - Measure(MeasureStmt), + Measure(MeasureArrowStmt), Pragma(Pragma), QuantumGateDefinition(QuantumGateDefinition), QuantumDecl(QubitDeclaration), @@ -445,12 +468,14 @@ impl Display for BarrierStmt { #[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) } } @@ -991,6 +1016,7 @@ impl Display for IncludeStmt { #[derive(Clone, Debug)] pub struct QubitDeclaration { pub span: Span, + pub ty_span: Span, pub qubit: Ident, pub size: Option, } @@ -998,6 +1024,7 @@ pub struct QubitDeclaration { 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()) } @@ -1110,15 +1137,15 @@ impl Display for BoxStmt { } #[derive(Clone, Debug)] -pub struct MeasureStmt { +pub struct MeasureArrowStmt { pub span: Span, pub measurement: MeasureExpr, pub target: Option>, } -impl Display for MeasureStmt { +impl Display for MeasureArrowStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - writeln_header(f, "MeasureStmt", self.span)?; + writeln_header(f, "MeasureArrowStmt", self.span)?; writeln_field(f, "measurement", &self.measurement)?; write_opt_field(f, "target", self.target.as_ref()) } @@ -1129,7 +1156,7 @@ pub struct ClassicalDeclarationStmt { pub span: Span, pub ty: Box, pub identifier: Ident, - pub init_expr: Option>, + pub init_expr: Option>, } impl Display for ClassicalDeclarationStmt { @@ -1142,16 +1169,16 @@ impl Display for ClassicalDeclarationStmt { } #[derive(Clone, Debug)] -pub enum ValueExpression { +pub enum ValueExpr { Expr(Expr), Measurement(MeasureExpr), } -impl Display for ValueExpression { +impl Display for ValueExpr { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - ValueExpression::Expr(expr) => write!(f, "{expr}"), - ValueExpression::Measurement(measure) => write!(f, "{measure}"), + Self::Expr(expr) => write!(f, "{expr}"), + Self::Measurement(measure) => write!(f, "{measure}"), } } } @@ -1178,7 +1205,7 @@ pub struct ConstantDeclStmt { pub span: Span, pub ty: TypeDef, pub identifier: Box, - pub init_expr: Expr, + pub init_expr: ValueExpr, } impl Display for ConstantDeclStmt { @@ -1339,7 +1366,7 @@ impl Display for DefStmt { #[derive(Clone, Debug)] pub struct ReturnStmt { pub span: Span, - pub expr: Option>, + pub expr: Option>, } impl Display for ReturnStmt { @@ -1470,7 +1497,7 @@ impl Display for ExprKind { pub struct AssignStmt { pub span: Span, pub lhs: IndexedIdent, - pub rhs: Expr, + pub rhs: ValueExpr, } impl Display for AssignStmt { @@ -1486,7 +1513,7 @@ pub struct AssignOpStmt { pub span: Span, pub op: BinOp, pub lhs: IndexedIdent, - pub rhs: Expr, + pub rhs: ValueExpr, } impl Display for AssignOpStmt { diff --git a/compiler/qsc_qasm3/src/parser/ast/display_utils.rs b/compiler/qsc_qasm3/src/parser/ast/display_utils.rs index 37b29d3198..4ffbb29042 100644 --- a/compiler/qsc_qasm3/src/parser/ast/display_utils.rs +++ b/compiler/qsc_qasm3/src/parser/ast/display_utils.rs @@ -129,7 +129,7 @@ pub(crate) fn writeln_opt_field( writeln!(f) } -/// Writes an field of a structure to the given buffer +/// 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>( @@ -148,7 +148,7 @@ where write_list(&mut indent, vals) } -/// Writes an field of a structure to the given buffer +/// 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. diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index b26b9204f6..f657496b3a 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -24,9 +24,9 @@ use crate::parser::Result; use super::{ ast::{ list_from_iter, BinOp, BinaryOpExpr, Cast, DiscreteSet, Expr, ExprKind, FunctionCall, - GateOperand, HardwareQubit, Ident, IndexElement, IndexExpr, IndexSet, IndexSetItem, - IndexedIdent, List, Lit, LiteralKind, MeasureExpr, RangeDefinition, TimeUnit, TypeDef, - UnaryOp, UnaryOpExpr, ValueExpression, Version, + 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}, @@ -708,9 +708,11 @@ fn lit_array_element(s: &mut ParserContext) -> Result { lit_array(s) } -pub(super) fn value_expr(s: &mut ParserContext) -> Result> { +/// 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(Box::new(ValueExpression::Measurement(measurement))); + return Ok(ValueExpr::Measurement(measurement)); } let expr = if let Some(expr) = opt(s, expr)? { @@ -719,7 +721,17 @@ pub(super) fn value_expr(s: &mut ParserContext) -> Result> lit_array(s)? }; - Ok(Box::new(ValueExpression::Expr(expr))) + 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> { @@ -729,18 +741,28 @@ pub(crate) fn expr_list(s: &mut ParserContext) -> Result> { 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), - operand: gate_operand(s)?, + measure_token_span, + operand, }) } pub(crate) fn gate_operand(s: &mut ParserContext) -> Result { - if let Some(indexed_ident) = opt(s, indexed_identifier)? { - return Ok(GateOperand::IndexedIdent(Box::new(indexed_ident))); - } - Ok(GateOperand::HardwareQubit(Box::new(hardware_qubit(s)?))) + 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 { diff --git a/compiler/qsc_qasm3/src/parser/expr/tests.rs b/compiler/qsc_qasm3/src/parser/expr/tests.rs index 8bb39cccfc..29e92bb9cd 100644 --- a/compiler/qsc_qasm3/src/parser/expr/tests.rs +++ b/compiler/qsc_qasm3/src/parser/expr/tests.rs @@ -1146,8 +1146,9 @@ fn measure_hardware_qubit() { super::measure_expr, "measure $12", &expect![[r#" - MeasureExpr [0-7]: - operand: HardwareQubit [8-11]: 12"#]], + MeasureExpr [0-11]: + operand: GateOperand [8-11]: + kind: HardwareQubit [8-11]: 12"#]], ); } @@ -1157,17 +1158,18 @@ fn measure_indexed_identifier() { super::measure_expr, "measure qubits[1][2]", &expect![[r#" - MeasureExpr [0-7]: - operand: 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)"#]], + 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)"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/mut_visit.rs b/compiler/qsc_qasm3/src/parser/mut_visit.rs index 9f76826f03..838523708f 100644 --- a/compiler/qsc_qasm3/src/parser/mut_visit.rs +++ b/compiler/qsc_qasm3/src/parser/mut_visit.rs @@ -9,12 +9,12 @@ use super::ast::{ BoxStmt, BreakStmt, CalibrationGrammarStmt, CalibrationStmt, Cast, ClassicalDeclarationStmt, ConstantDeclStmt, ContinueStmt, DefCalStmt, DefStmt, DelayStmt, DiscreteSet, EndStmt, EnumerableSet, Expr, ExprStmt, ExternDecl, ExternParameter, ForStmt, FunctionCall, GPhase, - GateCall, GateModifierKind, GateOperand, HardwareQubit, IODeclaration, Ident, Identifier, - IfStmt, IncludeStmt, IndexElement, IndexExpr, IndexSet, IndexSetItem, IndexedIdent, Lit, - LiteralKind, MeasureExpr, MeasureStmt, Pragma, Program, QuantumGateDefinition, + 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, ValueExpression, Version, WhileLoop, + TypedParameter, UnaryOp, UnaryOpExpr, ValueExpr, Version, WhileLoop, }; pub trait MutVisitor: Sized { @@ -130,7 +130,7 @@ pub trait MutVisitor: Sized { walk_io_declaration_stmt(self, stmt); } - fn visit_measure_stmt(&mut self, stmt: &mut MeasureStmt) { + fn visit_measure_stmt(&mut self, stmt: &mut MeasureArrowStmt) { walk_measure_stmt(self, stmt); } @@ -190,7 +190,7 @@ pub trait MutVisitor: Sized { walk_index_expr(self, expr); } - fn visit_value_expr(&mut self, expr: &mut ValueExpression) { + fn visit_value_expr(&mut self, expr: &mut ValueExpr) { walk_value_expr(self, expr); } @@ -379,14 +379,14 @@ fn walk_alias_decl_stmt(vis: &mut impl MutVisitor, stmt: &mut AliasDeclStmt) { 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_expr(&mut stmt.rhs); + 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_expr(&mut stmt.rhs); + vis.visit_value_expr(&mut stmt.rhs); } fn walk_barrier_stmt(vis: &mut impl MutVisitor, stmt: &mut BarrierStmt) { @@ -432,7 +432,7 @@ 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_expr(&mut stmt.init_expr); + vis.visit_value_expr(&mut stmt.init_expr); } fn walk_continue_stmt(vis: &mut impl MutVisitor, stmt: &mut ContinueStmt) { @@ -535,7 +535,7 @@ fn walk_io_declaration_stmt(vis: &mut impl MutVisitor, stmt: &mut IODeclaration) vis.visit_ident(&mut stmt.ident); } -fn walk_measure_stmt(vis: &mut impl MutVisitor, stmt: &mut MeasureStmt) { +fn walk_measure_stmt(vis: &mut impl MutVisitor, stmt: &mut MeasureArrowStmt) { vis.visit_span(&mut stmt.span); stmt.target .iter_mut() @@ -557,12 +557,14 @@ fn walk_quantum_gate_definition_stmt(vis: &mut impl MutVisitor, stmt: &mut Quant 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); } @@ -644,15 +646,16 @@ pub fn walk_index_expr(vis: &mut impl MutVisitor, expr: &mut IndexExpr) { vis.visit_index_element(&mut expr.index); } -pub fn walk_value_expr(vis: &mut impl MutVisitor, expr: &mut ValueExpression) { +pub fn walk_value_expr(vis: &mut impl MutVisitor, expr: &mut ValueExpr) { match &mut *expr { - ValueExpression::Expr(expr) => vis.visit_expr(expr), - ValueExpression::Measurement(measure_expr) => vis.visit_measure_expr(measure_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); } @@ -706,10 +709,11 @@ pub fn walk_index_set_item(vis: &mut impl MutVisitor, item: &mut IndexSetItem) { } pub fn walk_gate_operand(vis: &mut impl MutVisitor, operand: &mut GateOperand) { - match operand { - GateOperand::IndexedIdent(ident) => vis.visit_indexed_ident(ident), - GateOperand::HardwareQubit(hardware_qubit) => vis.visit_hardware_qubit(hardware_qubit), - GateOperand::Err => {} + 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 => {} } } diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index c2915be11b..27be880edc 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -27,7 +27,7 @@ use super::ast::{ 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, MeasureStmt, Pragma, + 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, @@ -204,7 +204,7 @@ fn disambiguate_ident(s: &mut ParserContext, indexed_ident: IndexedIdent) -> Res let lo = indexed_ident.span.lo; if s.peek().kind == TokenKind::Eq { s.advance(); - let expr = expr::expr(s)?; + let expr = expr::expr_or_measurement(s)?; recovering_semi(s); Ok(StmtKind::Assign(AssignStmt { span: s.span(lo), @@ -214,7 +214,7 @@ fn disambiguate_ident(s: &mut ParserContext, indexed_ident: IndexedIdent) -> Res } else if let TokenKind::BinOpEq(op) = s.peek().kind { s.advance(); let op = expr::closed_bin_op(op); - let expr = expr::expr(s)?; + let expr = expr::expr_or_measurement(s)?; recovering_semi(s); Ok(StmtKind::AssignOp(AssignOpStmt { span: s.span(lo), @@ -631,7 +631,7 @@ fn gate_params(s: &mut ParserContext<'_>) -> Result> { 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::value_expr)?; + let expr = opt(s, expr::expr_or_measurement)?.map(Box::new); recovering_semi(s); Ok(StmtKind::Return(ReturnStmt { span: s.span(lo), @@ -643,11 +643,13 @@ fn parse_return(s: &mut ParserContext) -> Result { 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, })) @@ -714,7 +716,7 @@ fn parse_non_constant_classical_decl( let identifier = prim::ident(s)?; let init_expr = if s.peek().kind == TokenKind::Eq { s.advance(); - Some(expr::value_expr(s)?) + Some(Box::new(expr::declaration_expr(s)?)) } else { None }; @@ -736,7 +738,7 @@ fn parse_constant_classical_decl(s: &mut ParserContext) -> Result { let ty = scalar_or_array_type(s)?; let identifier = Box::new(prim::ident(s)?); token(s, TokenKind::Eq)?; - let init_expr = expr::expr(s)?; + let init_expr = expr::declaration_expr(s)?; recovering_semi(s); let decl = ConstantDeclStmt { span: s.span(lo), @@ -878,6 +880,7 @@ fn qreg_decl(s: &mut ParserContext) -> Result { recovering_semi(s); Ok(StmtKind::QuantumDecl(QubitDeclaration { span: s.span(lo), + ty_span: s.span(lo), qubit: identifier, size, })) @@ -1751,17 +1754,19 @@ fn parse_delay(s: &mut ParserContext) -> Result { 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 { +fn parse_measure_stmt(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; let measure = expr::measure_expr(s)?; @@ -1772,7 +1777,7 @@ fn parse_measure_stmt(s: &mut ParserContext) -> Result { recovering_semi(s); - Ok(MeasureStmt { + Ok(MeasureArrowStmt { span: s.span(lo), measurement: measure, target, diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs index 7622ada484..2a37910565 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/barrier.rs @@ -15,18 +15,21 @@ fn barrier() { annotations: kind: BarrierStmt [0-20]: operands: - IndexedIdent [8-9]: - name: Ident [8-9] "r" - index_span: [0-0] - indices: - IndexedIdent [11-15]: - name: Ident [11-12] "q" - index_span: [12-15] - indices: - IndexSet [13-14]: - values: - Expr [13-14]: Lit: Int(0) - HardwareQubit [17-19]: 2"#]], + 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"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs index ac659f9903..5695656f8b 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/box_stmt.rs @@ -28,10 +28,11 @@ fn box_stmt() { args: duration: qubits: - IndexedIdent [21-23]: - name: Ident [21-23] "q0" - index_span: [0-0] - indices: + 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]: @@ -41,10 +42,11 @@ fn box_stmt() { Expr [36-39]: Lit: Float(2.4) duration: qubits: - IndexedIdent [41-43]: - name: Ident [41-43] "q1" - index_span: [0-0] - indices: "#]], + GateOperand [41-43]: + kind: IndexedIdent [41-43]: + name: Ident [41-43] "q1" + index_span: [0-0] + indices: "#]], ); } @@ -71,10 +73,11 @@ fn box_stmt_with_designator() { args: duration: qubits: - IndexedIdent [26-28]: - name: Ident [26-28] "q0" - index_span: [0-0] - indices: + 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]: @@ -84,9 +87,10 @@ fn box_stmt_with_designator() { Expr [41-44]: Lit: Float(2.4) duration: qubits: - IndexedIdent [46-48]: - name: Ident [46-48] "q1" - index_span: [0-0] - indices: "#]], + 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/classical_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs index c71849d8cd..2ae53268ce 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs @@ -1071,8 +1071,9 @@ fn measure_hardware_qubit_decl() { type: ScalarType [0-3]: BitType [0-3]: size: ident: Ident [4-7] "res" - init_expr: MeasureExpr [10-17]: - operand: HardwareQubit [18-21]: 12"#]], + init_expr: MeasureExpr [10-21]: + operand: GateOperand [18-21]: + kind: HardwareQubit [18-21]: 12"#]], ); } @@ -1088,16 +1089,17 @@ fn measure_register_decl() { type: ScalarType [0-3]: BitType [0-3]: size: ident: Ident [4-7] "res" - init_expr: MeasureExpr [10-17]: - operand: 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)"#]], + 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)"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs index 174a38ed06..35bf3bb7d1 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/delay.rs @@ -16,19 +16,21 @@ fn delay() { kind: DelayStmt [0-20]: duration: Expr [6-7]: Ident [6-7] "a" qubits: - IndexedIdent [9-13]: - name: Ident [9-10] "q" - index_span: [10-13] - indices: - IndexSet [11-12]: - values: - Expr [11-12]: Lit: Int(0) - IndexedIdent [15-19]: - name: Ident [15-16] "q" - index_span: [16-19] - indices: - IndexSet [17-18]: - values: - Expr [17-18]: Lit: Int(1)"#]], + 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/for_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs index eabf39e37b..a0debdda1c 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/for_loops.rs @@ -283,10 +283,11 @@ fn single_stmt_for_stmt() { args: duration: qubits: - IndexedIdent [18-19]: - name: Ident [18-19] "q" - index_span: [0-0] - indices: "#]], + GateOperand [18-19]: + kind: IndexedIdent [18-19]: + name: Ident [18-19] "q" + index_span: [0-0] + indices: "#]], ); } @@ -355,10 +356,11 @@ fn nested_single_stmt_for_stmt() { args: duration: qubits: - IndexedIdent [34-35]: - name: Ident [34-35] "q" - index_span: [0-0] - indices: "#]], + GateOperand [34-35]: + kind: IndexedIdent [34-35]: + name: Ident [34-35] "q" + index_span: [0-0] + indices: "#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs index e59d1e46e7..103b3f066f 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gate_call.rs @@ -19,10 +19,11 @@ fn gate_call() { args: duration: qubits: - IndexedIdent [2-4]: - name: Ident [2-4] "q0" - index_span: [0-0] - indices: "#]], + GateOperand [2-4]: + kind: IndexedIdent [2-4]: + name: Ident [2-4] "q0" + index_span: [0-0] + indices: "#]], ); } @@ -40,13 +41,14 @@ fn gate_call_qubit_register() { args: duration: qubits: - IndexedIdent [2-6]: - name: Ident [2-3] "q" - index_span: [3-6] - indices: - IndexSet [4-5]: - values: - Expr [4-5]: Lit: Int(2)"#]], + 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)"#]], ); } @@ -64,17 +66,19 @@ fn gate_multiple_qubits() { args: duration: qubits: - IndexedIdent [5-7]: - name: Ident [5-7] "q0" - index_span: [0-0] - indices: - IndexedIdent [9-13]: - name: Ident [9-10] "q" - index_span: [10-13] - indices: - IndexSet [11-12]: - values: - Expr [11-12]: Lit: Int(4)"#]], + 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)"#]], ); } @@ -125,10 +129,11 @@ fn gate_call_with_parameters() { rhs: Expr [8-9]: Lit: Int(2) duration: qubits: - IndexedIdent [11-13]: - name: Ident [11-13] "q0" - index_span: [0-0] - indices: "#]], + GateOperand [11-13]: + kind: IndexedIdent [11-13]: + name: Ident [11-13] "q0" + index_span: [0-0] + indices: "#]], ); } @@ -147,10 +152,11 @@ fn gate_call_inv_modifier() { args: duration: qubits: - IndexedIdent [8-10]: - name: Ident [8-10] "q0" - index_span: [0-0] - indices: "#]], + GateOperand [8-10]: + kind: IndexedIdent [8-10]: + name: Ident [8-10] "q0" + index_span: [0-0] + indices: "#]], ); } @@ -174,18 +180,21 @@ fn gate_call_ctrl_inv_modifiers() { rhs: Expr [24-25]: Lit: Int(2) duration: qubits: - IndexedIdent [27-29]: - name: Ident [27-29] "c1" - index_span: [0-0] - indices: - IndexedIdent [31-33]: - name: Ident [31-33] "c2" - index_span: [0-0] - indices: - IndexedIdent [35-37]: - name: Ident [35-37] "q0" - index_span: [0-0] - indices: "#]], + 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: "#]], ); } @@ -224,10 +233,11 @@ fn parametrized_gate_call() { Expr [8-9]: Lit: Int(3) duration: qubits: - IndexedIdent [11-12]: - name: Ident [11-12] "q" - index_span: [0-0] - indices: "#]], + GateOperand [11-12]: + kind: IndexedIdent [11-12]: + name: Ident [11-12] "q" + index_span: [0-0] + indices: "#]], ); } @@ -247,10 +257,11 @@ fn parametrized_gate_call_with_designator() { Expr [8-9]: Lit: Int(3) duration: Expr [11-12]: Lit: Int(1) qubits: - IndexedIdent [14-15]: - name: Ident [14-15] "q" - index_span: [0-0] - indices: "#]], + GateOperand [14-15]: + kind: IndexedIdent [14-15]: + name: Ident [14-15] "q" + index_span: [0-0] + indices: "#]], ); } @@ -286,10 +297,11 @@ fn gate_call_with_designator() { args: duration: Expr [2-5]: Lit: Duration(2.0, Us) qubits: - IndexedIdent [7-8]: - name: Ident [7-8] "q" - index_span: [0-0] - indices: "#]], + GateOperand [7-8]: + kind: IndexedIdent [7-8]: + name: Ident [7-8] "q" + index_span: [0-0] + indices: "#]], ); } @@ -307,10 +319,11 @@ fn gate_call_with_invalid_designator() { args: duration: Expr [2-5]: Lit: Duration(2.0, Us) qubits: - IndexedIdent [10-11]: - name: Ident [10-11] "q" - index_span: [0-0] - indices: + GateOperand [10-11]: + kind: IndexedIdent [10-11]: + name: Ident [10-11] "q" + index_span: [0-0] + indices: [ Error( diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs index be32d2fc52..f2b5cb94f0 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs @@ -39,10 +39,11 @@ fn gphase_qubit_ident() { Expr [7-8]: Ident [7-8] "a" duration: qubits: - IndexedIdent [10-12]: - name: Ident [10-12] "q0" - index_span: [0-0] - indices: "#]], + GateOperand [10-12]: + kind: IndexedIdent [10-12]: + name: Ident [10-12] "q0" + index_span: [0-0] + indices: "#]], ); } @@ -60,13 +61,14 @@ fn gphase_qubit_register() { Expr [7-8]: Ident [7-8] "a" duration: qubits: - IndexedIdent [10-14]: - name: Ident [10-11] "q" - index_span: [11-14] - indices: - IndexSet [12-13]: - values: - Expr [12-13]: Lit: Int(2)"#]], + 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)"#]], ); } @@ -84,17 +86,19 @@ fn gphase_multiple_qubits() { Expr [7-8]: Ident [7-8] "a" duration: qubits: - IndexedIdent [10-12]: - name: Ident [10-12] "q0" - index_span: [0-0] - indices: - IndexedIdent [14-18]: - name: Ident [14-15] "q" - index_span: [15-18] - indices: - IndexSet [16-17]: - values: - Expr [16-17]: Lit: Int(4)"#]], + 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)"#]], ); } @@ -182,9 +186,10 @@ fn gphase_ctrl_inv_modifiers() { rhs: Expr [25-26]: Lit: Int(2) duration: qubits: - IndexedIdent [28-30]: - name: Ident [28-30] "q0" - index_span: [0-0] - indices: "#]], + 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 index e8ceb04a91..b2d1bd177b 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/if_stmt.rs @@ -192,10 +192,11 @@ fn single_stmt_if_stmt() { args: duration: qubits: - IndexedIdent [9-10]: - name: Ident [9-10] "q" - index_span: [0-0] - indices: + GateOperand [9-10]: + kind: IndexedIdent [9-10]: + name: Ident [9-10] "q" + index_span: [0-0] + indices: else_body: "#]], ); } @@ -254,10 +255,11 @@ fn nested_single_stmt_if_stmt() { args: duration: qubits: - IndexedIdent [16-17]: - name: Ident [16-17] "q" - index_span: [0-0] - indices: + GateOperand [16-17]: + kind: IndexedIdent [16-17]: + name: Ident [16-17] "q" + index_span: [0-0] + indices: else_body: else_body: "#]], ); @@ -285,10 +287,11 @@ fn nested_single_stmt_if_else_stmt() { args: duration: qubits: - IndexedIdent [16-17]: - name: Ident [16-17] "q" - index_span: [0-0] - indices: + 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]: @@ -305,10 +308,11 @@ fn nested_single_stmt_if_else_stmt() { args: duration: qubits: - IndexedIdent [40-41]: - name: Ident [40-41] "q" - index_span: [0-0] - indices: + GateOperand [40-41]: + kind: IndexedIdent [40-41]: + name: Ident [40-41] "q" + index_span: [0-0] + indices: else_body: else_body: else_body: "#]], @@ -333,10 +337,11 @@ fn single_stmt_if_stmt_else_stmt() { args: duration: qubits: - IndexedIdent [9-10]: - name: Ident [9-10] "q" - index_span: [0-0] - indices: + 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]: @@ -345,9 +350,10 @@ fn single_stmt_if_stmt_else_stmt() { args: duration: qubits: - IndexedIdent [19-20]: - name: Ident [19-20] "q" - index_span: [0-0] - indices: "#]], + 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/invalid_stmts/gate_calls.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/gate_calls.rs index 3268d1ec75..0fdeb38c57 100644 --- 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 @@ -78,31 +78,32 @@ fn pow_with_two_args() { 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: - HardwareQubit [14-16]: 0 + 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, + [ + Error( + Token( + Close( + Paren, + ), + Comma, + Span { + lo: 5, + hi: 6, + }, ), - Comma, - Span { - lo: 5, - hi: 6, - }, ), - ), - ]"#]], + ]"#]], ); } @@ -112,32 +113,34 @@ fn ctrl_with_two_args() { 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: - HardwareQubit [15-17]: 0 - HardwareQubit [19-21]: 1 + 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, + [ + Error( + Token( + Close( + Paren, + ), + Comma, + Span { + lo: 6, + hi: 7, + }, ), - Comma, - Span { - lo: 6, - hi: 7, - }, ), - ), - ]"#]], + ]"#]], ); } @@ -147,32 +150,34 @@ fn negctrl_with_two_args() { 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: - HardwareQubit [18-20]: 0 - HardwareQubit [22-24]: 1 + 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, + [ + Error( + Token( + Close( + Paren, + ), + Comma, + Span { + lo: 9, + hi: 10, + }, ), - Comma, - Span { - lo: 9, - hi: 10, - }, ), - ), - ]"#]], + ]"#]], ); } @@ -182,32 +187,34 @@ fn inv_with_arg() { 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: - HardwareQubit [18-20]: 0 - HardwareQubit [22-24]: 1 + 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, + [ + Error( + Token( + At, + Open( + Paren, + ), + Span { + lo: 3, + hi: 4, + }, ), - Span { - lo: 3, - hi: 4, - }, ), - ), - ]"#]], + ]"#]], ); } 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 index 1a236c2ecb..b23fb4a5b3 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/measure.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/invalid_stmts/measure.rs @@ -10,25 +10,26 @@ fn measure_multiple_qubits() { parse, "measure $0, $1;", &expect![[r#" - Stmt [0-10]: - annotations: - kind: MeasureStmt [0-10]: - measurement: MeasureExpr [0-7]: - operand: HardwareQubit [8-10]: 0 - target: + 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, - }, + [ + Error( + Token( + Semicolon, + Comma, + Span { + lo: 10, + hi: 11, + }, + ), ), - ), - ]"#]], + ]"#]], ); } @@ -38,17 +39,35 @@ fn assign_measure_multiple_qubits() { parse, "a[0:1] = measure $0, $1;", &expect![[r#" - Error( - Rule( - "expression", - Measure, - Span { - lo: 9, - hi: 16, - }, - ), - ) - "#]], + 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, + }, + ), + ), + ]"#]], ); } @@ -58,17 +77,29 @@ fn assign_arrow() { parse, "a = measure $0 -> b;", &expect![[r#" - Error( - Rule( - "expression", - Measure, - Span { - lo: 4, - hi: 11, - }, - ), - ) - "#]], + 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, + }, + ), + ), + ]"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs index e58bf22e36..fd2518ff97 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/measure.rs @@ -13,12 +13,13 @@ fn measure_identifier() { &expect![[r#" Stmt [0-10]: annotations: - kind: MeasureStmt [0-10]: - measurement: MeasureExpr [0-7]: - operand: IndexedIdent [8-9]: - name: Ident [8-9] "q" - index_span: [0-0] - indices: + 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: "#]], ); } @@ -31,15 +32,16 @@ fn measure_indented_ident() { &expect![[r#" Stmt [0-13]: annotations: - kind: MeasureStmt [0-13]: - measurement: MeasureExpr [0-7]: - operand: IndexedIdent [8-12]: - name: Ident [8-9] "q" - index_span: [9-12] - indices: - IndexSet [10-11]: - values: - Expr [10-11]: Lit: Int(2) + 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: "#]], ); } @@ -52,9 +54,10 @@ fn measure_hardware_qubit() { &expect![[r#" Stmt [0-12]: annotations: - kind: MeasureStmt [0-12]: - measurement: MeasureExpr [0-7]: - operand: HardwareQubit [8-11]: 42 + kind: MeasureArrowStmt [0-12]: + measurement: MeasureExpr [0-11]: + operand: GateOperand [8-11]: + kind: HardwareQubit [8-11]: 42 target: "#]], ); } @@ -67,12 +70,13 @@ fn measure_arrow_into_ident() { &expect![[r#" Stmt [0-15]: annotations: - kind: MeasureStmt [0-15]: - measurement: MeasureExpr [0-7]: - operand: IndexedIdent [8-9]: - name: Ident [8-9] "q" - index_span: [0-0] - indices: + 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] @@ -88,12 +92,13 @@ fn measure_arrow_into_indented_ident() { &expect![[r#" Stmt [0-18]: annotations: - kind: MeasureStmt [0-18]: - measurement: MeasureExpr [0-7]: - operand: IndexedIdent [8-9]: - name: Ident [8-9] "q" - index_span: [0-0] - indices: + 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] @@ -103,3 +108,25 @@ fn measure_arrow_into_indented_ident() { 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 index ad0d3b7e1c..5b9071def6 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/old_style_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/old_style_decl.rs @@ -48,6 +48,7 @@ fn qreg_decl() { Stmt [0-7]: annotations: kind: QubitDeclaration [0-7]: + ty_span: [0-7] ident: Ident [5-6] "q" size: "#]], ); @@ -62,6 +63,7 @@ fn qreg_array_decl() { 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/quantum_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/quantum_decl.rs index 1bf02de48d..67031b9bd2 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/quantum_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/quantum_decl.rs @@ -16,6 +16,7 @@ fn quantum_decl() { Stmt [0-8]: annotations: kind: QubitDeclaration [0-8]: + ty_span: [0-5] ident: Ident [6-7] "q" size: "#]], ); @@ -35,6 +36,7 @@ fn annotated_quantum_decl() { identifier: "a.b.c" value: "123" kind: QubitDeclaration [28-36]: + ty_span: [28-33] ident: Ident [34-35] "q" size: "#]], ); @@ -62,6 +64,7 @@ fn multi_annotated_quantum_decl() { identifier: "a.b.c" value: "123" kind: QubitDeclaration [100-108]: + ty_span: [100-105] ident: Ident [106-107] "q" size: "#]], ); @@ -96,6 +99,7 @@ fn quantum_decl_with_designator() { Stmt [0-16]: annotations: kind: QubitDeclaration [0-16]: + ty_span: [0-8] ident: Ident [9-15] "qubits" size: Expr [6-7]: Lit: Int(5)"#]], ); diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs index fd56798242..dc8670b04a 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/reset.rs @@ -14,10 +14,12 @@ fn reset_ident() { Stmt [0-8]: annotations: kind: ResetStmt [0-8]: - operand: IndexedIdent [6-7]: - name: Ident [6-7] "a" - index_span: [0-0] - indices: "#]], + reset_token_span: [0-5] + operand: GateOperand [6-7]: + kind: IndexedIdent [6-7]: + name: Ident [6-7] "a" + index_span: [0-0] + indices: "#]], ); } @@ -30,13 +32,15 @@ fn reset_indexed_ident() { Stmt [0-11]: annotations: kind: ResetStmt [0-11]: - operand: IndexedIdent [6-10]: - name: Ident [6-7] "a" - index_span: [7-10] - indices: - IndexSet [8-9]: - values: - Expr [8-9]: Lit: Int(1)"#]], + 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)"#]], ); } @@ -49,6 +53,8 @@ fn reset_hardware_qubit() { Stmt [0-10]: annotations: kind: ResetStmt [0-10]: - operand: HardwareQubit [6-9]: 42"#]], + reset_token_span: [0-5] + operand: GateOperand [6-9]: + kind: HardwareQubit [6-9]: 42"#]], ); } diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs index 1f44485846..0e5493d1f5 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/while_loops.rs @@ -162,10 +162,11 @@ fn single_stmt_while_stmt() { args: duration: qubits: - IndexedIdent [12-13]: - name: Ident [12-13] "q" - index_span: [0-0] - indices: "#]], + GateOperand [12-13]: + kind: IndexedIdent [12-13]: + name: Ident [12-13] "q" + index_span: [0-0] + indices: "#]], ); } @@ -222,9 +223,10 @@ fn nested_single_stmt_while_stmt() { args: duration: qubits: - IndexedIdent [22-23]: - name: Ident [22-23] "q" - index_span: [0-0] - indices: "#]], + GateOperand [22-23]: + kind: IndexedIdent [22-23]: + name: Ident [22-23] "q" + index_span: [0-0] + indices: "#]], ); } diff --git a/compiler/qsc_qasm3/src/semantic/ast.rs b/compiler/qsc_qasm3/src/semantic/ast.rs index 0382d51406..f1bde908fe 100644 --- a/compiler/qsc_qasm3/src/semantic/ast.rs +++ b/compiler/qsc_qasm3/src/semantic/ast.rs @@ -1,6 +1,8 @@ // 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::{ @@ -141,12 +143,14 @@ impl WithSpan for Path { #[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) } } @@ -268,29 +272,33 @@ impl Display for UnaryOp { } #[derive(Clone, Debug, Default)] -pub enum GateOperand { - IndexedIdent(Box), - HardwareQubit(Box), - #[default] - Err, +pub struct GateOperand { + pub span: Span, + pub kind: GateOperandKind, } impl Display for GateOperand { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - GateOperand::IndexedIdent(ident) => write!(f, "{ident}"), - GateOperand::HardwareQubit(qubit) => write!(f, "{qubit}"), - GateOperand::Err => write!(f, "Error"), - } + writeln_header(f, "GateOperand", self.span)?; + write_field(f, "kind", &self.kind) } } -impl WithSpan for GateOperand { - fn with_span(self, span: Span) -> Self { +#[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 { - GateOperand::IndexedIdent(ident) => GateOperand::IndexedIdent(ident.with_span(span)), - GateOperand::HardwareQubit(qubit) => GateOperand::HardwareQubit(qubit.with_span(span)), - GateOperand::Err => GateOperand::Err, + Self::Expr(expr) => write!(f, "{expr}"), + Self::HardwareQubit(qubit) => write!(f, "{qubit}"), + Self::Err => write!(f, "Err"), } } } @@ -353,10 +361,11 @@ pub enum StmtKind { Include(IncludeStmt), InputDeclaration(InputDeclaration), OutputDeclaration(OutputDeclaration), - Measure(MeasureStmt), + MeasureArrow(MeasureArrowStmt), Pragma(Pragma), QuantumGateDefinition(QuantumGateDefinition), - QuantumDecl(QubitDeclaration), + QubitDecl(QubitDeclaration), + QubitArrayDecl(QubitArrayDeclaration), Reset(ResetStmt), Return(ReturnStmt), Switch(SwitchStmt), @@ -391,10 +400,11 @@ impl Display for StmtKind { StmtKind::IndexedAssign(assign) => write!(f, "{assign}"), StmtKind::InputDeclaration(io) => write!(f, "{io}"), StmtKind::OutputDeclaration(io) => write!(f, "{io}"), - StmtKind::Measure(measure) => write!(f, "{measure}"), + StmtKind::MeasureArrow(measure) => write!(f, "{measure}"), StmtKind::Pragma(pragma) => write!(f, "{pragma}"), StmtKind::QuantumGateDefinition(gate) => write!(f, "{gate}"), - StmtKind::QuantumDecl(decl) => write!(f, "{decl}"), + 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}"), @@ -461,12 +471,14 @@ impl Display for BarrierStmt { #[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) } } @@ -653,8 +665,8 @@ impl Display for QuantumGateModifier { pub enum GateModifierKind { Inv, Pow(Expr), - Ctrl(Option), - NegCtrl(Option), + Ctrl(u32), + NegCtrl(u32), } impl Display for GateModifierKind { @@ -662,8 +674,8 @@ impl Display for GateModifierKind { 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:?}"), + GateModifierKind::Ctrl(ctrls) => write!(f, "Ctrl {ctrls:?}"), + GateModifierKind::NegCtrl(ctrls) => write!(f, "NegCtrl {ctrls:?}"), } } } @@ -1001,15 +1013,30 @@ impl Display for IncludeStmt { #[derive(Clone, Debug)] pub struct QubitDeclaration { pub span: Span, - pub qubit: Box, - pub size: Option, + pub symbol_id: SymbolId, } impl Display for QubitDeclaration { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln_header(f, "QubitDeclaration", self.span)?; - writeln_field(f, "ident", &self.qubit)?; - write_opt_field(f, "size", self.size.as_ref()) + 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) } } @@ -1053,20 +1080,28 @@ impl Display for ExternDecl { pub struct GateCall { pub span: Span, pub modifiers: List, - pub name: Ident, + pub symbol_id: SymbolId, pub args: List, pub qubits: List, pub duration: Option, + pub quantum_arity: u32, + pub quantum_arity_with_modifiers: 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, "name", &self.name)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; writeln_list_field(f, "args", &self.args)?; writeln_opt_field(f, "duration", self.duration.as_ref())?; - write_list_field(f, "qubits", &self.qubits) + writeln_list_field(f, "qubits", &self.qubits)?; + writeln_field(f, "quantum_arity", &self.quantum_arity)?; + write_field( + f, + "quantum_arity_with_modifiers", + &self.quantum_arity_with_modifiers, + ) } } @@ -1120,15 +1155,15 @@ impl Display for BoxStmt { } #[derive(Clone, Debug)] -pub struct MeasureStmt { +pub struct MeasureArrowStmt { pub span: Span, pub measurement: MeasureExpr, pub target: Option>, } -impl Display for MeasureStmt { +impl Display for MeasureArrowStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - writeln_header(f, "MeasureStmt", self.span)?; + writeln_header(f, "MeasureArrowStmt", self.span)?; writeln_field(f, "measurement", &self.measurement)?; write_opt_field(f, "target", self.target.as_ref()) } @@ -1139,7 +1174,7 @@ pub struct ClassicalDeclarationStmt { pub span: Span, pub ty_span: Span, pub symbol_id: SymbolId, - pub init_expr: Box, + pub init_expr: Box, } impl Display for ClassicalDeclarationStmt { @@ -1151,21 +1186,6 @@ impl Display for ClassicalDeclarationStmt { } } -#[derive(Clone, Debug)] -pub enum ValueExpression { - Expr(Expr), - Measurement(MeasureExpr), -} - -impl Display for ValueExpression { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - ValueExpression::Expr(expr) => write!(f, "{expr}"), - ValueExpression::Measurement(measure) => write!(f, "{measure}"), - } - } -} - #[derive(Clone, Debug)] pub struct InputDeclaration { pub span: Span, @@ -1320,7 +1340,7 @@ impl Display for DefStmt { #[derive(Clone, Debug)] pub struct ReturnStmt { pub span: Span, - pub expr: Option>, + pub expr: Option>, } impl Display for ReturnStmt { @@ -1333,14 +1353,14 @@ impl Display for ReturnStmt { #[derive(Clone, Debug)] pub struct WhileLoop { pub span: Span, - pub while_condition: Expr, + 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.while_condition)?; + writeln_field(f, "condition", &self.condition)?; write_field(f, "body", &self.body) } } @@ -1428,6 +1448,7 @@ pub enum ExprKind { Cast(Cast), IndexExpr(IndexExpr), Paren(Expr), + Measure(MeasureExpr), } impl Display for ExprKind { @@ -1443,6 +1464,7 @@ impl Display for ExprKind { 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}"), } } } @@ -1468,7 +1490,7 @@ impl Display for AssignStmt { pub struct IndexedAssignStmt { pub span: Span, pub symbol_id: SymbolId, - pub lhs: Expr, + pub indices: List, pub rhs: Expr, } @@ -1476,7 +1498,7 @@ impl Display for IndexedAssignStmt { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln_header(f, "AssignStmt", self.span)?; writeln_field(f, "symbol_id", &self.symbol_id)?; - writeln_field(f, "lhs", &self.rhs)?; + writeln_list_field(f, "indices", &self.indices)?; write_field(f, "rhs", &self.rhs) } } @@ -1596,6 +1618,7 @@ pub enum LiteralKind { Int(i64), BigInt(BigInt), String(Rc), + Bit(bool), } impl Display for LiteralKind { @@ -1606,6 +1629,7 @@ impl Display for LiteralKind { let width = *width as usize; write!(f, "Bitstring(\"{:0>width$}\")", value.to_str_radix(2)) } + 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) => { 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..15f4ca2344 --- /dev/null +++ b/compiler/qsc_qasm3/src/semantic/ast/const_eval.rs @@ -0,0 +1,628 @@ +// 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 std::f64; + +use super::{ + BinOp, BinaryOpExpr, Cast, Expr, ExprKind, FunctionCall, IndexExpr, IndexedIdent, LiteralKind, + SymbolId, UnaryOp, UnaryOpExpr, +}; +use crate::semantic::{ + symbols::SymbolTable, + types::{ArrayDimensions, Type}, +}; +use num_bigint::BigInt; + +impl Expr { + pub fn const_eval(&self, symbols: &SymbolTable) -> Option { + let ty = &self.ty; + if !ty.is_const() { + return None; + } + + match &*self.kind { + ExprKind::Ident(symbol_id) => symbol_id.const_eval(symbols), + ExprKind::IndexedIdentifier(indexed_ident) => indexed_ident.const_eval(symbols), + ExprKind::UnaryOp(unary_op_expr) => unary_op_expr.const_eval(symbols), + ExprKind::BinaryOp(binary_op_expr) => binary_op_expr.const_eval(symbols), + ExprKind::Lit(literal_kind) => Some(literal_kind.clone()), + ExprKind::FunctionCall(function_call) => function_call.const_eval(symbols, ty), + ExprKind::Cast(cast) => cast.const_eval(symbols, ty), + ExprKind::IndexExpr(index_expr) => index_expr.const_eval(symbols, ty), + ExprKind::Paren(expr) => expr.const_eval(symbols), + // Measurements are non-const, so we don't need to implement them. + ExprKind::Measure(_) | ExprKind::Err => None, + } + } +} + +impl SymbolId { + fn const_eval(self, symbols: &SymbolTable) -> Option { + let symbol = symbols[self].clone(); + symbol + .get_const_expr() // get the value of the symbol (an Expr) + .const_eval(symbols) // const eval that Expr + } +} + +impl IndexedIdent { + #[allow(clippy::unused_self)] + fn const_eval(&self, _symbols: &SymbolTable) -> 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 { + None + } + }; +} + +impl UnaryOpExpr { + fn const_eval(&self, symbols: &SymbolTable) -> Option { + use LiteralKind::{Bit, Bitstring, Bool, Float, Int}; + let operand_ty = &self.expr.ty; + let lit = self.expr.const_eval(symbols)?; + + 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, Float(val), Float(f64::consts::TAU - 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::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, symbols: &SymbolTable) -> Option { + use LiteralKind::{Bit, Bitstring, Bool, Float, Int}; + + assert_binary_op_ty_invariant(self.op, &self.lhs.ty, &self.rhs.ty); + let lhs = self.lhs.const_eval(symbols)?; + let rhs = self.rhs.const_eval(symbols)?; + let lhs_ty = &self.lhs.ty; + + match &self.op { + // Bit Shifts + BinOp::Shl => match lhs_ty { + Type::UInt(Some(size), _) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), { + let mask = (1 << size) - 1; + Int((lhs << rhs) & mask) + }), + Type::UInt(..) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs << rhs)), + Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Int(rhs)), { + // 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, rhs), (Bitstring(lhs, size), Int(rhs)), { + let mask = BigInt::from((1 << size) - 1); + Bitstring((lhs << rhs) & mask, size) + }), + _ => None, + }, + BinOp::Shr => match lhs_ty { + Type::UInt(..) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs >> rhs)), + Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Int(rhs)), { + // 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, rhs), (Bitstring(lhs, size), Int(rhs)), { + 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::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::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::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(..) | Type::Angle(..) => { + 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::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(..) | Type::Angle(..) => { + 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::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(..) | Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(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(..) | Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(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(..) | Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(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(..) | Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(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), (Float(lhs), Float(rhs)), { + let mut ans = lhs + rhs; + if ans >= f64::consts::TAU { + ans -= f64::consts::TAU; + } + Float(ans) + }), + _ => 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), (Float(lhs), Float(rhs)), { + let mut ans = lhs - rhs; + if ans < 0.0 { + ans += f64::consts::TAU; + } + Float(ans) + }), + _ => 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), Float(rhs)), { + // allow reason: angles are in [0, 2π) + #[allow(clippy::cast_precision_loss)] + let mut ans = (lhs as f64) * rhs; + while ans >= f64::consts::TAU { + ans -= f64::consts::TAU; + } + Float(ans) + }), + _ => None, + }, + Type::Float(..) => { + rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Float(lhs * rhs)) + } + Type::Angle(..) => rewrap_lit!((lhs, rhs), (Float(lhs), Int(rhs)), { + // allow reason: angles are in [0, 2π) + #[allow(clippy::cast_precision_loss)] + let mut ans = lhs * (rhs as f64); + while ans >= f64::consts::TAU { + ans -= f64::consts::TAU; + } + Float(ans) + }), + _ => 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(..) => rewrap_lit!((lhs, rhs), (Float(lhs), Int(rhs)), { + // allow reason: angles are in [0, 2π) + #[allow(clippy::cast_precision_loss)] + Float(lhs / (rhs as f64)) + }), + _ => 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, _symbols: &SymbolTable, _ty: &Type) -> Option { + None + } +} + +impl IndexExpr { + #[allow(clippy::unused_self)] + fn const_eval(&self, _symbols: &SymbolTable, _ty: &Type) -> Option { + None + } +} + +impl Cast { + fn const_eval(&self, symbols: &SymbolTable, ty: &Type) -> Option { + let lit = self.expr.const_eval(symbols)?; + let from_ty = &self.expr.ty; + + match ty { + Type::Bool(_) => cast_to_bool(from_ty, lit), + Type::Int(_, _) => cast_to_int(from_ty, lit), + Type::UInt(_, _) => cast_to_uint(from_ty, lit), + Type::Float(_, _) => cast_to_float(from_ty, lit), + Type::Angle(_, _) => cast_to_angle(from_ty, lit), + Type::Bit(_) => cast_to_bit(from_ty, lit), + Type::BitArray(dims, _) => cast_to_bitarray(from_ty, lit, dims), + _ => None, + } + } +} + +/// +---------------+-----------------------------------------+ +/// | Allowed casts | Casting from | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | Casting to | bool | int | uint | float | angle | bit | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | bool | - | Yes | Yes | Yes | Yes | Yes | +/// +---------------+------+-----+------+-------+-------+-----+ +fn cast_to_bool(ty: &Type, lit: LiteralKind) -> Option { + use LiteralKind::{Bit, Bitstring, Bool, Float, Int}; + + match 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(..) | Type::Angle(..) => rewrap_lit!(lit, Float(val), Bool(val != 0.0)), + _ => None, + } +} + +/// +---------------+-----------------------------------------+ +/// | Allowed casts | Casting from | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | Casting to | bool | int | uint | float | angle | bit | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | int | Yes | - | Yes | Yes | No | Yes | +/// +---------------+------+-----+------+-------+-------+-----+ +fn cast_to_int(ty: &Type, lit: LiteralKind) -> Option { + use LiteralKind::{Bit, Bitstring, Bool, Float, Int}; + + match 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 from | bool | int | uint | float | angle | bit | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | uint | Yes | Yes | - | Yes | No | Yes | +/// +---------------+------+-----+------+-------+-------+-----+ +fn cast_to_uint(ty: &Type, lit: LiteralKind) -> Option { + use LiteralKind::{Bit, Bitstring, Bool, Float, Int}; + + match 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(ty: &Type, lit: LiteralKind) -> Option { + use LiteralKind::{Bool, Float, Int}; + + match 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(val as f64) + }), + 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(ty: &Type, lit: LiteralKind) -> Option { + match ty { + Type::Float(..) | Type::Angle(..) => Some(lit), + _ => None, + } +} + +/// +---------------+-----------------------------------------+ +/// | Allowed casts | Casting from | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | Casting to | bool | int | uint | float | angle | bit | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | bit | Yes | Yes | Yes | No | Yes | - | +/// +---------------+------+-----+------+-------+-------+-----+ +fn cast_to_bit(ty: &Type, lit: LiteralKind) -> Option { + use LiteralKind::{Bit, Bool, Int}; + + match 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)), + _ => None, + } +} + +/// +---------------+-----------------------------------------+ +/// | Allowed casts | Casting from | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | Casting to | bool | int | uint | float | angle | bit | +/// +---------------+------+-----+------+-------+-------+-----+ +/// | bitarray | Yes | Yes | Yes | No | Yes | - | +/// +---------------+------+-----+------+-------+-------+-----+ +fn cast_to_bitarray(ty: &Type, lit: LiteralKind, dims: &ArrayDimensions) -> Option { + use LiteralKind::{Bit, Bitstring, Bool, Int}; + + let ArrayDimensions::One(size) = dims else { + return None; + }; + let size = *size; + + match ty { + Type::Bool(..) => rewrap_lit!(lit, Bool(val), Bitstring(BigInt::from(val), 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 { + 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 { + 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 index 02df4074f1..6a6652456a 100644 --- a/compiler/qsc_qasm3/src/semantic/error.rs +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -29,6 +29,12 @@ 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), @@ -72,6 +78,12 @@ pub enum SemanticErrorKind { #[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("Designator is too large.")] #[diagnostic(code("Qsc.Qasm3.Compile.DesignatorTooLarge"))] DesignatorTooLarge(#[label] Span), @@ -171,6 +183,9 @@ pub enum SemanticErrorKind { #[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), @@ -220,6 +235,8 @@ impl SemanticErrorKind { 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) } @@ -250,6 +267,12 @@ impl SemanticErrorKind { Self::DesignatorMustBePositiveIntLiteral(span) => { Self::DesignatorMustBePositiveIntLiteral(span + offset) } + Self::TypeWidthMustBePositiveIntConstExpr(span) => { + Self::TypeWidthMustBePositiveIntConstExpr(span + offset) + } + Self::ArraySizeMustBeNonNegativeConstExpr(span) => { + Self::ArraySizeMustBeNonNegativeConstExpr(span + offset) + } Self::DesignatorTooLarge(span) => Self::DesignatorTooLarge(span + offset), Self::FailedToCompileExpressionList(span) => { Self::FailedToCompileExpressionList(span + offset) @@ -327,6 +350,9 @@ impl SemanticErrorKind { Self::ResetExpressionMustHaveName(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) => { diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index 4c47394b48..dd4e054af3 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -2,6 +2,7 @@ // Licensed under the MIT License. use std::ops::ShlAssign; +use std::rc::Rc; use super::symbols::ScopeKind; use super::types::binop_requires_int_conversion_for_type; @@ -27,6 +28,7 @@ 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::types::get_qsharp_gate_name; use super::ast as semantic; use crate::parser::ast as syntax; @@ -180,7 +182,7 @@ impl Lowerer { 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), + 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) => { @@ -227,11 +229,13 @@ impl Lowerer { /// when calling them. fn define_stdgates(&mut self, include: &syntax::IncludeStmt) { fn gate_symbol(name: &str, cargs: u32, qargs: u32) -> Symbol { - Symbol { - name: name.to_string(), - ty: Type::Gate(cargs, qargs), - ..Default::default() - } + Symbol::new( + name, + Span::default(), + Type::Gate(cargs, qargs), + Default::default(), + Default::default(), + ) } let gates = vec![ gate_symbol("X", 0, 1), @@ -313,13 +317,13 @@ impl Lowerer { .collect::>(); let first = rhs.first().expect("missing rhs"); - let symbol = Symbol { - name: name.to_string(), - ty: first.ty.clone(), - qsharp_ty: self.convert_semantic_type_to_qsharp_type(&first.ty, alias.ident.span()), - span: alias.ident.span(), - io_kind: IOKind::Default, - }; + 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, alias.ident.span()); @@ -351,14 +355,24 @@ impl Lowerer { fn lower_simple_assign_expr( &mut self, ident: &syntax::Ident, - rhs: &syntax::Expr, + 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 = self.lower_expr_with_target_type(Some(rhs), &ty, span); + 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); @@ -377,18 +391,36 @@ impl Lowerer { fn lower_indexed_assign_expr( &mut self, index_expr: &syntax::IndexedIdent, - rhs: &syntax::Expr, + 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 ty = &symbol.ty; - let lhs = self.lower_indexed_ident_expr(index_expr); - let rhs = self.lower_expr_with_target_type(Some(rhs), ty, span); + let indexed_ty = &symbol + .ty + .get_indexed_type() + .expect("we only get here if there is at least one index"); - if ty.is_const() { + let indices = index_expr + .indices + .iter() + .map(|index| self.lower_index_element(index)); + let indices = list_from_iter(indices); + + 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); @@ -397,7 +429,7 @@ impl Lowerer { semantic::StmtKind::IndexedAssign(semantic::IndexedAssignStmt { span, symbol_id, - lhs, + indices, rhs, }) } @@ -420,8 +452,16 @@ impl Lowerer { } let lhs = self.lower_indexed_ident_expr(lhs); - let rhs = self.lower_expr(rhs); - let rhs = self.cast_expr_to_type(ty, &rhs, stmt.span); + 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, @@ -485,7 +525,7 @@ impl Lowerer { } syntax::LiteralKind::Bitstring(value, size) => ( semantic::ExprKind::Lit(semantic::LiteralKind::Bitstring(value.clone(), *size)), - Type::BitArray(super::types::ArrayDimensions::One(*size), true), + Type::BitArray(super::types::ArrayDimensions::from(*size), true), ), syntax::LiteralKind::Bool(value) => ( semantic::ExprKind::Lit(semantic::LiteralKind::Bool(*value)), @@ -553,7 +593,7 @@ impl Lowerer { fn lower_unary_op_expr(&mut self, expr: &syntax::UnaryOpExpr) -> semantic::Expr { match expr.op { - syntax::UnaryOp::Neg | syntax::UnaryOp::NotB => { + syntax::UnaryOp::Neg => { let op = expr.op; let expr = self.lower_expr(&expr.expr); let ty = expr.ty.clone(); @@ -576,13 +616,39 @@ impl Lowerer { 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 target_ty = Type::Bool(false); + let expr = self.lower_expr(&expr.expr); + let expr_span = expr.span; + let target_ty = Type::Bool(expr.ty.is_const()); + let expr = - self.lower_expr_with_target_type(Some(&expr.expr), &target_ty, expr.expr.span); + self.cast_expr_with_target_type_or_default(Some(expr), &target_ty, expr_span); let ty = expr.ty.clone(); @@ -698,6 +764,7 @@ impl Lowerer { } 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); @@ -706,9 +773,13 @@ impl Lowerer { } } - fn lower_barrier(&mut self, stmt: &syntax::BarrierStmt) -> semantic::StmtKind { - self.push_unimplemented_error_message("barrier stmt", stmt.span); - semantic::StmtKind::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 @@ -792,29 +863,27 @@ impl Lowerer { 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 { - name: name.to_string(), - ty: ty.clone(), + let symbol = Symbol::new( + &name, + stmt.identifier.span, + ty.clone(), qsharp_ty, - span: stmt.identifier.span, - io_kind: IOKind::Default, - }; + IOKind::Default, + ); // process the symbol and init_expr gathering any errors let init_expr = match init_expr { Some(expr) => match expr { - syntax::ValueExpression::Expr(expr) => semantic::ValueExpression::Expr( - self.lower_expr_with_target_type(Some(expr), &ty, stmt_span), - ), - syntax::ValueExpression::Measurement(measure_expr) => { - semantic::ValueExpression::Measurement( - self.lower_measure_expr(measure_expr, stmt_span), - ) + 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 => semantic::ValueExpression::Expr( - self.lower_expr_with_target_type(None, &ty, stmt_span), - ), + None => self.cast_expr_with_target_type_or_default(None, &ty, stmt_span), }; let symbol_id = @@ -834,25 +903,41 @@ impl Lowerer { 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 symbol = Symbol { - name: name.to_string(), - ty: ty.clone(), - qsharp_ty, - span: stmt.identifier.span, - io_kind: IOKind::Default, + 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), }; - // process the symbol and init_expr gathering any errors - let init_expr = self.lower_expr_with_target_type(Some(&stmt.init_expr), &ty, stmt.span); + 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, stmt.identifier.span); + 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(semantic::ValueExpression::Expr(init_expr)), + init_expr: Box::new(init_expr), }) } @@ -902,13 +987,13 @@ impl Lowerer { 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 { - name: stmt.ident.name.to_string(), - span: stmt.ident.span, - ty: ty.clone(), + let symbol = Symbol::new( + &stmt.ident.name, + stmt.ident.span, + ty.clone(), qsharp_ty, - io_kind: IOKind::Default, - }; + IOKind::Default, + ); // This is the first variable in this scope, so // we don't need to check for redefined symbols. @@ -941,8 +1026,8 @@ impl Lowerer { // 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(false); - let condition = self.cast_expr_to_type(&cond_ty, &condition, condition.span); + 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, @@ -953,8 +1038,172 @@ impl Lowerer { } fn lower_gate_call(&mut self, stmt: &syntax::GateCall) -> semantic::StmtKind { - self.push_unimplemented_error_message("gate call stmt", stmt.span); - semantic::StmtKind::Err + // 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| self.lower_expr(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); + } else { + name = get_qsharp_gate_name(&name).unwrap_or(&name).to_string(); + } + + // 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, + quantum_arity, + quantum_arity_with_modifiers, + }) + + // 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. + } + + 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.symbols) 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) } fn lower_gphase(&mut self, stmt: &syntax::GPhase) -> semantic::StmtKind { @@ -992,13 +1241,7 @@ impl Lowerer { 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 { - name: name.to_string(), - ty: ty.clone(), - qsharp_ty, - span: stmt.ident.span, - io_kind, - }; + 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, stmt.ident.span); @@ -1021,9 +1264,21 @@ impl Lowerer { }) } - fn lower_measure(&mut self, stmt: &syntax::MeasureStmt) -> semantic::StmtKind { - self.push_unimplemented_error_message("measure stmt", stmt.span); - semantic::StmtKind::Err + 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 { @@ -1037,13 +1292,69 @@ impl Lowerer { } fn lower_quantum_decl(&mut self, stmt: &syntax::QubitDeclaration) -> semantic::StmtKind { - self.push_unimplemented_error_message("qubit decl stmt", stmt.span); - semantic::StmtKind::Err + // 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.symbols)) + { + 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, stmt.qubit.span); + + 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 { - self.push_unimplemented_error_message("reset stmt", stmt.span); - semantic::StmtKind::Err + 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 { @@ -1072,16 +1383,17 @@ impl Lowerer { // 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, false); - let target = self.cast_expr_to_type(&target_ty, &target, target.span); + let target_ty = Type::Int(None, target.ty.is_const()); + let target = self.cast_expr_to_type(&target_ty, &target); // It is a parse error to have a switch statement with no cases, // even if the default block is present. Getting here means the // parser is broken or they changed the grammar. - assert!( - !cases.is_empty(), - "switch statement must have a control expression and at least one case" - ); + if cases.is_empty() { + self.push_semantic_error(SemanticErrorKind::SwitchStatementMustHaveAtLeastOneCase( + stmt.span, + )); + } // We push a semantic error on switch statements if version is less than 3.1, // as they were introduced in 3.1. @@ -1109,7 +1421,7 @@ impl Lowerer { } fn lower_switch_case(&mut self, switch_case: &syntax::SwitchCase) -> semantic::SwitchCase { - let label_ty = Type::Int(None, false); + let label_ty = Type::Int(None, true); let labels = switch_case .labels .iter() @@ -1118,7 +1430,7 @@ impl Lowerer { // 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, label.span) + self.cast_expr_to_type(&label_ty, &label) }) .collect::>(); @@ -1132,28 +1444,27 @@ impl Lowerer { } fn lower_while_stmt(&mut self, stmt: &syntax::WhileLoop) -> semantic::StmtKind { - let while_condition = self.lower_expr(&stmt.while_condition); + 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(false); - let while_condition = - self.cast_expr_to_type(&cond_ty, &while_condition, while_condition.span); + let cond_ty = Type::Bool(condition.ty.is_const()); + let while_condition = self.cast_expr_to_type(&cond_ty, &condition); semantic::StmtKind::WhileLoop(semantic::WhileLoop { span: stmt.span, - while_condition, + condition: while_condition, body, }) } fn get_semantic_type_from_tydef( &mut self, - scalar_ty: &syntax::TypeDef, + ty: &syntax::TypeDef, is_const: bool, ) -> crate::semantic::types::Type { - match scalar_ty { + match ty { syntax::TypeDef::Scalar(scalar_type) => { self.get_semantic_type_from_scalar_ty(scalar_type, is_const) } @@ -1166,38 +1477,73 @@ impl Lowerer { } } - /// designators are positive integer literals when used - /// in the context of a type definition. - fn get_size_designator_from_expr(&mut self, expr: &syntax::Expr) -> Option { - if let syntax::ExprKind::Lit(lit) = expr.kind.as_ref() { - if let syntax::LiteralKind::Int(value) = lit.kind { - if value > 0 { - if let Ok(value) = u32::try_from(value) { - Some(value) - } else { - self.push_semantic_error(SemanticErrorKind::DesignatorTooLarge(lit.span)); - None - } - } else { - self.push_semantic_error( - SemanticErrorKind::DesignatorMustBePositiveIntLiteral(lit.span), - ); - None - } - } else { - self.push_semantic_error(SemanticErrorKind::DesignatorMustBePositiveIntLiteral( - lit.span, - )); - None - } + /// 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.symbols) { + Some(lit) } else { - self.push_semantic_error(SemanticErrorKind::DesignatorMustBePositiveIntLiteral( + 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, @@ -1206,11 +1552,11 @@ impl Lowerer { match &scalar_ty.kind { syntax::ScalarTypeKind::Bit(bit_type) => match &bit_type.size { Some(size) => { - let Some(size) = self.get_size_designator_from_expr(size) else { + 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::One(size), + super::types::ArrayDimensions::from(size), is_const, ) } @@ -1218,7 +1564,7 @@ impl Lowerer { }, syntax::ScalarTypeKind::Int(int_type) => match &int_type.size { Some(size) => { - let Some(size) = self.get_size_designator_from_expr(size) else { + 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) @@ -1227,7 +1573,7 @@ impl Lowerer { }, syntax::ScalarTypeKind::UInt(uint_type) => match &uint_type.size { Some(size) => { - let Some(size) = self.get_size_designator_from_expr(size) else { + 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) @@ -1236,7 +1582,7 @@ impl Lowerer { }, syntax::ScalarTypeKind::Float(float_type) => match &float_type.size { Some(size) => { - let Some(size) = self.get_size_designator_from_expr(size) else { + let Some(size) = self.const_eval_type_width_designator_from_expr(size) else { return crate::semantic::types::Type::Err; }; crate::semantic::types::Type::Float(Some(size), is_const) @@ -1246,7 +1592,8 @@ impl Lowerer { syntax::ScalarTypeKind::Complex(complex_type) => match &complex_type.base_size { Some(float_type) => match &float_type.size { Some(size) => { - let Some(size) = self.get_size_designator_from_expr(size) else { + 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) @@ -1257,7 +1604,7 @@ impl Lowerer { }, syntax::ScalarTypeKind::Angle(angle_type) => match &angle_type.size { Some(size) => { - let Some(size) = self.get_size_designator_from_expr(size) else { + let Some(size) = self.const_eval_type_width_designator_from_expr(size) else { return crate::semantic::types::Type::Err; }; crate::semantic::types::Type::Angle(Some(size), is_const) @@ -1291,22 +1638,33 @@ impl Lowerer { ); crate::semantic::types::Type::Err } - fn lower_expr_with_target_type( + + fn cast_expr_with_target_type_or_default( &mut self, - expr: Option<&syntax::Expr>, + expr: Option, ty: &Type, span: Span, ) -> semantic::Expr { - let Some(expr) = expr else { + 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 = self.lower_expr(expr); + 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; } @@ -1332,17 +1690,19 @@ impl Lowerer { // 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, span) + self.cast_expr_to_type(ty, &rhs) } - fn lower_measure_expr( - &mut self, - expr: &syntax::MeasureExpr, - span: Span, - ) -> semantic::MeasureExpr { - semantic::MeasureExpr { - span, + 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), } } @@ -1358,9 +1718,8 @@ impl Lowerer { } }; let expr = match ty { - Type::Bit(_) | Type::Int(_, _) | Type::UInt(_, _) => { - Some(from_lit_kind(LiteralKind::Int(0))) - } + 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::Angle(_, _) | Type::Float(_, _) => Some(from_lit_kind(LiteralKind::Float(0.0))), Type::Complex(_, _) => Some(from_lit_kind(LiteralKind::Complex(0.0, 0.0))), @@ -1384,10 +1743,19 @@ impl Lowerer { self.push_unsupported_error_message(message, span); None } - Type::BitArray(_, _) => { - self.push_unimplemented_error_message("bit array default value", 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::BoolArray(_) => { self.push_unimplemented_error_message("bool array default value", span); None @@ -1416,16 +1784,12 @@ impl Lowerer { self.push_unimplemented_error_message("uint array default value", span); None } - Type::Duration(_) - | Type::Err - | Type::Gate(_, _) - | Type::Range - | Type::Set - | Type::Void => { + Type::Duration(_) | Type::Gate(_, _) | Type::Range | Type::Set | Type::Void => { let message = format!("Default values for {ty:?} are unsupported."); self.push_unsupported_error_message(message, span); None } + Type::Err => None, }; let Some(expr) = expr else { return err_expr!(ty.as_const()); @@ -1445,6 +1809,7 @@ impl Lowerer { }; expr } + #[allow(clippy::too_many_lines)] fn try_coerce_literal_expr_to_type( &mut self, @@ -1475,15 +1840,15 @@ impl Lowerer { // 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::Int(*value))), + 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::Int( - i64::from(*value), - ))), + kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Bit(*value))), ty: lhs_ty.as_const(), }); } @@ -1677,14 +2042,9 @@ impl Lowerer { } } - fn cast_expr_to_type( - &mut self, - ty: &Type, - expr: &semantic::Expr, - span: Span, - ) -> semantic::Expr { - let Some(cast_expr) = self.try_cast_expr_to_type(ty, expr, span) else { - self.push_invalid_cast_error(ty, &expr.ty, span); + 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 @@ -1694,7 +2054,6 @@ impl Lowerer { &mut self, ty: &Type, expr: &semantic::Expr, - span: Span, ) -> Option { if *ty == expr.ty { // Base case, we shouldn't have gotten here @@ -1704,7 +2063,10 @@ impl Lowerer { 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 - return Some(expr.clone()); + 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 @@ -1740,7 +2102,7 @@ impl Lowerer { // 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, span), + 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), @@ -1774,24 +2136,19 @@ impl Lowerer { /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ /// | bit | Yes | Yes | Yes | No | Yes | - | No | No | /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - fn cast_bit_expr_to_type( - &mut self, - ty: &Type, - rhs: &semantic::Expr, - span: Span, - ) -> Option { + fn cast_bit_expr_to_type(&mut self, 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::Angle(..) => { let msg = "Cast bit to angle"; - self.push_unimplemented_error_message(msg, span); + self.push_unimplemented_error_message(msg, rhs.span); None } &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 + // casts to other types that cast to float. For now, we'll // say it is invalid like the spec. None } @@ -1921,7 +2278,7 @@ impl Lowerer { 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, false); + 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( @@ -1955,10 +2312,10 @@ impl Lowerer { }; // Now that we know the effective Uint type, we can cast the lhs and rhs // so that operations can be performed on them. - let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs.span); + 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, rhs.span) + self.cast_expr_to_type(&ty, &rhs) } else { rhs }; @@ -1975,7 +2332,7 @@ impl Lowerer { ty, }; - let final_expr = self.cast_expr_to_type(&left_type, &expr, span); + let final_expr = self.cast_expr_to_type(&left_type, &expr); return final_expr; } @@ -1986,15 +2343,17 @@ impl Lowerer { // 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(false); - let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs.span); - let new_rhs = self.cast_expr_to_type(&ty, &rhs, rhs.span); + 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_int_conversion_for_type(op, &left_type, &rhs.ty) { - let ty = Type::Int(None, false); - let new_lhs = self.cast_expr_to_type(&ty, &lhs, lhs.span); - let new_rhs = self.cast_expr_to_type(&ty, &rhs, lhs.span); + 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); @@ -2008,10 +2367,10 @@ impl Lowerer { { self.coerce_literal_expr_to_type(&promoted_type, &lhs, kind) } else { - self.cast_expr_to_type(&promoted_type, &lhs, lhs.span) + self.cast_expr_to_type(&promoted_type, &lhs) } } - _ => self.cast_expr_to_type(&promoted_type, &lhs, lhs.span), + _ => self.cast_expr_to_type(&promoted_type, &lhs), } }; let new_right = if promoted_type == right_type { @@ -2024,16 +2383,16 @@ impl Lowerer { { self.coerce_literal_expr_to_type(&promoted_type, &rhs, kind) } else { - self.cast_expr_to_type(&promoted_type, &rhs, rhs.span) + self.cast_expr_to_type(&promoted_type, &rhs) } } - _ => self.cast_expr_to_type(&promoted_type, &rhs, rhs.span), + _ => self.cast_expr_to_type(&promoted_type, &rhs), } }; (new_left, new_right, promoted_type) } else if binop_requires_symmetric_int_conversion(op) { - let ty = Type::Int(None, false); - let new_rhs = self.cast_expr_to_type(&ty, &rhs, rhs.span); + let ty = Type::Int(None, ty_constness); + let new_rhs = self.cast_expr_to_type(&ty, &rhs); (lhs, new_rhs, left_type) } else { (lhs, rhs, left_type) @@ -2082,7 +2441,7 @@ impl Lowerer { | semantic::BinOp::Lt | semantic::BinOp::Lte | semantic::BinOp::Neq - | semantic::BinOp::OrL => Type::Bool(false), + | semantic::BinOp::OrL => Type::Bool(ty_constness), _ => ty, }; let mut expr = expr; @@ -2302,10 +2661,27 @@ impl Lowerer { #[allow(clippy::unused_self)] fn lower_gate_operand(&mut self, operand: &syntax::GateOperand) -> semantic::GateOperand { - match operand { - syntax::GateOperand::IndexedIdent(_) - | syntax::GateOperand::HardwareQubit(_) - | syntax::GateOperand::Err => semantic::GateOperand::Err, + 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(), } } @@ -2421,3 +2797,26 @@ fn get_identifier_name(identifier: &syntax::Identifier) -> std::rc::Rc { 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, + }; + + // ch, crx, cry, crz, sdg, and tdg + match gate_name.as_ref() { + "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)))), + "sdg" => Some(("S".to_string(), make_modifier(Inv))), + "tdg" => Some(("T".to_string(), make_modifier(Inv))), + _ => None, + } +} diff --git a/compiler/qsc_qasm3/src/semantic/symbols.rs b/compiler/qsc_qasm3/src/semantic/symbols.rs index 5479c8ada4..11a0a01dd6 100644 --- a/compiler/qsc_qasm3/src/semantic/symbols.rs +++ b/compiler/qsc_qasm3/src/semantic/symbols.rs @@ -1,12 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use core::f64; use std::rc::Rc; use qsc_data_structures::{index_map::IndexMap, span::Span}; use rustc_hash::FxHashMap; -use super::types::Type; +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 @@ -89,13 +93,59 @@ impl std::fmt::Display for SymbolId { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[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 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 { @@ -119,6 +169,7 @@ impl Default for Symbol { ty: Type::Err, qsharp_ty: crate::types::Type::Tuple(vec![]), io_kind: IOKind::default(), + const_expr: None, } } } @@ -223,7 +274,14 @@ pub enum ScopeKind { Block, } -const BUILTIN_SYMBOLS: [&str; 6] = ["pi", "π", "tau", "τ", "euler", "ℇ"]; +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 { @@ -235,14 +293,42 @@ impl Default for SymbolTable { current_id: SymbolId::default(), }; - // Define global constants - for symbol in BUILTIN_SYMBOLS { + 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: Type::Float(None, true), + 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}")); } @@ -287,6 +373,7 @@ impl SymbolTable { 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(); @@ -440,7 +527,11 @@ impl SymbolTable { for symbol in scope .get_ordered_symbols() .into_iter() - .filter(|symbol| !BUILTIN_SYMBOLS.contains(&symbol.name.as_str())) + .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() { diff --git a/compiler/qsc_qasm3/src/semantic/tests.rs b/compiler/qsc_qasm3/src/semantic/tests.rs index 268ace49fb..ce82fc5ff6 100644 --- a/compiler/qsc_qasm3/src/semantic/tests.rs +++ b/compiler/qsc_qasm3/src/semantic/tests.rs @@ -243,15 +243,15 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { Stmt [196-206]: annotations: kind: ClassicalDeclarationStmt [196-206]: - symbol_id: 24 + symbol_id: 26 ty_span: [196-199] init_expr: Expr [204-205]: ty: Bit(true) - kind: Lit: Int(1) + kind: Lit: Bit(1) Stmt [211-227]: annotations: kind: ClassicalDeclarationStmt [211-227]: - symbol_id: 24 + symbol_id: 26 ty_span: [211-215] init_expr: Expr [220-226]: ty: Bool(false) @@ -259,18 +259,18 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { op: AndL lhs: Expr [220-221]: ty: Err - kind: SymbolId(25) + kind: SymbolId(27) rhs: Expr [225-226]: ty: Bool(false) kind: Cast [0-0]: ty: Bool(false) expr: Expr [225-226]: ty: Bit(false) - kind: SymbolId(24) + kind: SymbolId(26) Stmt [140-154]: annotations: kind: ClassicalDeclarationStmt [140-154]: - symbol_id: 26 + symbol_id: 28 ty_span: [140-145] init_expr: Expr [150-153]: ty: Angle(None, true) @@ -278,7 +278,7 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { Stmt [159-179]: annotations: kind: ClassicalDeclarationStmt [159-179]: - symbol_id: 27 + symbol_id: 29 ty_span: [159-164] init_expr: Expr [169-178]: ty: Float(None, false) @@ -286,7 +286,7 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { op: Add lhs: Expr [169-170]: ty: Angle(None, false) - kind: SymbolId(26) + kind: SymbolId(28) rhs: Expr [173-178]: ty: Float(None, false) kind: Cast [0-0]: @@ -297,11 +297,11 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { Stmt [74-84]: annotations: kind: ClassicalDeclarationStmt [74-84]: - symbol_id: 29 + symbol_id: 31 ty_span: [74-77] init_expr: Expr [82-83]: ty: Err - kind: SymbolId(28) + kind: SymbolId(30) [Qsc.Qasm3.Compile.UndefinedSymbol @@ -348,10 +348,10 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { , Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Err to type Bit(false) - ,-[source0.qasm:4:5] + ,-[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/decls.rs b/compiler/qsc_qasm3/src/semantic/tests/decls.rs index ca1c08904f..daf0381d85 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls.rs @@ -61,106 +61,61 @@ fn scalar_ty_designator_must_be_positive() { Stmt [0-10]: annotations: kind: ClassicalDeclarationStmt [0-10]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-7] init_expr: Expr [0-0]: ty: Err kind: Err - [Qsc.Qasm3.Compile.DesignatorMustBePositiveIntLiteral + [Qsc.Qasm3.Compile.TypeWidthMustBePositiveIntConstExpr - x Designator must be a positive literal integer. + x Type width must be a positive integer const expression. ,-[test:1:5] 1 | int[-5] i; : ^^ `---- - , Qsc.Qasm3.Compile.Unimplemented - - x this statement is not yet handled during OpenQASM 3 import: Converting Err - | to Q# type - ,-[test:1:1] - 1 | int[-5] i; - : ^^^^^^^ - `---- - , Qsc.Qasm3.Compile.NotSupported - - x Default values for Err are unsupported. are not supported. - ,-[test:1:1] - 1 | int[-5] i; - : ^^^^^^^^^^ - `---- ]"#]], ); } #[test] -fn scalar_ty_designator_must_be_int_literal() { +fn scalar_ty_designator_must_be_castable_to_const_int() { check( - r#"int[size] i; float[0.0] j;"#, + r#"const angle size = 2.0; int[size] i;"#, &expect![[r#" Program: version: statements: - Stmt [0-12]: + Stmt [0-23]: annotations: - kind: ClassicalDeclarationStmt [0-12]: - symbol_id: 6 - ty_span: [0-9] - init_expr: Expr [0-0]: - ty: Err - kind: Err - Stmt [13-26]: + kind: ClassicalDeclarationStmt [0-23]: + symbol_id: 8 + ty_span: [6-11] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(2.0) + Stmt [24-36]: annotations: - kind: ClassicalDeclarationStmt [13-26]: - symbol_id: 7 - ty_span: [13-23] + kind: ClassicalDeclarationStmt [24-36]: + symbol_id: 9 + ty_span: [24-33] init_expr: Expr [0-0]: ty: Err kind: Err - [Qsc.Qasm3.Compile.DesignatorMustBePositiveIntLiteral + [Qsc.Qasm3.Compile.CannotCast - x Designator must be a positive literal integer. - ,-[test:1:5] - 1 | int[size] i; float[0.0] j; - : ^^^^ + 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.Unimplemented - - x this statement is not yet handled during OpenQASM 3 import: Converting Err - | to Q# type - ,-[test:1:1] - 1 | int[size] i; float[0.0] j; - : ^^^^^^^^^ - `---- - , Qsc.Qasm3.Compile.NotSupported - - x Default values for Err are unsupported. are not supported. - ,-[test:1:1] - 1 | int[size] i; float[0.0] j; - : ^^^^^^^^^^^^ - `---- - , Qsc.Qasm3.Compile.DesignatorMustBePositiveIntLiteral - - x Designator must be a positive literal integer. - ,-[test:1:20] - 1 | int[size] i; float[0.0] j; - : ^^^ - `---- - , Qsc.Qasm3.Compile.Unimplemented - - x this statement is not yet handled during OpenQASM 3 import: Converting Err - | to Q# type - ,-[test:1:14] - 1 | int[size] i; float[0.0] j; - : ^^^^^^^^^^ - `---- - , Qsc.Qasm3.Compile.NotSupported + , Qsc.Qasm3.Compile.TypeWidthMustBePositiveIntConstExpr - x Default values for Err are unsupported. are not supported. - ,-[test:1:14] - 1 | int[size] i; float[0.0] j; - : ^^^^^^^^^^^^^ + 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 index 8643afdd38..74363762e3 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs @@ -11,12 +11,12 @@ fn implicit_bitness_default() { "angle x;", &expect![[r#" ClassicalDeclarationStmt [0-8]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-5] init_expr: Expr [0-0]: ty: Angle(None, true) kind: Lit: Float(0.0) - [6] Symbol [6-7]: + [8] Symbol [6-7]: name: x type: Angle(None, false) qsharp_type: Double @@ -30,12 +30,12 @@ fn lit() { "angle x = 42.1;", &expect![[r#" ClassicalDeclarationStmt [0-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-5] init_expr: Expr [10-14]: ty: Angle(None, true) kind: Lit: Float(42.1) - [6] Symbol [6-7]: + [8] Symbol [6-7]: name: x type: Angle(None, false) qsharp_type: Double @@ -49,12 +49,12 @@ fn const_lit() { "const angle x = 42.1;", &expect![[r#" ClassicalDeclarationStmt [0-21]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-11] init_expr: Expr [16-20]: ty: Angle(None, true) kind: Lit: Float(42.1) - [6] Symbol [12-13]: + [8] Symbol [12-13]: name: x type: Angle(None, true) qsharp_type: Double @@ -68,12 +68,12 @@ fn lit_explicit_width() { "angle[64] x = 42.1;", &expect![[r#" ClassicalDeclarationStmt [0-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-9] init_expr: Expr [14-18]: ty: Angle(Some(64), true) kind: Lit: Float(42.1) - [6] Symbol [10-11]: + [8] Symbol [10-11]: name: x type: Angle(Some(64), false) qsharp_type: Double @@ -87,12 +87,12 @@ fn const_explicit_width_lit() { "const angle[64] x = 42.1;", &expect![[r#" ClassicalDeclarationStmt [0-25]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-15] init_expr: Expr [20-24]: ty: Angle(Some(64), true) kind: Lit: Float(42.1) - [6] Symbol [16-17]: + [8] Symbol [16-17]: name: x type: Angle(Some(64), true) qsharp_type: Double @@ -106,12 +106,12 @@ fn lit_decl_leading_dot() { "angle x = .421;", &expect![[r#" ClassicalDeclarationStmt [0-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-5] init_expr: Expr [10-14]: ty: Angle(None, true) kind: Lit: Float(0.421) - [6] Symbol [6-7]: + [8] Symbol [6-7]: name: x type: Angle(None, false) qsharp_type: Double @@ -125,12 +125,12 @@ fn const_lit_decl_leading_dot() { "const angle x = .421;", &expect![[r#" ClassicalDeclarationStmt [0-21]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-11] init_expr: Expr [16-20]: ty: Angle(None, true) kind: Lit: Float(0.421) - [6] Symbol [12-13]: + [8] Symbol [12-13]: name: x type: Angle(None, true) qsharp_type: Double @@ -144,12 +144,12 @@ fn const_lit_decl_leading_dot_scientific() { "const angle x = .421e2;", &expect![[r#" ClassicalDeclarationStmt [0-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-11] init_expr: Expr [16-22]: ty: Angle(None, true) kind: Lit: Float(42.1) - [6] Symbol [12-13]: + [8] Symbol [12-13]: name: x type: Angle(None, true) qsharp_type: Double @@ -163,12 +163,12 @@ fn lit_decl_trailing_dot() { "angle x = 421.;", &expect![[r#" ClassicalDeclarationStmt [0-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-5] init_expr: Expr [10-14]: ty: Angle(None, true) kind: Lit: Float(421.0) - [6] Symbol [6-7]: + [8] Symbol [6-7]: name: x type: Angle(None, false) qsharp_type: Double @@ -182,12 +182,12 @@ fn const_lit_decl_trailing_dot() { "const angle x = 421.;", &expect![[r#" ClassicalDeclarationStmt [0-21]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-11] init_expr: Expr [16-20]: ty: Angle(None, true) kind: Lit: Float(421.0) - [6] Symbol [12-13]: + [8] Symbol [12-13]: name: x type: Angle(None, true) qsharp_type: Double @@ -201,12 +201,12 @@ fn lit_decl_scientific() { "angle x = 4.21e1;", &expect![[r#" ClassicalDeclarationStmt [0-17]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-5] init_expr: Expr [10-16]: ty: Angle(None, true) kind: Lit: Float(42.1) - [6] Symbol [6-7]: + [8] Symbol [6-7]: name: x type: Angle(None, false) qsharp_type: Double @@ -220,12 +220,12 @@ fn const_lit_decl_scientific() { "const angle x = 4.21e1;", &expect![[r#" ClassicalDeclarationStmt [0-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-11] init_expr: Expr [16-22]: ty: Angle(None, true) kind: Lit: Float(42.1) - [6] Symbol [12-13]: + [8] Symbol [12-13]: name: x type: Angle(None, true) qsharp_type: Double @@ -239,12 +239,12 @@ fn lit_decl_scientific_signed_pos() { "angle x = 4.21e+1;", &expect![[r#" ClassicalDeclarationStmt [0-18]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-5] init_expr: Expr [10-17]: ty: Angle(None, true) kind: Lit: Float(42.1) - [6] Symbol [6-7]: + [8] Symbol [6-7]: name: x type: Angle(None, false) qsharp_type: Double @@ -258,12 +258,12 @@ fn const_lit_decl_scientific_signed_pos() { "const angle x = 4.21e+1;", &expect![[r#" ClassicalDeclarationStmt [0-24]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-11] init_expr: Expr [16-23]: ty: Angle(None, true) kind: Lit: Float(42.1) - [6] Symbol [12-13]: + [8] Symbol [12-13]: name: x type: Angle(None, true) qsharp_type: Double @@ -277,12 +277,12 @@ fn lit_decl_scientific_cap_e() { "angle x = 4.21E1;", &expect![[r#" ClassicalDeclarationStmt [0-17]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-5] init_expr: Expr [10-16]: ty: Angle(None, true) kind: Lit: Float(42.1) - [6] Symbol [6-7]: + [8] Symbol [6-7]: name: x type: Angle(None, false) qsharp_type: Double @@ -296,12 +296,12 @@ fn const_lit_decl_scientific_cap_e() { "const angle x = 4.21E1;", &expect![[r#" ClassicalDeclarationStmt [0-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-11] init_expr: Expr [16-22]: ty: Angle(None, true) kind: Lit: Float(42.1) - [6] Symbol [12-13]: + [8] Symbol [12-13]: name: x type: Angle(None, true) qsharp_type: Double @@ -315,12 +315,12 @@ fn lit_decl_scientific_signed_neg() { "angle x = 421.0e-1;", &expect![[r#" ClassicalDeclarationStmt [0-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-5] init_expr: Expr [10-18]: ty: Angle(None, true) kind: Lit: Float(42.1) - [6] Symbol [6-7]: + [8] Symbol [6-7]: name: x type: Angle(None, false) qsharp_type: Double @@ -334,12 +334,12 @@ fn const_lit_decl_scientific_signed_neg() { "const angle x = 421.0e-1;", &expect![[r#" ClassicalDeclarationStmt [0-25]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-11] init_expr: Expr [16-24]: ty: Angle(None, true) kind: Lit: Float(42.1) - [6] Symbol [12-13]: + [8] Symbol [12-13]: name: x type: Angle(None, true) qsharp_type: Double @@ -353,7 +353,7 @@ fn const_lit_decl_signed_float_lit_cast_neg() { "const angle x = -7.;", &expect![[r#" ClassicalDeclarationStmt [0-20]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-11] init_expr: Expr [17-19]: ty: Angle(None, true) @@ -366,7 +366,7 @@ fn const_lit_decl_signed_float_lit_cast_neg() { expr: Expr [17-19]: ty: Float(None, true) kind: Lit: Float(7.0) - [6] Symbol [12-13]: + [8] Symbol [12-13]: name: x type: Angle(None, true) qsharp_type: Double @@ -385,7 +385,7 @@ fn const_lit_decl_signed_int_lit_cast_neg_fails() { Stmt [0-19]: annotations: kind: ClassicalDeclarationStmt [0-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-11] init_expr: Expr [17-18]: ty: Int(None, true) @@ -398,9 +398,9 @@ fn const_lit_decl_signed_int_lit_cast_neg_fails() { [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Int(None, true) to type Angle(None, true) - ,-[test:1:1] + ,-[test:1:18] 1 | const angle x = -7; - : ^^^^^^^^^^^^^^^^^^^ + : ^ `---- ]"#]], ); diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/bit.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/bit.rs index dc29049bf5..6a12f8b39e 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/bit.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/bit.rs @@ -11,12 +11,12 @@ fn with_no_init_expr_has_generated_lit_expr() { "bit a;", &expect![[r#" ClassicalDeclarationStmt [0-6]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-3] init_expr: Expr [0-0]: ty: Bit(true) - kind: Lit: Int(0) - [6] Symbol [4-5]: + kind: Lit: Bit(0) + [8] Symbol [4-5]: name: a type: Bit(false) qsharp_type: Result @@ -25,24 +25,21 @@ fn with_no_init_expr_has_generated_lit_expr() { } #[test] -#[ignore = "Unimplemented"] fn array_with_no_init_expr_has_generated_lit_expr() { check_classical_decl( "bit[4] a;", &expect![[r#" - Program: - version: - statements: - - [Qsc.Qasm3.Compile.Unimplemented - - x this statement is not yet handled during OpenQASM 3 import: bit array - | default value - ,-[test:1:1] - 1 | bit[4] a; - : ^^^^^^^^^ - `---- - ]"#]], + 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"#]], ); } @@ -52,12 +49,12 @@ fn decl_with_lit_0_init_expr() { "bit a = 0;", &expect![[r#" ClassicalDeclarationStmt [0-10]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-3] init_expr: Expr [8-9]: ty: Bit(true) - kind: Lit: Int(0) - [6] Symbol [4-5]: + kind: Lit: Bit(0) + [8] Symbol [4-5]: name: a type: Bit(false) qsharp_type: Result @@ -71,12 +68,12 @@ fn decl_with_lit_1_init_expr() { "bit a = 1;", &expect![[r#" ClassicalDeclarationStmt [0-10]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-3] init_expr: Expr [8-9]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [4-5]: + kind: Lit: Bit(1) + [8] Symbol [4-5]: name: a type: Bit(false) qsharp_type: Result @@ -90,12 +87,12 @@ fn const_decl_with_lit_0_init_expr() { "const bit a = 0;", &expect![[r#" ClassicalDeclarationStmt [0-16]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-9] init_expr: Expr [14-15]: ty: Bit(true) - kind: Lit: Int(0) - [6] Symbol [10-11]: + kind: Lit: Bit(0) + [8] Symbol [10-11]: name: a type: Bit(true) qsharp_type: Result @@ -109,12 +106,12 @@ fn const_decl_with_lit_1_init_expr() { "const bit a = 1;", &expect![[r#" ClassicalDeclarationStmt [0-16]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-9] init_expr: Expr [14-15]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [10-11]: + kind: Lit: Bit(1) + [8] Symbol [10-11]: name: a type: Bit(true) qsharp_type: Result diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/bool.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/bool.rs index d6b7dc7ea3..895c0a6942 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/bool.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/bool.rs @@ -11,12 +11,12 @@ fn with_no_init_expr_has_generated_lit_expr() { "bool a;", &expect![[r#" ClassicalDeclarationStmt [0-7]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-4] init_expr: Expr [0-0]: ty: Bool(true) kind: Lit: Bool(false) - [6] Symbol [5-6]: + [8] Symbol [5-6]: name: a type: Bool(false) qsharp_type: bool @@ -52,12 +52,12 @@ fn decl_with_lit_false_init_expr() { "bool a = false;", &expect![[r#" ClassicalDeclarationStmt [0-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-4] init_expr: Expr [9-14]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(false) - [6] Symbol [5-6]: + [8] Symbol [5-6]: name: a type: Bool(false) qsharp_type: bool @@ -71,12 +71,12 @@ fn decl_with_lit_true_init_expr() { "bool a = true;", &expect![[r#" ClassicalDeclarationStmt [0-14]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-4] init_expr: Expr [9-13]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(true) - [6] Symbol [5-6]: + [8] Symbol [5-6]: name: a type: Bool(false) qsharp_type: bool @@ -90,12 +90,12 @@ fn const_decl_with_lit_false_init_expr() { "const bool a = false;", &expect![[r#" ClassicalDeclarationStmt [0-21]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-10] init_expr: Expr [15-20]: ty: Bool(true) kind: Lit: Bool(false) - [6] Symbol [11-12]: + [8] Symbol [11-12]: name: a type: Bool(true) qsharp_type: bool @@ -109,12 +109,12 @@ fn const_decl_with_lit_true_init_expr() { "const bool a = true;", &expect![[r#" ClassicalDeclarationStmt [0-20]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-10] init_expr: Expr [15-19]: ty: Bool(true) kind: Lit: Bool(true) - [6] Symbol [11-12]: + [8] Symbol [11-12]: name: a type: Bool(true) qsharp_type: bool diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs index ab56fbd88b..6435cfa507 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs @@ -11,12 +11,12 @@ fn implicit_bitness_default() { "complex[float] x;", &expect![[r#" ClassicalDeclarationStmt [0-17]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-14] init_expr: Expr [0-0]: ty: Complex(None, true) kind: Lit: Complex(0.0, 0.0) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: Complex(None, false) qsharp_type: Complex @@ -30,12 +30,12 @@ fn explicit_bitness_default() { "complex[float[42]] x;", &expect![[r#" ClassicalDeclarationStmt [0-21]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-18] init_expr: Expr [0-0]: ty: Complex(Some(42), true) kind: Lit: Complex(0.0, 0.0) - [6] Symbol [19-20]: + [8] Symbol [19-20]: name: x type: Complex(Some(42), false) qsharp_type: Complex @@ -48,17 +48,17 @@ fn const_implicit_bitness_double_img_only() { check_classical_decl( "const complex[float] x = 1.01im;", &expect![[r#" - ClassicalDeclarationStmt [0-32]: - symbol_id: 6 - ty_span: [6-20] - init_expr: Expr [25-31]: - ty: Complex(None, true) - kind: Lit: Complex(0.0, 1.01) - [6] Symbol [21-22]: - name: x - type: Complex(None, true) - qsharp_type: Complex - io_kind: Default"#]], + 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"#]], ); } @@ -67,17 +67,17 @@ fn const_implicit_bitness_int_img_only() { check_classical_decl( "const complex[float] x = 1im;", &expect![[r#" - ClassicalDeclarationStmt [0-29]: - symbol_id: 6 - ty_span: [6-20] - init_expr: Expr [25-28]: - ty: Complex(None, true) - kind: Lit: Complex(0.0, 1.0) - [6] Symbol [21-22]: - name: x - type: Complex(None, true) - qsharp_type: Complex - io_kind: Default"#]], + 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"#]], ); } @@ -87,12 +87,12 @@ fn const_explicit_bitness_double_img_only() { "const complex[float[42]] x = 1.01im;", &expect![[r#" ClassicalDeclarationStmt [0-36]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-24] init_expr: Expr [29-35]: ty: Complex(Some(42), true) kind: Lit: Complex(0.0, 1.01) - [6] Symbol [25-26]: + [8] Symbol [25-26]: name: x type: Complex(Some(42), true) qsharp_type: Complex @@ -106,12 +106,12 @@ fn const_explicit_bitness_int_img_only() { "const complex[float[42]] x = 1im;", &expect![[r#" ClassicalDeclarationStmt [0-33]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-24] init_expr: Expr [29-32]: ty: Complex(Some(42), true) kind: Lit: Complex(0.0, 1.0) - [6] Symbol [25-26]: + [8] Symbol [25-26]: name: x type: Complex(Some(42), true) qsharp_type: Complex @@ -124,17 +124,17 @@ fn implicit_bitness_double_img_only() { check_classical_decl( "complex[float] x = 1.01im;", &expect![[r#" - ClassicalDeclarationStmt [0-26]: - symbol_id: 6 - ty_span: [0-14] - init_expr: Expr [19-25]: - ty: Complex(None, true) - kind: Lit: Complex(0.0, 1.01) - [6] Symbol [15-16]: - name: x - type: Complex(None, false) - qsharp_type: Complex - io_kind: Default"#]], + 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"#]], ); } @@ -143,17 +143,17 @@ fn implicit_bitness_int_img_only() { check_classical_decl( "complex[float] x = 1im;", &expect![[r#" - ClassicalDeclarationStmt [0-23]: - symbol_id: 6 - ty_span: [0-14] - init_expr: Expr [19-22]: - ty: Complex(None, true) - kind: Lit: Complex(0.0, 1.0) - [6] Symbol [15-16]: - name: x - type: Complex(None, false) - qsharp_type: Complex - io_kind: Default"#]], + 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"#]], ); } @@ -162,17 +162,17 @@ fn const_implicit_bitness_double_real_only() { check_classical_decl( "const complex[float] x = 1.01;", &expect![[r#" - ClassicalDeclarationStmt [0-30]: - symbol_id: 6 - ty_span: [6-20] - init_expr: Expr [25-29]: - ty: Complex(None, true) - kind: Lit: Complex(1.01, 0.0) - [6] Symbol [21-22]: - name: x - type: Complex(None, true) - qsharp_type: Complex - io_kind: Default"#]], + 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"#]], ); } @@ -181,17 +181,17 @@ fn const_implicit_bitness_int_real_only() { check_classical_decl( "const complex[float] x = 1;", &expect![[r#" - ClassicalDeclarationStmt [0-27]: - symbol_id: 6 - ty_span: [6-20] - init_expr: Expr [25-26]: - ty: Complex(None, true) - kind: Lit: Complex(1.0, 0.0) - [6] Symbol [21-22]: - name: x - type: Complex(None, true) - qsharp_type: Complex - io_kind: Default"#]], + 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"#]], ); } @@ -201,12 +201,12 @@ fn implicit_bitness_double_real_only() { "complex[float] x = 1.01;", &expect![[r#" ClassicalDeclarationStmt [0-24]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-14] init_expr: Expr [19-23]: ty: Complex(None, true) kind: Lit: Complex(1.01, 0.0) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: Complex(None, false) qsharp_type: Complex @@ -220,12 +220,12 @@ fn implicit_bitness_int_real_only() { "complex[float] x = 1;", &expect![[r#" ClassicalDeclarationStmt [0-21]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-14] init_expr: Expr [19-20]: ty: Complex(None, true) kind: Lit: Complex(1.0, 0.0) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: Complex(None, false) qsharp_type: Complex diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/creg.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/creg.rs index 1f7014b74e..7ac120ca04 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/creg.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/creg.rs @@ -11,12 +11,12 @@ fn with_no_init_expr_has_generated_lit_expr() { "creg a;", &expect![[r#" ClassicalDeclarationStmt [0-7]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-7] init_expr: Expr [0-0]: ty: Bit(true) - kind: Lit: Int(0) - [6] Symbol [5-6]: + kind: Lit: Bit(0) + [8] Symbol [5-6]: name: a type: Bit(false) qsharp_type: Result @@ -25,23 +25,20 @@ fn with_no_init_expr_has_generated_lit_expr() { } #[test] -#[ignore = "Unimplemented"] fn array_with_no_init_expr_has_generated_lit_expr() { check_classical_decl( "creg a[4];", &expect![[r#" - Program: - version: - statements: - - [Qsc.Qasm3.Compile.Unimplemented - - x this statement is not yet handled during OpenQASM 3 import: bit array - | default value - ,-[test:1:1] - 1 | creg a[4]; - : ^^^^^^^^^^ - `---- - ]"#]], + 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 index 870ea73d21..9f8a20da98 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/duration.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/duration.rs @@ -16,7 +16,7 @@ fn with_no_init_expr_has_generated_lit_expr() { Stmt [0-11]: annotations: kind: ClassicalDeclarationStmt [0-11]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-8] init_expr: Expr [0-0]: ty: Duration(true) diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs index 9af5a1933e..5c372ba5b6 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/float.rs @@ -11,12 +11,12 @@ fn implicit_bitness_default() { "float x;", &expect![[r#" ClassicalDeclarationStmt [0-8]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-5] init_expr: Expr [0-0]: ty: Float(None, true) kind: Lit: Float(0.0) - [6] Symbol [6-7]: + [8] Symbol [6-7]: name: x type: Float(None, false) qsharp_type: Double @@ -29,17 +29,17 @@ fn lit() { check_classical_decl( "float x = 42.1;", &expect![[r#" - ClassicalDeclarationStmt [0-15]: - symbol_id: 6 - ty_span: [0-5] - init_expr: Expr [10-14]: - ty: Float(None, true) - kind: Lit: Float(42.1) - [6] Symbol [6-7]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -48,17 +48,17 @@ fn const_lit() { check_classical_decl( "const float x = 42.1;", &expect![[r#" - ClassicalDeclarationStmt [0-21]: - symbol_id: 6 - ty_span: [6-11] - init_expr: Expr [16-20]: - ty: Float(None, true) - kind: Lit: Float(42.1) - [6] Symbol [12-13]: - name: x - type: Float(None, true) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -68,12 +68,12 @@ fn lit_explicit_width() { "float[64] x = 42.1;", &expect![[r#" ClassicalDeclarationStmt [0-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-9] init_expr: Expr [14-18]: ty: Float(Some(64), true) kind: Lit: Float(42.1) - [6] Symbol [10-11]: + [8] Symbol [10-11]: name: x type: Float(Some(64), false) qsharp_type: Double @@ -87,12 +87,12 @@ fn const_explicit_width_lit() { "const float[64] x = 42.1;", &expect![[r#" ClassicalDeclarationStmt [0-25]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-15] init_expr: Expr [20-24]: ty: Float(Some(64), true) kind: Lit: Float(42.1) - [6] Symbol [16-17]: + [8] Symbol [16-17]: name: x type: Float(Some(64), true) qsharp_type: Double @@ -105,17 +105,17 @@ fn lit_decl_leading_dot() { check_classical_decl( "float x = .421;", &expect![[r#" - ClassicalDeclarationStmt [0-15]: - symbol_id: 6 - ty_span: [0-5] - init_expr: Expr [10-14]: - ty: Float(None, true) - kind: Lit: Float(0.421) - [6] Symbol [6-7]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -124,17 +124,17 @@ fn const_lit_decl_leading_dot() { check_classical_decl( "const float x = .421;", &expect![[r#" - ClassicalDeclarationStmt [0-21]: - symbol_id: 6 - ty_span: [6-11] - init_expr: Expr [16-20]: - ty: Float(None, true) - kind: Lit: Float(0.421) - [6] Symbol [12-13]: - name: x - type: Float(None, true) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -143,17 +143,17 @@ fn const_lit_decl_leading_dot_scientific() { check_classical_decl( "const float x = .421e2;", &expect![[r#" - ClassicalDeclarationStmt [0-23]: - symbol_id: 6 - ty_span: [6-11] - init_expr: Expr [16-22]: - ty: Float(None, true) - kind: Lit: Float(42.1) - [6] Symbol [12-13]: - name: x - type: Float(None, true) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -162,17 +162,17 @@ fn lit_decl_trailing_dot() { check_classical_decl( "float x = 421.;", &expect![[r#" - ClassicalDeclarationStmt [0-15]: - symbol_id: 6 - ty_span: [0-5] - init_expr: Expr [10-14]: - ty: Float(None, true) - kind: Lit: Float(421.0) - [6] Symbol [6-7]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -181,17 +181,17 @@ fn const_lit_decl_trailing_dot() { check_classical_decl( "const float x = 421.;", &expect![[r#" - ClassicalDeclarationStmt [0-21]: - symbol_id: 6 - ty_span: [6-11] - init_expr: Expr [16-20]: - ty: Float(None, true) - kind: Lit: Float(421.0) - [6] Symbol [12-13]: - name: x - type: Float(None, true) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -200,17 +200,17 @@ fn lit_decl_scientific() { check_classical_decl( "float x = 4.21e1;", &expect![[r#" - ClassicalDeclarationStmt [0-17]: - symbol_id: 6 - ty_span: [0-5] - init_expr: Expr [10-16]: - ty: Float(None, true) - kind: Lit: Float(42.1) - [6] Symbol [6-7]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -219,17 +219,17 @@ fn const_lit_decl_scientific() { check_classical_decl( "const float x = 4.21e1;", &expect![[r#" - ClassicalDeclarationStmt [0-23]: - symbol_id: 6 - ty_span: [6-11] - init_expr: Expr [16-22]: - ty: Float(None, true) - kind: Lit: Float(42.1) - [6] Symbol [12-13]: - name: x - type: Float(None, true) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -238,17 +238,17 @@ fn lit_decl_scientific_signed_pos() { check_classical_decl( "float x = 4.21e+1;", &expect![[r#" - ClassicalDeclarationStmt [0-18]: - symbol_id: 6 - ty_span: [0-5] - init_expr: Expr [10-17]: - ty: Float(None, true) - kind: Lit: Float(42.1) - [6] Symbol [6-7]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -257,17 +257,17 @@ fn const_lit_decl_scientific_signed_pos() { check_classical_decl( "const float x = 4.21e+1;", &expect![[r#" - ClassicalDeclarationStmt [0-24]: - symbol_id: 6 - ty_span: [6-11] - init_expr: Expr [16-23]: - ty: Float(None, true) - kind: Lit: Float(42.1) - [6] Symbol [12-13]: - name: x - type: Float(None, true) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -276,17 +276,17 @@ fn lit_decl_scientific_cap_e() { check_classical_decl( "float x = 4.21E1;", &expect![[r#" - ClassicalDeclarationStmt [0-17]: - symbol_id: 6 - ty_span: [0-5] - init_expr: Expr [10-16]: - ty: Float(None, true) - kind: Lit: Float(42.1) - [6] Symbol [6-7]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -295,17 +295,17 @@ fn const_lit_decl_scientific_cap_e() { check_classical_decl( "const float x = 4.21E1;", &expect![[r#" - ClassicalDeclarationStmt [0-23]: - symbol_id: 6 - ty_span: [6-11] - init_expr: Expr [16-22]: - ty: Float(None, true) - kind: Lit: Float(42.1) - [6] Symbol [12-13]: - name: x - type: Float(None, true) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -314,17 +314,17 @@ fn lit_decl_scientific_signed_neg() { check_classical_decl( "float x = 421.0e-1;", &expect![[r#" - ClassicalDeclarationStmt [0-19]: - symbol_id: 6 - ty_span: [0-5] - init_expr: Expr [10-18]: - ty: Float(None, true) - kind: Lit: Float(42.1) - [6] Symbol [6-7]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -333,17 +333,17 @@ fn const_lit_decl_scientific_signed_neg() { check_classical_decl( "const float x = 421.0e-1;", &expect![[r#" - ClassicalDeclarationStmt [0-25]: - symbol_id: 6 - ty_span: [6-11] - init_expr: Expr [16-24]: - ty: Float(None, true) - kind: Lit: Float(42.1) - [6] Symbol [12-13]: - name: x - type: Float(None, true) - qsharp_type: Double - io_kind: Default"#]], + 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"#]], ); } @@ -353,7 +353,7 @@ fn const_lit_decl_signed_float_lit_cast_neg() { "const float x = -7.;", &expect![[r#" ClassicalDeclarationStmt [0-20]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-11] init_expr: Expr [17-19]: ty: Float(None, true) @@ -362,7 +362,7 @@ fn const_lit_decl_signed_float_lit_cast_neg() { expr: Expr [17-19]: ty: Float(None, true) kind: Lit: Float(7.0) - [6] Symbol [12-13]: + [8] Symbol [12-13]: name: x type: Float(None, true) qsharp_type: Double @@ -376,7 +376,7 @@ fn const_lit_decl_signed_int_lit_cast_neg() { "const float x = -7;", &expect![[r#" ClassicalDeclarationStmt [0-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-11] init_expr: Expr [17-18]: ty: Float(None, true) @@ -389,7 +389,7 @@ fn const_lit_decl_signed_int_lit_cast_neg() { expr: Expr [17-18]: ty: Int(None, true) kind: Lit: Int(7) - [6] Symbol [12-13]: + [8] Symbol [12-13]: name: x type: Float(None, true) qsharp_type: Double @@ -404,12 +404,12 @@ fn init_float_with_int_value_equal_max_safely_representable_values() { &format!("float a = {max_exact_int};"), &expect![[r#" ClassicalDeclarationStmt [0-27]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-5] init_expr: Expr [10-26]: ty: Float(None, true) kind: Lit: Float(9007199254740992.0) - [6] Symbol [6-7]: + [8] Symbol [6-7]: name: a type: Float(None, false) qsharp_type: Double @@ -430,7 +430,7 @@ fn init_float_with_int_value_greater_than_safely_representable_values() { Stmt [0-27]: annotations: kind: ClassicalDeclarationStmt [0-27]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-5] init_expr: Expr [10-26]: ty: Int(None, true) @@ -463,7 +463,7 @@ fn init_float_with_int_value_equal_min_safely_representable_values() { &format!("float a = {min_exact_int};"), &expect![[r#" ClassicalDeclarationStmt [0-28]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-5] init_expr: Expr [11-27]: ty: Float(None, false) @@ -476,7 +476,7 @@ fn init_float_with_int_value_equal_min_safely_representable_values() { expr: Expr [11-27]: ty: Int(None, true) kind: Lit: Int(9007199254740992) - [6] Symbol [6-7]: + [8] Symbol [6-7]: name: a type: Float(None, false) qsharp_type: Double diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs index 2e7d7ed18d..6beda22ff3 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/int.rs @@ -11,16 +11,16 @@ fn implicit_bitness_int_negative() { "int x = -42;", &expect![[r#" ClassicalDeclarationStmt [0-12]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-3] init_expr: Expr [9-11]: - ty: Int(None, true) + ty: Int(None, false) kind: UnaryOpExpr [9-11]: op: Neg expr: Expr [9-11]: ty: Int(None, true) kind: Lit: Int(42) - [6] Symbol [4-5]: + [8] Symbol [4-5]: name: x type: Int(None, false) qsharp_type: Int @@ -34,7 +34,7 @@ fn implicit_bitness_int_const_negative() { "const int x = -42;", &expect![[r#" ClassicalDeclarationStmt [0-18]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-9] init_expr: Expr [15-17]: ty: Int(None, true) @@ -43,7 +43,7 @@ fn implicit_bitness_int_const_negative() { expr: Expr [15-17]: ty: Int(None, true) kind: Lit: Int(42) - [6] Symbol [10-11]: + [8] Symbol [10-11]: name: x type: Int(None, true) qsharp_type: Int @@ -57,12 +57,12 @@ fn implicit_bitness_int_default() { "int x;", &expect![[r#" ClassicalDeclarationStmt [0-6]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-3] init_expr: Expr [0-0]: ty: Int(None, true) kind: Lit: Int(0) - [6] Symbol [4-5]: + [8] Symbol [4-5]: name: x type: Int(None, false) qsharp_type: Int @@ -76,12 +76,12 @@ fn const_implicit_bitness_int_lit() { "const int x = 42;", &expect![[r#" ClassicalDeclarationStmt [0-17]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-9] init_expr: Expr [14-16]: ty: Int(None, true) kind: Lit: Int(42) - [6] Symbol [10-11]: + [8] Symbol [10-11]: name: x type: Int(None, true) qsharp_type: Int @@ -95,12 +95,12 @@ fn implicit_bitness_int_hex_cap() { "int x = 0XFa_1F;", &expect![[r#" ClassicalDeclarationStmt [0-16]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-3] init_expr: Expr [8-15]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(64031) - [6] Symbol [4-5]: + [8] Symbol [4-5]: name: x type: Int(None, false) qsharp_type: Int @@ -114,12 +114,12 @@ fn const_implicit_bitness_int_hex_cap() { "const int y = 0XFa_1F;", &expect![[r#" ClassicalDeclarationStmt [0-22]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-9] init_expr: Expr [14-21]: ty: Int(None, true) kind: Lit: Int(64031) - [6] Symbol [10-11]: + [8] Symbol [10-11]: name: y type: Int(None, true) qsharp_type: Int @@ -133,12 +133,12 @@ fn implicit_bitness_int_octal() { "int x = 0o42;", &expect![[r#" ClassicalDeclarationStmt [0-13]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-3] init_expr: Expr [8-12]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(34) - [6] Symbol [4-5]: + [8] Symbol [4-5]: name: x type: Int(None, false) qsharp_type: Int @@ -152,12 +152,12 @@ fn const_implicit_bitness_int_octal() { "const int x = 0o42;", &expect![[r#" ClassicalDeclarationStmt [0-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-9] init_expr: Expr [14-18]: ty: Int(None, true) kind: Lit: Int(34) - [6] Symbol [10-11]: + [8] Symbol [10-11]: name: x type: Int(None, true) qsharp_type: Int @@ -171,12 +171,12 @@ fn const_implicit_bitness_int_octal_cap() { "const int x = 0O42;", &expect![[r#" ClassicalDeclarationStmt [0-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-9] init_expr: Expr [14-18]: ty: Int(None, true) kind: Lit: Int(34) - [6] Symbol [10-11]: + [8] Symbol [10-11]: name: x type: Int(None, true) qsharp_type: Int @@ -190,12 +190,12 @@ fn implicit_bitness_int_binary_low() { "int x = 0b1001_1001;", &expect![[r#" ClassicalDeclarationStmt [0-20]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-3] init_expr: Expr [8-19]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(153) - [6] Symbol [4-5]: + [8] Symbol [4-5]: name: x type: Int(None, false) qsharp_type: Int @@ -209,12 +209,12 @@ fn implicit_bitness_int_binary_cap() { "int x = 0B1010;", &expect![[r#" ClassicalDeclarationStmt [0-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-3] init_expr: Expr [8-14]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(10) - [6] Symbol [4-5]: + [8] Symbol [4-5]: name: x type: Int(None, false) qsharp_type: Int @@ -228,12 +228,12 @@ fn const_implicit_bitness_int_binary_low() { "const int x = 0b1001_1001;", &expect![[r#" ClassicalDeclarationStmt [0-26]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-9] init_expr: Expr [14-25]: ty: Int(None, true) kind: Lit: Int(153) - [6] Symbol [10-11]: + [8] Symbol [10-11]: name: x type: Int(None, true) qsharp_type: Int @@ -247,12 +247,12 @@ fn const_implicit_bitness_int_binary_cap() { "const int x = 0B1010;", &expect![[r#" ClassicalDeclarationStmt [0-21]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-9] init_expr: Expr [14-20]: ty: Int(None, true) kind: Lit: Int(10) - [6] Symbol [10-11]: + [8] Symbol [10-11]: name: x type: Int(None, true) qsharp_type: Int @@ -266,12 +266,12 @@ fn implicit_bitness_int_formatted() { "int x = 2_0_00;", &expect![[r#" ClassicalDeclarationStmt [0-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-3] init_expr: Expr [8-14]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(2000) - [6] Symbol [4-5]: + [8] Symbol [4-5]: name: x type: Int(None, false) qsharp_type: Int @@ -285,12 +285,12 @@ fn const_implicit_bitness_int_formatted() { "const int x = 2_0_00;", &expect![[r#" ClassicalDeclarationStmt [0-21]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-9] init_expr: Expr [14-20]: ty: Int(None, true) kind: Lit: Int(2000) - [6] Symbol [10-11]: + [8] Symbol [10-11]: name: x type: Int(None, true) qsharp_type: Int @@ -304,12 +304,12 @@ fn explicit_bitness_int_default() { "int[10] x;", &expect![[r#" ClassicalDeclarationStmt [0-10]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-7] init_expr: Expr [0-0]: ty: Int(Some(10), true) kind: Lit: Int(0) - [6] Symbol [8-9]: + [8] Symbol [8-9]: name: x type: Int(Some(10), false) qsharp_type: Int @@ -323,12 +323,12 @@ fn explicit_bitness_int() { "int[10] x = 42;", &expect![[r#" ClassicalDeclarationStmt [0-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-7] init_expr: Expr [12-14]: ty: Int(Some(10), true) kind: Lit: Int(42) - [6] Symbol [8-9]: + [8] Symbol [8-9]: name: x type: Int(Some(10), false) qsharp_type: Int @@ -342,12 +342,12 @@ fn const_explicit_bitness_int() { "const int[10] x = 42;", &expect![[r#" ClassicalDeclarationStmt [0-21]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-13] init_expr: Expr [18-20]: ty: Int(Some(10), true) kind: Lit: Int(42) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: Int(Some(10), true) qsharp_type: Int @@ -361,7 +361,7 @@ fn implicit_bitness_int_negative_float_decl_is_runtime_conversion() { "int x = -42.;", &expect![[r#" ClassicalDeclarationStmt [0-13]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-3] init_expr: Expr [9-12]: ty: Int(None, false) @@ -374,7 +374,7 @@ fn implicit_bitness_int_negative_float_decl_is_runtime_conversion() { expr: Expr [9-12]: ty: Float(None, true) kind: Lit: Float(42.0) - [6] Symbol [4-5]: + [8] Symbol [4-5]: name: x type: Int(None, false) qsharp_type: Int diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/qreg.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/qreg.rs index 37fb09e2a4..349b3bc768 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/qreg.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/qreg.rs @@ -6,23 +6,23 @@ use expect_test::expect; use crate::semantic::tests::check_stmt_kind; #[test] -#[ignore = "unimplemented"] -fn with_no_init_expr_has_generated_lit_expr() { +fn with_no_init_expr() { check_stmt_kind( "qreg a;", &expect![[r#" - Program: - version: - statements: - - [Qsc.Qasm3.Compile.Unimplemented + QubitDeclaration [0-7]: + symbol_id: 8"#]], + ); +} - x this statement is not yet handled during OpenQASM 3 import: qubit decl - | stmt - ,-[test:1:1] - 1 | qreg a; - : ^^^^^^^ - `---- - ]"#]], +#[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 index 8c9848f665..56f4f0dd61 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/stretch.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/stretch.rs @@ -16,7 +16,7 @@ fn with_no_init_expr_has_generated_lit_expr() { Stmt [0-10]: annotations: kind: ClassicalDeclarationStmt [0-10]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-7] init_expr: Expr [0-0]: ty: Stretch(true) diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs index f53b975b87..29eaf3c1ae 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/uint.rs @@ -11,12 +11,12 @@ fn implicit_bitness_int_default() { "uint x;", &expect![[r#" ClassicalDeclarationStmt [0-7]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-4] init_expr: Expr [0-0]: ty: UInt(None, true) kind: Lit: Int(0) - [6] Symbol [5-6]: + [8] Symbol [5-6]: name: x type: UInt(None, false) qsharp_type: Int @@ -30,12 +30,12 @@ fn const_implicit_bitness_int_lit() { "const uint x = 42;", &expect![[r#" ClassicalDeclarationStmt [0-18]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-10] init_expr: Expr [15-17]: ty: UInt(None, true) kind: Lit: Int(42) - [6] Symbol [11-12]: + [8] Symbol [11-12]: name: x type: UInt(None, true) qsharp_type: Int @@ -49,12 +49,12 @@ fn implicit_bitness_int_hex_cap() { "uint x = 0XFa_1F;", &expect![[r#" ClassicalDeclarationStmt [0-17]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-4] init_expr: Expr [9-16]: ty: UInt(None, true) kind: Lit: Int(64031) - [6] Symbol [5-6]: + [8] Symbol [5-6]: name: x type: UInt(None, false) qsharp_type: Int @@ -68,12 +68,12 @@ fn const_implicit_bitness_int_hex_low() { "const uint x = 0xFa_1F;", &expect![[r#" ClassicalDeclarationStmt [0-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-10] init_expr: Expr [15-22]: ty: UInt(None, true) kind: Lit: Int(64031) - [6] Symbol [11-12]: + [8] Symbol [11-12]: name: x type: UInt(None, true) qsharp_type: Int @@ -87,12 +87,12 @@ fn const_implicit_bitness_int_hex_cap() { "const uint y = 0XFa_1F;", &expect![[r#" ClassicalDeclarationStmt [0-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-10] init_expr: Expr [15-22]: ty: UInt(None, true) kind: Lit: Int(64031) - [6] Symbol [11-12]: + [8] Symbol [11-12]: name: y type: UInt(None, true) qsharp_type: Int @@ -106,12 +106,12 @@ fn implicit_bitness_int_octal_low() { "uint x = 0o42;", &expect![[r#" ClassicalDeclarationStmt [0-14]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-4] init_expr: Expr [9-13]: ty: UInt(None, true) kind: Lit: Int(34) - [6] Symbol [5-6]: + [8] Symbol [5-6]: name: x type: UInt(None, false) qsharp_type: Int @@ -125,12 +125,12 @@ fn implicit_bitness_int_octal_cap() { "uint x = 0O42;", &expect![[r#" ClassicalDeclarationStmt [0-14]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-4] init_expr: Expr [9-13]: ty: UInt(None, true) kind: Lit: Int(34) - [6] Symbol [5-6]: + [8] Symbol [5-6]: name: x type: UInt(None, false) qsharp_type: Int @@ -144,12 +144,12 @@ fn const_implicit_bitness_int_octal_low() { "const uint x = 0o42;", &expect![[r#" ClassicalDeclarationStmt [0-20]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-10] init_expr: Expr [15-19]: ty: UInt(None, true) kind: Lit: Int(34) - [6] Symbol [11-12]: + [8] Symbol [11-12]: name: x type: UInt(None, true) qsharp_type: Int @@ -163,12 +163,12 @@ fn const_implicit_bitness_int_octal_cap() { "const uint x = 0O42;", &expect![[r#" ClassicalDeclarationStmt [0-20]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-10] init_expr: Expr [15-19]: ty: UInt(None, true) kind: Lit: Int(34) - [6] Symbol [11-12]: + [8] Symbol [11-12]: name: x type: UInt(None, true) qsharp_type: Int @@ -182,12 +182,12 @@ fn implicit_bitness_int_binary_low() { "uint x = 0b1001_1001;", &expect![[r#" ClassicalDeclarationStmt [0-21]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-4] init_expr: Expr [9-20]: ty: UInt(None, true) kind: Lit: Int(153) - [6] Symbol [5-6]: + [8] Symbol [5-6]: name: x type: UInt(None, false) qsharp_type: Int @@ -201,12 +201,12 @@ fn implicit_bitness_int_binary_cap() { "uint x = 0B1010;", &expect![[r#" ClassicalDeclarationStmt [0-16]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-4] init_expr: Expr [9-15]: ty: UInt(None, true) kind: Lit: Int(10) - [6] Symbol [5-6]: + [8] Symbol [5-6]: name: x type: UInt(None, false) qsharp_type: Int @@ -220,12 +220,12 @@ fn const_implicit_bitness_int_binary_low() { "const uint x = 0b1001_1001;", &expect![[r#" ClassicalDeclarationStmt [0-27]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-10] init_expr: Expr [15-26]: ty: UInt(None, true) kind: Lit: Int(153) - [6] Symbol [11-12]: + [8] Symbol [11-12]: name: x type: UInt(None, true) qsharp_type: Int @@ -239,12 +239,12 @@ fn const_implicit_bitness_int_binary_cap() { "const uint x = 0B1010;", &expect![[r#" ClassicalDeclarationStmt [0-22]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-10] init_expr: Expr [15-21]: ty: UInt(None, true) kind: Lit: Int(10) - [6] Symbol [11-12]: + [8] Symbol [11-12]: name: x type: UInt(None, true) qsharp_type: Int @@ -258,12 +258,12 @@ fn implicit_bitness_int_formatted() { "uint x = 2_0_00;", &expect![[r#" ClassicalDeclarationStmt [0-16]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-4] init_expr: Expr [9-15]: ty: UInt(None, true) kind: Lit: Int(2000) - [6] Symbol [5-6]: + [8] Symbol [5-6]: name: x type: UInt(None, false) qsharp_type: Int @@ -277,12 +277,12 @@ fn const_implicit_bitness_int_formatted() { "const uint x = 2_0_00;", &expect![[r#" ClassicalDeclarationStmt [0-22]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-10] init_expr: Expr [15-21]: ty: UInt(None, true) kind: Lit: Int(2000) - [6] Symbol [11-12]: + [8] Symbol [11-12]: name: x type: UInt(None, true) qsharp_type: Int @@ -296,12 +296,12 @@ fn const_explicit_bitness_int() { "uint[10] x;", &expect![[r#" ClassicalDeclarationStmt [0-11]: - symbol_id: 6 + symbol_id: 8 ty_span: [0-8] init_expr: Expr [0-0]: ty: UInt(Some(10), true) kind: Lit: Int(0) - [6] Symbol [9-10]: + [8] Symbol [9-10]: name: x type: UInt(Some(10), false) qsharp_type: Int @@ -315,7 +315,7 @@ fn assigning_uint_to_negative_lit() { "const uint[10] x = -42;", &expect![[r#" ClassicalDeclarationStmt [0-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-14] init_expr: Expr [20-22]: ty: UInt(Some(10), true) @@ -328,7 +328,7 @@ fn assigning_uint_to_negative_lit() { expr: Expr [20-22]: ty: Int(None, true) kind: Lit: Int(42) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: UInt(Some(10), true) qsharp_type: Int @@ -342,7 +342,7 @@ fn implicit_bitness_uint_const_negative_decl() { "const uint x = -42;", &expect![[r#" ClassicalDeclarationStmt [0-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-10] init_expr: Expr [16-18]: ty: UInt(None, true) @@ -355,7 +355,7 @@ fn implicit_bitness_uint_const_negative_decl() { expr: Expr [16-18]: ty: Int(None, true) kind: Lit: Int(42) - [6] Symbol [11-12]: + [8] Symbol [11-12]: name: x type: UInt(None, true) qsharp_type: Int @@ -369,7 +369,7 @@ fn explicit_bitness_uint_const_negative_decl() { "const uint[32] x = -42;", &expect![[r#" ClassicalDeclarationStmt [0-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [6-14] init_expr: Expr [20-22]: ty: UInt(Some(32), true) @@ -382,7 +382,7 @@ fn explicit_bitness_uint_const_negative_decl() { expr: Expr [20-22]: ty: Int(None, true) kind: Lit: Int(42) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: UInt(Some(32), true) qsharp_type: Int 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 index 1df284f262..6b8223047b 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/expression/binary/arithmetic_conversions.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/arithmetic_conversions.rs @@ -16,30 +16,30 @@ fn int_idents_without_width_can_be_multiplied() { check_stmt_kinds( input, &expect![[r#" - ClassicalDeclarationStmt [9-19]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-18]: - ty: Int(None, true) - kind: Lit: Int(5) - ClassicalDeclarationStmt [28-38]: - symbol_id: 7 - ty_span: [28-31] - init_expr: Expr [36-37]: - ty: Int(None, true) - 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(6) - rhs: Expr [51-52]: - ty: Int(None, false) - kind: SymbolId(7) - "#]], + 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) + "#]], ); } @@ -54,30 +54,30 @@ fn int_idents_with_same_width_can_be_multiplied() { check_stmt_kinds( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-16] - init_expr: Expr [21-22]: - ty: Int(Some(32), true) - kind: Lit: Int(5) - ClassicalDeclarationStmt [32-46]: - symbol_id: 7 - 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(6) - rhs: Expr [59-60]: - ty: Int(Some(32), false) - kind: SymbolId(7) - "#]], + 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) + "#]], ); } @@ -92,34 +92,34 @@ fn int_idents_with_different_width_can_be_multiplied() { check_stmt_kinds( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-16] - init_expr: Expr [21-22]: - ty: Int(Some(32), true) - kind: Lit: Int(5) - ClassicalDeclarationStmt [32-46]: - symbol_id: 7 - 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]: + 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) - expr: Expr [55-56]: - ty: Int(Some(32), false) - kind: SymbolId(6) - rhs: Expr [59-60]: - ty: Int(Some(64), false) - kind: SymbolId(7) - "#]], + 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) + "#]], ); } @@ -134,36 +134,36 @@ fn multiplying_int_idents_with_different_width_result_in_higher_width_result() { check_stmt_kinds( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-16] - init_expr: Expr [21-22]: - ty: Int(Some(32), true) - kind: Lit: Int(5) - ClassicalDeclarationStmt [32-46]: - symbol_id: 7 - ty_span: [32-39] - init_expr: Expr [44-45]: - ty: Int(Some(64), true) - kind: Lit: Int(3) - ClassicalDeclarationStmt [55-73]: - symbol_id: 8 - 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]: + 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) - expr: Expr [67-68]: - ty: Int(Some(32), false) - kind: SymbolId(6) - rhs: Expr [71-72]: - ty: Int(Some(64), false) - kind: SymbolId(7) - "#]], + kind: SymbolId(9) + "#]], ); } @@ -178,40 +178,40 @@ fn multiplying_int_idents_with_different_width_result_in_no_width_result() { check_stmt_kinds( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-16] - init_expr: Expr [21-22]: - ty: Int(Some(32), true) - kind: Lit: Int(5) - ClassicalDeclarationStmt [32-46]: - symbol_id: 7 - ty_span: [32-39] - init_expr: Expr [44-45]: - ty: Int(Some(64), true) - kind: Lit: Int(3) - ClassicalDeclarationStmt [55-69]: - symbol_id: 8 - ty_span: [55-58] - init_expr: Expr [63-68]: - ty: Int(None, false) - kind: Cast [0-0]: + 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) - 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]: + 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) - expr: Expr [63-64]: - ty: Int(Some(32), false) - kind: SymbolId(6) - rhs: Expr [67-68]: - ty: Int(Some(64), false) - kind: SymbolId(7) - "#]], + 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) + "#]], ); } @@ -226,39 +226,39 @@ fn multiplying_int_idents_with_width_greater_than_64_result_in_bigint_result() { check_stmt_kinds( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-16] - init_expr: Expr [21-22]: - ty: Int(Some(32), true) - kind: Lit: Int(5) - ClassicalDeclarationStmt [32-46]: - symbol_id: 7 - ty_span: [32-39] - init_expr: Expr [44-45]: - ty: Int(Some(64), true) - kind: Lit: Int(3) - ClassicalDeclarationStmt [55-73]: - symbol_id: 8 - ty_span: [55-62] - init_expr: Expr [67-72]: - ty: Int(Some(67), false) - kind: Cast [0-0]: + 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) - 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]: + 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) - expr: Expr [67-68]: - ty: Int(Some(32), false) - kind: SymbolId(6) - rhs: Expr [71-72]: - ty: Int(Some(64), false) - kind: SymbolId(7) - "#]], + 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) + "#]], ); } 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 index 2f62c3202b..435eec3e15 100644 --- 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 @@ -17,29 +17,29 @@ fn logical_and() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [13-14]: + kind: Lit: Bit(1) + [8] Symbol [13-14]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: ty: Bit(true) - kind: Lit: Int(0) - [7] Symbol [32-33]: + kind: Lit: Bit(0) + [9] Symbol [32-33]: name: y type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [47-63]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-62]: ty: Bool(false) @@ -51,15 +51,15 @@ fn logical_and() { ty: Bool(false) expr: Expr [56-57]: ty: Bit(false) - kind: SymbolId(6) + 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(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: a type: Bool(false) qsharp_type: bool @@ -80,29 +80,29 @@ fn logical_or() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [13-14]: + kind: Lit: Bit(1) + [8] Symbol [13-14]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: ty: Bit(true) - kind: Lit: Int(0) - [7] Symbol [32-33]: + kind: Lit: Bit(0) + [9] Symbol [32-33]: name: y type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [47-63]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-62]: ty: Bool(false) @@ -114,15 +114,15 @@ fn logical_or() { ty: Bool(false) expr: Expr [56-57]: ty: Bit(false) - kind: SymbolId(6) + 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(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: a type: Bool(false) qsharp_type: bool @@ -143,29 +143,29 @@ fn unop_not_logical_and_unop_not() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [13-14]: + kind: Lit: Bit(1) + [8] Symbol [13-14]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: ty: Bit(true) - kind: Lit: Int(0) - [7] Symbol [32-33]: + kind: Lit: Bit(0) + [9] Symbol [32-33]: name: y type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [47-65]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-64]: ty: Bool(false) @@ -181,7 +181,7 @@ fn unop_not_logical_and_unop_not() { ty: Bool(false) expr: Expr [57-58]: ty: Bit(false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [63-64]: ty: Bool(false) kind: UnaryOpExpr [63-64]: @@ -192,8 +192,8 @@ fn unop_not_logical_and_unop_not() { ty: Bool(false) expr: Expr [63-64]: ty: Bit(false) - kind: SymbolId(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: a type: Bool(false) qsharp_type: bool @@ -214,29 +214,29 @@ fn unop_not_logical_or_unop_not() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [13-14]: + kind: Lit: Bit(1) + [8] Symbol [13-14]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: ty: Bit(true) - kind: Lit: Int(0) - [7] Symbol [32-33]: + kind: Lit: Bit(0) + [9] Symbol [32-33]: name: y type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [47-65]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-64]: ty: Bool(false) @@ -252,7 +252,7 @@ fn unop_not_logical_or_unop_not() { ty: Bool(false) expr: Expr [57-58]: ty: Bit(false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [63-64]: ty: Bool(false) kind: UnaryOpExpr [63-64]: @@ -263,8 +263,8 @@ fn unop_not_logical_or_unop_not() { ty: Bool(false) expr: Expr [63-64]: ty: Bit(false) - kind: SymbolId(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: a type: Bool(false) qsharp_type: bool @@ -285,29 +285,29 @@ fn unop_not_logical_and() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [13-14]: + kind: Lit: Bit(1) + [8] Symbol [13-14]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: ty: Bit(true) - kind: Lit: Int(0) - [7] Symbol [32-33]: + kind: Lit: Bit(0) + [9] Symbol [32-33]: name: y type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [47-64]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-63]: ty: Bool(false) @@ -323,15 +323,15 @@ fn unop_not_logical_and() { ty: Bool(false) expr: Expr [57-58]: ty: Bit(false) - kind: SymbolId(6) + 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(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: a type: Bool(false) qsharp_type: bool @@ -352,29 +352,29 @@ fn unop_not_logical_or() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [13-14]: + kind: Lit: Bit(1) + [8] Symbol [13-14]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: ty: Bit(true) - kind: Lit: Int(0) - [7] Symbol [32-33]: + kind: Lit: Bit(0) + [9] Symbol [32-33]: name: y type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [47-64]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-63]: ty: Bool(false) @@ -390,15 +390,15 @@ fn unop_not_logical_or() { ty: Bool(false) expr: Expr [57-58]: ty: Bit(false) - kind: SymbolId(6) + 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(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: a type: Bool(false) qsharp_type: bool @@ -419,29 +419,29 @@ fn logical_and_unop_not() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [13-14]: + kind: Lit: Bit(1) + [8] Symbol [13-14]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: ty: Bit(true) - kind: Lit: Int(0) - [7] Symbol [32-33]: + kind: Lit: Bit(0) + [9] Symbol [32-33]: name: y type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [47-64]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-63]: ty: Bool(false) @@ -453,7 +453,7 @@ fn logical_and_unop_not() { ty: Bool(false) expr: Expr [56-57]: ty: Bit(false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [62-63]: ty: Bool(false) kind: UnaryOpExpr [62-63]: @@ -464,8 +464,8 @@ fn logical_and_unop_not() { ty: Bool(false) expr: Expr [62-63]: ty: Bit(false) - kind: SymbolId(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: a type: Bool(false) qsharp_type: bool @@ -486,29 +486,29 @@ fn logical_or_unop_not() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [13-14]: + kind: Lit: Bit(1) + [8] Symbol [13-14]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: ty: Bit(true) - kind: Lit: Int(0) - [7] Symbol [32-33]: + kind: Lit: Bit(0) + [9] Symbol [32-33]: name: y type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [47-64]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-63]: ty: Bool(false) @@ -520,7 +520,7 @@ fn logical_or_unop_not() { ty: Bool(false) expr: Expr [56-57]: ty: Bit(false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [62-63]: ty: Bool(false) kind: UnaryOpExpr [62-63]: @@ -531,8 +531,8 @@ fn logical_or_unop_not() { ty: Bool(false) expr: Expr [62-63]: ty: Bit(false) - kind: SymbolId(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: a type: Bool(false) qsharp_type: bool 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 index 56579fd91c..d1e2ee4edb 100644 --- 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 @@ -17,29 +17,29 @@ fn logical_and() { input, &expect![[r#" ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-22]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(true) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [32-47]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-36] init_expr: Expr [41-46]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(false) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [56-72]: - symbol_id: 8 + symbol_id: 10 ty_span: [56-60] init_expr: Expr [65-71]: ty: Bool(false) @@ -47,11 +47,11 @@ fn logical_and() { op: AndL lhs: Expr [65-66]: ty: Bool(false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [70-71]: ty: Bool(false) - kind: SymbolId(7) - [8] Symbol [61-62]: + kind: SymbolId(9) + [10] Symbol [61-62]: name: a type: Bool(false) qsharp_type: bool @@ -72,29 +72,29 @@ fn logical_or() { input, &expect![[r#" ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-22]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(true) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [32-47]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-36] init_expr: Expr [41-46]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(false) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [56-72]: - symbol_id: 8 + symbol_id: 10 ty_span: [56-60] init_expr: Expr [65-71]: ty: Bool(false) @@ -102,11 +102,11 @@ fn logical_or() { op: OrL lhs: Expr [65-66]: ty: Bool(false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [70-71]: ty: Bool(false) - kind: SymbolId(7) - [8] Symbol [61-62]: + kind: SymbolId(9) + [10] Symbol [61-62]: name: a type: Bool(false) qsharp_type: bool @@ -127,29 +127,29 @@ fn unop_not_logical_and_unop_not() { input, &expect![[r#" ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-22]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(true) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [32-47]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-36] init_expr: Expr [41-46]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(false) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [56-74]: - symbol_id: 8 + symbol_id: 10 ty_span: [56-60] init_expr: Expr [65-73]: ty: Bool(false) @@ -161,15 +161,15 @@ fn unop_not_logical_and_unop_not() { op: NotL expr: Expr [66-67]: ty: Bool(false) - kind: SymbolId(6) + 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(7) - [8] Symbol [61-62]: + kind: SymbolId(9) + [10] Symbol [61-62]: name: a type: Bool(false) qsharp_type: bool @@ -190,29 +190,29 @@ fn unop_not_logical_or_unop_not() { input, &expect![[r#" ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-22]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(true) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [32-47]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-36] init_expr: Expr [41-46]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(false) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [56-74]: - symbol_id: 8 + symbol_id: 10 ty_span: [56-60] init_expr: Expr [65-73]: ty: Bool(false) @@ -224,15 +224,15 @@ fn unop_not_logical_or_unop_not() { op: NotL expr: Expr [66-67]: ty: Bool(false) - kind: SymbolId(6) + 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(7) - [8] Symbol [61-62]: + kind: SymbolId(9) + [10] Symbol [61-62]: name: a type: Bool(false) qsharp_type: bool @@ -253,29 +253,29 @@ fn unop_not_logical_and() { input, &expect![[r#" ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-22]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(true) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [32-47]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-36] init_expr: Expr [41-46]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(false) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [56-73]: - symbol_id: 8 + symbol_id: 10 ty_span: [56-60] init_expr: Expr [65-72]: ty: Bool(false) @@ -287,11 +287,11 @@ fn unop_not_logical_and() { op: NotL expr: Expr [66-67]: ty: Bool(false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [71-72]: ty: Bool(false) - kind: SymbolId(7) - [8] Symbol [61-62]: + kind: SymbolId(9) + [10] Symbol [61-62]: name: a type: Bool(false) qsharp_type: bool @@ -312,29 +312,29 @@ fn unop_not_logical_or() { input, &expect![[r#" ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-22]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(true) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [32-47]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-36] init_expr: Expr [41-46]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(false) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [56-73]: - symbol_id: 8 + symbol_id: 10 ty_span: [56-60] init_expr: Expr [65-72]: ty: Bool(false) @@ -346,11 +346,11 @@ fn unop_not_logical_or() { op: NotL expr: Expr [66-67]: ty: Bool(false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [71-72]: ty: Bool(false) - kind: SymbolId(7) - [8] Symbol [61-62]: + kind: SymbolId(9) + [10] Symbol [61-62]: name: a type: Bool(false) qsharp_type: bool @@ -371,29 +371,29 @@ fn logical_and_unop_not() { input, &expect![[r#" ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-22]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(true) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [32-47]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-36] init_expr: Expr [41-46]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(false) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [56-73]: - symbol_id: 8 + symbol_id: 10 ty_span: [56-60] init_expr: Expr [65-72]: ty: Bool(false) @@ -401,15 +401,15 @@ fn logical_and_unop_not() { op: AndL lhs: Expr [65-66]: ty: Bool(false) - kind: SymbolId(6) + 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(7) - [8] Symbol [61-62]: + kind: SymbolId(9) + [10] Symbol [61-62]: name: a type: Bool(false) qsharp_type: bool @@ -430,29 +430,29 @@ fn logical_or_unop_not() { input, &expect![[r#" ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-22]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(true) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [32-47]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-36] init_expr: Expr [41-46]: - ty: Bool(true) + ty: Bool(false) kind: Lit: Bool(false) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Bool(false) qsharp_type: bool io_kind: Default ClassicalDeclarationStmt [56-73]: - symbol_id: 8 + symbol_id: 10 ty_span: [56-60] init_expr: Expr [65-72]: ty: Bool(false) @@ -460,15 +460,15 @@ fn logical_or_unop_not() { op: OrL lhs: Expr [65-66]: ty: Bool(false) - kind: SymbolId(6) + 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(7) - [8] Symbol [61-62]: + kind: SymbolId(9) + [10] Symbol [61-62]: name: a type: Bool(false) qsharp_type: bool 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 index 76cf8683f6..9439a6c6d2 100644 --- 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 @@ -17,29 +17,29 @@ fn greater_than() { input, &expect![[r#" ClassicalDeclarationStmt [9-22]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-21]: - ty: Float(None, true) + ty: Float(None, false) kind: Lit: Float(5.0) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: Float(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [31-44]: - symbol_id: 7 + symbol_id: 9 ty_span: [31-36] init_expr: Expr [41-43]: - ty: Float(None, true) + ty: Float(None, false) kind: Lit: Float(3.0) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Float(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [53-68]: - symbol_id: 8 + symbol_id: 10 ty_span: [53-57] init_expr: Expr [62-67]: ty: Bool(false) @@ -47,11 +47,11 @@ fn greater_than() { op: Gt lhs: Expr [62-63]: ty: Float(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [66-67]: ty: Float(None, false) - kind: SymbolId(7) - [8] Symbol [58-59]: + kind: SymbolId(9) + [10] Symbol [58-59]: name: f type: Bool(false) qsharp_type: bool @@ -72,29 +72,29 @@ fn greater_than_equals() { input, &expect![[r#" ClassicalDeclarationStmt [9-22]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-21]: - ty: Float(None, true) + ty: Float(None, false) kind: Lit: Float(5.0) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: Float(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [31-44]: - symbol_id: 7 + symbol_id: 9 ty_span: [31-36] init_expr: Expr [41-43]: - ty: Float(None, true) + ty: Float(None, false) kind: Lit: Float(3.0) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Float(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [53-69]: - symbol_id: 8 + symbol_id: 10 ty_span: [53-57] init_expr: Expr [62-68]: ty: Bool(false) @@ -102,11 +102,11 @@ fn greater_than_equals() { op: Gte lhs: Expr [62-63]: ty: Float(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [67-68]: ty: Float(None, false) - kind: SymbolId(7) - [8] Symbol [58-59]: + kind: SymbolId(9) + [10] Symbol [58-59]: name: e type: Bool(false) qsharp_type: bool @@ -127,29 +127,29 @@ fn less_than() { input, &expect![[r#" ClassicalDeclarationStmt [9-22]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-21]: - ty: Float(None, true) + ty: Float(None, false) kind: Lit: Float(5.0) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: Float(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [31-44]: - symbol_id: 7 + symbol_id: 9 ty_span: [31-36] init_expr: Expr [41-43]: - ty: Float(None, true) + ty: Float(None, false) kind: Lit: Float(3.0) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Float(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [53-68]: - symbol_id: 8 + symbol_id: 10 ty_span: [53-57] init_expr: Expr [62-67]: ty: Bool(false) @@ -157,11 +157,11 @@ fn less_than() { op: Lt lhs: Expr [62-63]: ty: Float(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [66-67]: ty: Float(None, false) - kind: SymbolId(7) - [8] Symbol [58-59]: + kind: SymbolId(9) + [10] Symbol [58-59]: name: a type: Bool(false) qsharp_type: bool @@ -182,29 +182,29 @@ fn less_than_equals() { input, &expect![[r#" ClassicalDeclarationStmt [9-22]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-21]: - ty: Float(None, true) + ty: Float(None, false) kind: Lit: Float(5.0) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: Float(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [31-44]: - symbol_id: 7 + symbol_id: 9 ty_span: [31-36] init_expr: Expr [41-43]: - ty: Float(None, true) + ty: Float(None, false) kind: Lit: Float(3.0) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Float(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [53-69]: - symbol_id: 8 + symbol_id: 10 ty_span: [53-57] init_expr: Expr [62-68]: ty: Bool(false) @@ -212,11 +212,11 @@ fn less_than_equals() { op: Lte lhs: Expr [62-63]: ty: Float(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [67-68]: ty: Float(None, false) - kind: SymbolId(7) - [8] Symbol [58-59]: + kind: SymbolId(9) + [10] Symbol [58-59]: name: c type: Bool(false) qsharp_type: bool @@ -237,29 +237,29 @@ fn equals() { input, &expect![[r#" ClassicalDeclarationStmt [9-22]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-21]: - ty: Float(None, true) + ty: Float(None, false) kind: Lit: Float(5.0) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: Float(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [31-44]: - symbol_id: 7 + symbol_id: 9 ty_span: [31-36] init_expr: Expr [41-43]: - ty: Float(None, true) + ty: Float(None, false) kind: Lit: Float(3.0) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Float(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [53-69]: - symbol_id: 8 + symbol_id: 10 ty_span: [53-57] init_expr: Expr [62-68]: ty: Bool(false) @@ -267,11 +267,11 @@ fn equals() { op: Eq lhs: Expr [62-63]: ty: Float(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [67-68]: ty: Float(None, false) - kind: SymbolId(7) - [8] Symbol [58-59]: + kind: SymbolId(9) + [10] Symbol [58-59]: name: b type: Bool(false) qsharp_type: bool @@ -292,29 +292,29 @@ fn not_equals() { input, &expect![[r#" ClassicalDeclarationStmt [9-22]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-21]: - ty: Float(None, true) + ty: Float(None, false) kind: Lit: Float(5.0) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: Float(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [31-44]: - symbol_id: 7 + symbol_id: 9 ty_span: [31-36] init_expr: Expr [41-43]: - ty: Float(None, true) + ty: Float(None, false) kind: Lit: Float(3.0) - [7] Symbol [37-38]: + [9] Symbol [37-38]: name: y type: Float(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [53-69]: - symbol_id: 8 + symbol_id: 10 ty_span: [53-57] init_expr: Expr [62-68]: ty: Bool(false) @@ -322,11 +322,11 @@ fn not_equals() { op: Neq lhs: Expr [62-63]: ty: Float(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [67-68]: ty: Float(None, false) - kind: SymbolId(7) - [8] Symbol [58-59]: + kind: SymbolId(9) + [10] Symbol [58-59]: name: d type: Bool(false) qsharp_type: bool 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 index b9a35b85f0..a3ff2d14e1 100644 --- 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 @@ -17,29 +17,29 @@ fn greater_than() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(5) - [6] Symbol [13-14]: + [8] Symbol [13-14]: name: x type: Int(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(3) - [7] Symbol [32-33]: + [9] Symbol [32-33]: name: y type: Int(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [47-62]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-61]: ty: Bool(false) @@ -47,11 +47,11 @@ fn greater_than() { op: Gt lhs: Expr [56-57]: ty: Int(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [60-61]: ty: Int(None, false) - kind: SymbolId(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: f type: Bool(false) qsharp_type: bool @@ -72,29 +72,29 @@ fn greater_than_equals() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(5) - [6] Symbol [13-14]: + [8] Symbol [13-14]: name: x type: Int(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(3) - [7] Symbol [32-33]: + [9] Symbol [32-33]: name: y type: Int(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [47-63]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-62]: ty: Bool(false) @@ -102,11 +102,11 @@ fn greater_than_equals() { op: Gte lhs: Expr [56-57]: ty: Int(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [61-62]: ty: Int(None, false) - kind: SymbolId(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: e type: Bool(false) qsharp_type: bool @@ -127,29 +127,29 @@ fn less_than() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(5) - [6] Symbol [13-14]: + [8] Symbol [13-14]: name: x type: Int(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(3) - [7] Symbol [32-33]: + [9] Symbol [32-33]: name: y type: Int(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [47-62]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-61]: ty: Bool(false) @@ -157,11 +157,11 @@ fn less_than() { op: Lt lhs: Expr [56-57]: ty: Int(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [60-61]: ty: Int(None, false) - kind: SymbolId(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: a type: Bool(false) qsharp_type: bool @@ -182,29 +182,29 @@ fn less_than_equals() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(5) - [6] Symbol [13-14]: + [8] Symbol [13-14]: name: x type: Int(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(3) - [7] Symbol [32-33]: + [9] Symbol [32-33]: name: y type: Int(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [47-63]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-62]: ty: Bool(false) @@ -212,11 +212,11 @@ fn less_than_equals() { op: Lte lhs: Expr [56-57]: ty: Int(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [61-62]: ty: Int(None, false) - kind: SymbolId(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: c type: Bool(false) qsharp_type: bool @@ -237,29 +237,29 @@ fn equals() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(5) - [6] Symbol [13-14]: + [8] Symbol [13-14]: name: x type: Int(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(3) - [7] Symbol [32-33]: + [9] Symbol [32-33]: name: y type: Int(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [47-63]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-62]: ty: Bool(false) @@ -267,11 +267,11 @@ fn equals() { op: Eq lhs: Expr [56-57]: ty: Int(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [61-62]: ty: Int(None, false) - kind: SymbolId(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: b type: Bool(false) qsharp_type: bool @@ -292,29 +292,29 @@ fn not_equals() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(5) - [6] Symbol [13-14]: + [8] Symbol [13-14]: name: x type: Int(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(3) - [7] Symbol [32-33]: + [9] Symbol [32-33]: name: y type: Int(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [47-63]: - symbol_id: 8 + symbol_id: 10 ty_span: [47-51] init_expr: Expr [56-62]: ty: Bool(false) @@ -322,11 +322,11 @@ fn not_equals() { op: Neq lhs: Expr [56-57]: ty: Int(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [61-62]: ty: Int(None, false) - kind: SymbolId(7) - [8] Symbol [52-53]: + kind: SymbolId(9) + [10] Symbol [52-53]: name: d type: Bool(false) qsharp_type: bool 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 index ba39fdf302..37847ee8e2 100644 --- 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 @@ -17,29 +17,29 @@ fn greater_than() { input, &expect![[r#" ClassicalDeclarationStmt [9-20]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-19]: ty: UInt(None, true) kind: Lit: Int(5) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: UInt(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [29-40]: - symbol_id: 7 + symbol_id: 9 ty_span: [29-33] init_expr: Expr [38-39]: ty: UInt(None, true) kind: Lit: Int(3) - [7] Symbol [34-35]: + [9] Symbol [34-35]: name: y type: UInt(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [49-64]: - symbol_id: 8 + symbol_id: 10 ty_span: [49-53] init_expr: Expr [58-63]: ty: Bool(false) @@ -47,11 +47,11 @@ fn greater_than() { op: Gt lhs: Expr [58-59]: ty: UInt(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [62-63]: ty: UInt(None, false) - kind: SymbolId(7) - [8] Symbol [54-55]: + kind: SymbolId(9) + [10] Symbol [54-55]: name: f type: Bool(false) qsharp_type: bool @@ -72,29 +72,29 @@ fn greater_than_equals() { input, &expect![[r#" ClassicalDeclarationStmt [9-20]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-19]: ty: UInt(None, true) kind: Lit: Int(5) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: UInt(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [29-40]: - symbol_id: 7 + symbol_id: 9 ty_span: [29-33] init_expr: Expr [38-39]: ty: UInt(None, true) kind: Lit: Int(3) - [7] Symbol [34-35]: + [9] Symbol [34-35]: name: y type: UInt(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [49-65]: - symbol_id: 8 + symbol_id: 10 ty_span: [49-53] init_expr: Expr [58-64]: ty: Bool(false) @@ -102,11 +102,11 @@ fn greater_than_equals() { op: Gte lhs: Expr [58-59]: ty: UInt(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [63-64]: ty: UInt(None, false) - kind: SymbolId(7) - [8] Symbol [54-55]: + kind: SymbolId(9) + [10] Symbol [54-55]: name: e type: Bool(false) qsharp_type: bool @@ -127,29 +127,29 @@ fn less_than() { input, &expect![[r#" ClassicalDeclarationStmt [9-20]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-19]: ty: UInt(None, true) kind: Lit: Int(5) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: UInt(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [29-40]: - symbol_id: 7 + symbol_id: 9 ty_span: [29-33] init_expr: Expr [38-39]: ty: UInt(None, true) kind: Lit: Int(3) - [7] Symbol [34-35]: + [9] Symbol [34-35]: name: y type: UInt(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [49-64]: - symbol_id: 8 + symbol_id: 10 ty_span: [49-53] init_expr: Expr [58-63]: ty: Bool(false) @@ -157,11 +157,11 @@ fn less_than() { op: Lt lhs: Expr [58-59]: ty: UInt(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [62-63]: ty: UInt(None, false) - kind: SymbolId(7) - [8] Symbol [54-55]: + kind: SymbolId(9) + [10] Symbol [54-55]: name: a type: Bool(false) qsharp_type: bool @@ -182,29 +182,29 @@ fn less_than_equals() { input, &expect![[r#" ClassicalDeclarationStmt [9-20]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-19]: ty: UInt(None, true) kind: Lit: Int(5) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: UInt(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [29-40]: - symbol_id: 7 + symbol_id: 9 ty_span: [29-33] init_expr: Expr [38-39]: ty: UInt(None, true) kind: Lit: Int(3) - [7] Symbol [34-35]: + [9] Symbol [34-35]: name: y type: UInt(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [49-65]: - symbol_id: 8 + symbol_id: 10 ty_span: [49-53] init_expr: Expr [58-64]: ty: Bool(false) @@ -212,11 +212,11 @@ fn less_than_equals() { op: Lte lhs: Expr [58-59]: ty: UInt(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [63-64]: ty: UInt(None, false) - kind: SymbolId(7) - [8] Symbol [54-55]: + kind: SymbolId(9) + [10] Symbol [54-55]: name: c type: Bool(false) qsharp_type: bool @@ -237,29 +237,29 @@ fn equals() { input, &expect![[r#" ClassicalDeclarationStmt [9-20]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-19]: ty: UInt(None, true) kind: Lit: Int(5) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: UInt(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [29-40]: - symbol_id: 7 + symbol_id: 9 ty_span: [29-33] init_expr: Expr [38-39]: ty: UInt(None, true) kind: Lit: Int(3) - [7] Symbol [34-35]: + [9] Symbol [34-35]: name: y type: UInt(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [49-65]: - symbol_id: 8 + symbol_id: 10 ty_span: [49-53] init_expr: Expr [58-64]: ty: Bool(false) @@ -267,11 +267,11 @@ fn equals() { op: Eq lhs: Expr [58-59]: ty: UInt(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [63-64]: ty: UInt(None, false) - kind: SymbolId(7) - [8] Symbol [54-55]: + kind: SymbolId(9) + [10] Symbol [54-55]: name: b type: Bool(false) qsharp_type: bool @@ -292,29 +292,29 @@ fn not_equals() { input, &expect![[r#" ClassicalDeclarationStmt [9-20]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-13] init_expr: Expr [18-19]: ty: UInt(None, true) kind: Lit: Int(5) - [6] Symbol [14-15]: + [8] Symbol [14-15]: name: x type: UInt(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [29-40]: - symbol_id: 7 + symbol_id: 9 ty_span: [29-33] init_expr: Expr [38-39]: ty: UInt(None, true) kind: Lit: Int(3) - [7] Symbol [34-35]: + [9] Symbol [34-35]: name: y type: UInt(None, false) qsharp_type: Int io_kind: Default ClassicalDeclarationStmt [49-65]: - symbol_id: 8 + symbol_id: 10 ty_span: [49-53] init_expr: Expr [58-64]: ty: Bool(false) @@ -322,11 +322,11 @@ fn not_equals() { op: Neq lhs: Expr [58-59]: ty: UInt(None, false) - kind: SymbolId(6) + kind: SymbolId(8) rhs: Expr [63-64]: ty: UInt(None, false) - kind: SymbolId(7) - [8] Symbol [54-55]: + kind: SymbolId(9) + [10] Symbol [54-55]: name: d type: Bool(false) qsharp_type: bool diff --git a/compiler/qsc_qasm3/src/semantic/tests/expression/binary/ident.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/ident.rs index 60b21ef238..a88ba0f04f 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/expression/binary/ident.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/ident.rs @@ -15,30 +15,30 @@ fn mutable_int_idents_without_width_can_be_multiplied() { check_stmt_kinds( input, &expect![[r#" - ClassicalDeclarationStmt [9-19]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-18]: - ty: Int(None, true) - kind: Lit: Int(5) - ClassicalDeclarationStmt [28-38]: - symbol_id: 7 - ty_span: [28-31] - init_expr: Expr [36-37]: - ty: Int(None, true) - 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(6) - rhs: Expr [51-52]: - ty: Int(None, false) - kind: SymbolId(7) - "#]], + 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) + "#]], ); } @@ -53,30 +53,30 @@ fn const_int_idents_without_width_can_be_multiplied() { check_stmt_kinds( input, &expect![[r#" - ClassicalDeclarationStmt [9-25]: - symbol_id: 6 - ty_span: [15-18] - init_expr: Expr [23-24]: - ty: Int(None, true) - kind: Lit: Int(5) - ClassicalDeclarationStmt [34-50]: - symbol_id: 7 - 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(6) - rhs: Expr [63-64]: - ty: Int(None, true) - kind: SymbolId(7) - "#]], + 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) + "#]], ); } @@ -91,30 +91,30 @@ fn const_and_mut_int_idents_without_width_can_be_multiplied() { check_stmt_kinds( input, &expect![[r#" - ClassicalDeclarationStmt [9-19]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-18]: - ty: Int(None, true) - kind: Lit: Int(5) - ClassicalDeclarationStmt [28-44]: - symbol_id: 7 - 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(6) - rhs: Expr [57-58]: - ty: Int(None, true) - kind: SymbolId(7) - "#]], + 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) + "#]], ); } @@ -129,33 +129,33 @@ fn const_int_idents_widthless_lhs_can_be_multiplied_by_explicit_width_int() { check_stmt_kinds( input, &expect![[r#" - ClassicalDeclarationStmt [9-29]: - symbol_id: 6 - ty_span: [15-22] - init_expr: Expr [27-28]: - ty: Int(Some(32), true) - kind: Lit: Int(5) - ClassicalDeclarationStmt [38-54]: - symbol_id: 7 - 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]: + 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) - expr: Expr [63-64]: - ty: Int(Some(32), true) - kind: SymbolId(6) - rhs: Expr [67-68]: - ty: Int(None, true) - kind: SymbolId(7) - "#]], + 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 index 02c6cb12cc..fb6601199e 100644 --- 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 @@ -15,33 +15,33 @@ fn to_bit_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Angle(None, true) - kind: Lit: Float(42.0) - [6] Symbol [15-16]: - name: x - type: Angle(None, false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [32-42]: - symbol_id: 7 - ty_span: [32-35] - init_expr: Expr [40-41]: - ty: Bit(false) - kind: Cast [0-0]: + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Angle(None, false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [32-42]: + symbol_id: 9 + ty_span: [32-35] + init_expr: Expr [40-41]: ty: Bit(false) - expr: Expr [40-41]: - ty: Angle(None, false) - kind: SymbolId(6) - [7] Symbol [36-37]: - name: y - type: Bit(false) - qsharp_type: Result - io_kind: Default - "#]], + 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 + "#]], ); } @@ -55,33 +55,33 @@ fn explicit_width_to_bit_implicitly_fails() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-27]: - symbol_id: 6 - ty_span: [9-18] - init_expr: Expr [23-26]: - ty: Angle(Some(64), true) - kind: Lit: Float(42.0) - [6] Symbol [19-20]: - name: x - type: Angle(Some(64), false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [36-46]: - symbol_id: 7 - ty_span: [36-39] - init_expr: Expr [44-45]: - ty: Bit(false) - kind: Cast [0-0]: + ClassicalDeclarationStmt [9-27]: + symbol_id: 8 + ty_span: [9-18] + init_expr: Expr [23-26]: + ty: Angle(Some(64), true) + kind: Lit: Float(42.0) + [8] Symbol [19-20]: + name: x + type: Angle(Some(64), false) + qsharp_type: Double + io_kind: Default + ClassicalDeclarationStmt [36-46]: + symbol_id: 9 + ty_span: [36-39] + init_expr: Expr [44-45]: ty: Bit(false) - expr: Expr [44-45]: - ty: Angle(Some(64), false) - kind: SymbolId(6) - [7] Symbol [40-41]: - name: y - type: Bit(false) - qsharp_type: Result - io_kind: Default - "#]], + 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 + "#]], ); } @@ -94,33 +94,33 @@ fn to_bool_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Angle(None, true) - kind: Lit: Float(42.0) - [6] Symbol [15-16]: - name: x - type: Angle(None, false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [32-43]: - symbol_id: 7 - ty_span: [32-36] - init_expr: Expr [41-42]: - ty: Bool(false) - kind: Cast [0-0]: + ClassicalDeclarationStmt [9-23]: + symbol_id: 8 + ty_span: [9-14] + init_expr: Expr [19-22]: + ty: Angle(None, true) + kind: Lit: Float(42.0) + [8] Symbol [15-16]: + name: x + type: Angle(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) - expr: Expr [41-42]: - ty: Angle(None, false) - kind: SymbolId(6) - [7] Symbol [37-38]: - name: y - type: Bool(false) - qsharp_type: bool - io_kind: Default - "#]], + 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 + "#]], ); } @@ -140,7 +140,7 @@ fn to_implicit_int_implicitly_fails() { Stmt [9-23]: annotations: kind: ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) @@ -148,19 +148,19 @@ fn to_implicit_int_implicitly_fails() { Stmt [32-42]: annotations: kind: ClassicalDeclarationStmt [32-42]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-35] init_expr: Expr [40-41]: ty: Angle(None, false) - kind: SymbolId(6) + kind: SymbolId(8) [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Angle(None, false) to type Int(None, false) - ,-[test:3:9] + ,-[test:3:17] 2 | angle x = 42.; 3 | int y = x; - : ^^^^^^^^^^ + : ^ 4 | `---- ]"#]], @@ -183,7 +183,7 @@ fn to_explicit_int_implicitly_fails() { Stmt [9-23]: annotations: kind: ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) @@ -191,20 +191,20 @@ fn to_explicit_int_implicitly_fails() { Stmt [32-46]: annotations: kind: ClassicalDeclarationStmt [32-46]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-39] init_expr: Expr [44-45]: ty: Angle(None, false) - kind: SymbolId(6) + kind: SymbolId(8) [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Angle(None, false) to type Int(Some(32), | false) - ,-[test:3:9] + ,-[test:3:21] 2 | angle x = 42.; 3 | int[32] y = x; - : ^^^^^^^^^^^^^^ + : ^ 4 | `---- ]"#]], @@ -227,7 +227,7 @@ fn to_implicit_uint_implicitly_fails() { Stmt [9-23]: annotations: kind: ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) @@ -235,20 +235,20 @@ fn to_implicit_uint_implicitly_fails() { Stmt [32-43]: annotations: kind: ClassicalDeclarationStmt [32-43]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-36] init_expr: Expr [41-42]: ty: Angle(None, false) - kind: SymbolId(6) + kind: SymbolId(8) [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Angle(None, false) to type UInt(None, | false) - ,-[test:3:9] + ,-[test:3:18] 2 | angle x = 42.; 3 | uint y = x; - : ^^^^^^^^^^^ + : ^ 4 | `---- ]"#]], @@ -271,7 +271,7 @@ fn negative_lit_to_implicit_uint_implicitly_fails() { Stmt [9-24]: annotations: kind: ClassicalDeclarationStmt [9-24]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [20-23]: ty: Angle(None, false) @@ -287,20 +287,20 @@ fn negative_lit_to_implicit_uint_implicitly_fails() { Stmt [33-44]: annotations: kind: ClassicalDeclarationStmt [33-44]: - symbol_id: 7 + symbol_id: 9 ty_span: [33-37] init_expr: Expr [42-43]: ty: Angle(None, false) - kind: SymbolId(6) + kind: SymbolId(8) [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Angle(None, false) to type UInt(None, | false) - ,-[test:3:9] + ,-[test:3:18] 2 | angle x = -42.; 3 | uint y = x; - : ^^^^^^^^^^^ + : ^ 4 | `---- ]"#]], @@ -323,7 +323,7 @@ fn to_explicit_uint_implicitly_fails() { Stmt [9-23]: annotations: kind: ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) @@ -331,20 +331,20 @@ fn to_explicit_uint_implicitly_fails() { Stmt [32-47]: annotations: kind: ClassicalDeclarationStmt [32-47]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-40] init_expr: Expr [45-46]: ty: Angle(None, false) - kind: SymbolId(6) + kind: SymbolId(8) [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Angle(None, false) to type UInt(Some(32), | false) - ,-[test:3:9] + ,-[test:3:22] 2 | angle x = 42.; 3 | uint[32] y = x; - : ^^^^^^^^^^^^^^^ + : ^ 4 | `---- ]"#]], @@ -367,7 +367,7 @@ fn to_explicit_bigint_implicitly_fails() { Stmt [9-23]: annotations: kind: ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) @@ -375,20 +375,20 @@ fn to_explicit_bigint_implicitly_fails() { Stmt [32-46]: annotations: kind: ClassicalDeclarationStmt [32-46]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-39] init_expr: Expr [44-45]: ty: Angle(None, false) - kind: SymbolId(6) + kind: SymbolId(8) [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Angle(None, false) to type Int(Some(65), | false) - ,-[test:3:9] + ,-[test:3:21] 2 | angle x = 42.; 3 | int[65] y = x; - : ^^^^^^^^^^^^^^ + : ^ 4 | `---- ]"#]], @@ -411,7 +411,7 @@ fn to_implicit_float_implicitly_fails() { Stmt [9-23]: annotations: kind: ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) @@ -419,20 +419,20 @@ fn to_implicit_float_implicitly_fails() { Stmt [32-44]: annotations: kind: ClassicalDeclarationStmt [32-44]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-37] init_expr: Expr [42-43]: ty: Angle(None, false) - kind: SymbolId(6) + kind: SymbolId(8) [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Angle(None, false) to type Float(None, | false) - ,-[test:3:9] + ,-[test:3:19] 2 | angle x = 42.; 3 | float y = x; - : ^^^^^^^^^^^^ + : ^ 4 | `---- ]"#]], @@ -455,7 +455,7 @@ fn to_explicit_float_implicitly_fails() { Stmt [9-23]: annotations: kind: ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) @@ -463,20 +463,20 @@ fn to_explicit_float_implicitly_fails() { Stmt [32-48]: annotations: kind: ClassicalDeclarationStmt [32-48]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-41] init_expr: Expr [46-47]: ty: Angle(None, false) - kind: SymbolId(6) + kind: SymbolId(8) [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Angle(None, false) to type Float(Some(32), | false) - ,-[test:3:9] + ,-[test:3:23] 2 | angle x = 42.; 3 | float[32] y = x; - : ^^^^^^^^^^^^^^^^ + : ^ 4 | `---- ]"#]], @@ -499,7 +499,7 @@ fn to_implicit_complex_implicitly_fails() { Stmt [9-23]: annotations: kind: ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) @@ -507,20 +507,20 @@ fn to_implicit_complex_implicitly_fails() { Stmt [32-53]: annotations: kind: ClassicalDeclarationStmt [32-53]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-46] init_expr: Expr [51-52]: ty: Angle(None, false) - kind: SymbolId(6) + kind: SymbolId(8) [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Angle(None, false) to type Complex(None, | false) - ,-[test:3:9] + ,-[test:3:28] 2 | angle x = 42.; 3 | complex[float] y = x; - : ^^^^^^^^^^^^^^^^^^^^^ + : ^ 4 | `---- ]"#]], @@ -543,7 +543,7 @@ fn to_explicit_complex_implicitly_fails() { Stmt [9-23]: annotations: kind: ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) @@ -551,20 +551,20 @@ fn to_explicit_complex_implicitly_fails() { Stmt [32-57]: annotations: kind: ClassicalDeclarationStmt [32-57]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-50] init_expr: Expr [55-56]: ty: Angle(None, false) - kind: SymbolId(6) + kind: SymbolId(8) [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Angle(None, false) to type | Complex(Some(32), false) - ,-[test:3:9] + ,-[test:3:32] 2 | angle x = 42.; 3 | complex[float[32]] y = x; - : ^^^^^^^^^^^^^^^^^^^^^^^^^ + : ^ 4 | `---- ]"#]], @@ -582,23 +582,23 @@ fn to_angle_implicitly() { input, &expect![[r#" ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) kind: Lit: Float(42.0) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: Angle(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [32-44]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-37] init_expr: Expr [42-43]: ty: Angle(None, false) - kind: SymbolId(6) - [7] Symbol [38-39]: + kind: SymbolId(8) + [9] Symbol [38-39]: name: y type: Angle(None, false) qsharp_type: Double @@ -618,23 +618,23 @@ fn to_explicit_angle_implicitly() { input, &expect![[r#" ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) kind: Lit: Float(42.0) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: Angle(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [32-47]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-40] init_expr: Expr [45-46]: ty: Angle(Some(4), false) - kind: SymbolId(6) - [7] Symbol [41-42]: + kind: SymbolId(8) + [9] Symbol [41-42]: name: y type: Angle(Some(4), false) qsharp_type: Double 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 index 7798b240e6..9e207073e3 100644 --- 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 @@ -38,18 +38,18 @@ fn to_bool_implicitly() { input, &expect![[r#" ClassicalDeclarationStmt [10-20]: - symbol_id: 6 + symbol_id: 8 ty_span: [10-13] init_expr: Expr [18-19]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [14-15]: + kind: Lit: Bit(1) + [8] Symbol [14-15]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [30-41]: - symbol_id: 7 + symbol_id: 9 ty_span: [30-34] init_expr: Expr [39-40]: ty: Bool(false) @@ -57,8 +57,8 @@ fn to_bool_implicitly() { ty: Bool(false) expr: Expr [39-40]: ty: Bit(false) - kind: SymbolId(6) - [7] Symbol [35-36]: + kind: SymbolId(8) + [9] Symbol [35-36]: name: y type: Bool(false) qsharp_type: bool @@ -78,18 +78,18 @@ fn to_implicit_int_implicitly() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [13-14]: + kind: Lit: Bit(1) + [8] Symbol [13-14]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [28-38]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-31] init_expr: Expr [36-37]: ty: Int(None, false) @@ -97,8 +97,8 @@ fn to_implicit_int_implicitly() { ty: Int(None, false) expr: Expr [36-37]: ty: Bit(false) - kind: SymbolId(6) - [7] Symbol [32-33]: + kind: SymbolId(8) + [9] Symbol [32-33]: name: y type: Int(None, false) qsharp_type: Int @@ -118,18 +118,18 @@ fn to_explicit_int_implicitly() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [13-14]: + kind: Lit: Bit(1) + [8] Symbol [13-14]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [28-42]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-35] init_expr: Expr [40-41]: ty: Int(Some(32), false) @@ -137,8 +137,8 @@ fn to_explicit_int_implicitly() { ty: Int(Some(32), false) expr: Expr [40-41]: ty: Bit(false) - kind: SymbolId(6) - [7] Symbol [36-37]: + kind: SymbolId(8) + [9] Symbol [36-37]: name: y type: Int(Some(32), false) qsharp_type: Int @@ -158,18 +158,18 @@ fn to_implicit_uint_implicitly() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [13-14]: + kind: Lit: Bit(1) + [8] Symbol [13-14]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [28-39]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-32] init_expr: Expr [37-38]: ty: UInt(None, false) @@ -177,8 +177,8 @@ fn to_implicit_uint_implicitly() { ty: UInt(None, false) expr: Expr [37-38]: ty: Bit(false) - kind: SymbolId(6) - [7] Symbol [33-34]: + kind: SymbolId(8) + [9] Symbol [33-34]: name: y type: UInt(None, false) qsharp_type: Int @@ -198,18 +198,18 @@ fn to_explicit_uint_implicitly() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [13-14]: + kind: Lit: Bit(1) + [8] Symbol [13-14]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [28-43]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-36] init_expr: Expr [41-42]: ty: UInt(Some(32), false) @@ -217,8 +217,8 @@ fn to_explicit_uint_implicitly() { ty: UInt(Some(32), false) expr: Expr [41-42]: ty: Bit(false) - kind: SymbolId(6) - [7] Symbol [37-38]: + kind: SymbolId(8) + [9] Symbol [37-38]: name: y type: UInt(Some(32), false) qsharp_type: Int @@ -238,18 +238,18 @@ fn to_explicit_bigint_implicitly() { input, &expect![[r#" ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) - [6] Symbol [13-14]: + kind: Lit: Bit(1) + [8] Symbol [13-14]: name: x type: Bit(false) qsharp_type: Result io_kind: Default ClassicalDeclarationStmt [28-42]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-35] init_expr: Expr [40-41]: ty: Int(Some(65), false) @@ -257,8 +257,8 @@ fn to_explicit_bigint_implicitly() { ty: Int(Some(65), false) expr: Expr [40-41]: ty: Bit(false) - kind: SymbolId(6) - [7] Symbol [36-37]: + kind: SymbolId(8) + [9] Symbol [36-37]: name: y type: Int(Some(65), false) qsharp_type: BigInt @@ -283,27 +283,27 @@ fn to_implicit_float_implicitly_fails() { Stmt [9-19]: annotations: kind: ClassicalDeclarationStmt [9-19]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-12] init_expr: Expr [17-18]: ty: Bit(true) - kind: Lit: Int(1) + kind: Lit: Bit(1) Stmt [28-40]: annotations: kind: ClassicalDeclarationStmt [28-40]: - symbol_id: 7 + symbol_id: 9 ty_span: [28-33] init_expr: Expr [38-39]: ty: Bit(false) - kind: SymbolId(6) + kind: SymbolId(8) [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Bit(false) to type Float(None, false) - ,-[test:3:9] + ,-[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_bool.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_bool.rs index cd4178ebe8..5f444fb295 100644 --- 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 @@ -15,33 +15,33 @@ fn to_bit_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-13] - init_expr: Expr [18-22]: - ty: Bool(true) - kind: Lit: Bool(true) - [6] Symbol [14-15]: - name: x - type: Bool(false) - qsharp_type: bool - io_kind: Default - ClassicalDeclarationStmt [32-42]: - symbol_id: 7 - ty_span: [32-35] - init_expr: Expr [40-41]: - ty: Bit(false) - kind: Cast [0-0]: + 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) - expr: Expr [40-41]: - ty: Bool(false) - kind: SymbolId(6) - [7] Symbol [36-37]: - name: y - type: Bit(false) - qsharp_type: Result - io_kind: Default - "#]], + 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 + "#]], ); } @@ -55,33 +55,33 @@ fn to_implicit_int_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-13] - init_expr: Expr [18-22]: - ty: Bool(true) - kind: Lit: Bool(true) - [6] Symbol [14-15]: - name: x - type: Bool(false) - qsharp_type: bool - io_kind: Default - ClassicalDeclarationStmt [32-42]: - symbol_id: 7 - ty_span: [32-35] - init_expr: Expr [40-41]: - ty: Int(None, false) - kind: Cast [0-0]: + 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) - expr: Expr [40-41]: - ty: Bool(false) - kind: SymbolId(6) - [7] Symbol [36-37]: - name: y - type: Int(None, false) - qsharp_type: Int - io_kind: Default - "#]], + 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 + "#]], ); } @@ -95,33 +95,33 @@ fn to_explicit_int_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-13] - init_expr: Expr [18-22]: - ty: Bool(true) - kind: Lit: Bool(true) - [6] Symbol [14-15]: - name: x - type: Bool(false) - qsharp_type: bool - io_kind: Default - ClassicalDeclarationStmt [32-46]: - symbol_id: 7 - ty_span: [32-39] - init_expr: Expr [44-45]: - ty: Int(Some(32), false) - kind: Cast [0-0]: + 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) - expr: Expr [44-45]: - ty: Bool(false) - kind: SymbolId(6) - [7] Symbol [40-41]: - name: y - type: Int(Some(32), false) - qsharp_type: Int - io_kind: Default - "#]], + 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 + "#]], ); } @@ -135,33 +135,33 @@ fn to_implicit_uint_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-13] - init_expr: Expr [18-22]: - ty: Bool(true) - kind: Lit: Bool(true) - [6] Symbol [14-15]: - name: x - type: Bool(false) - qsharp_type: bool - io_kind: Default - ClassicalDeclarationStmt [32-43]: - symbol_id: 7 - ty_span: [32-36] - init_expr: Expr [41-42]: - ty: UInt(None, false) - kind: Cast [0-0]: + 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) - expr: Expr [41-42]: - ty: Bool(false) - kind: SymbolId(6) - [7] Symbol [37-38]: - name: y - type: UInt(None, false) - qsharp_type: Int - io_kind: Default - "#]], + 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 + "#]], ); } @@ -175,33 +175,33 @@ fn to_explicit_uint_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-13] - init_expr: Expr [18-22]: - ty: Bool(true) - kind: Lit: Bool(true) - [6] Symbol [14-15]: - name: x - type: Bool(false) - qsharp_type: bool - io_kind: Default - ClassicalDeclarationStmt [32-47]: - symbol_id: 7 - ty_span: [32-40] - init_expr: Expr [45-46]: - ty: UInt(Some(32), false) - kind: Cast [0-0]: + 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) - expr: Expr [45-46]: - ty: Bool(false) - kind: SymbolId(6) - [7] Symbol [41-42]: - name: y - type: UInt(Some(32), false) - qsharp_type: Int - io_kind: Default - "#]], + 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 + "#]], ); } @@ -215,33 +215,33 @@ fn to_explicit_bigint_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-13] - init_expr: Expr [18-22]: - ty: Bool(true) - kind: Lit: Bool(true) - [6] Symbol [14-15]: - name: x - type: Bool(false) - qsharp_type: bool - io_kind: Default - ClassicalDeclarationStmt [32-46]: - symbol_id: 7 - ty_span: [32-39] - init_expr: Expr [44-45]: - ty: Int(Some(65), false) - kind: Cast [0-0]: + 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) - expr: Expr [44-45]: - ty: Bool(false) - kind: SymbolId(6) - [7] Symbol [40-41]: - name: y - type: Int(Some(65), false) - qsharp_type: BigInt - io_kind: Default - "#]], + 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 + "#]], ); } @@ -255,33 +255,33 @@ fn to_implicit_float_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-13] - init_expr: Expr [18-22]: - ty: Bool(true) - kind: Lit: Bool(true) - [6] Symbol [14-15]: - name: x - type: Bool(false) - qsharp_type: bool - io_kind: Default - ClassicalDeclarationStmt [32-44]: - symbol_id: 7 - ty_span: [32-37] - init_expr: Expr [42-43]: - ty: Float(None, false) - kind: Cast [0-0]: + 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) - expr: Expr [42-43]: - ty: Bool(false) - kind: SymbolId(6) - [7] Symbol [38-39]: - name: y - type: Float(None, false) - qsharp_type: Double - io_kind: Default - "#]], + 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 + "#]], ); } @@ -295,32 +295,32 @@ fn to_explicit_float_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-13] - init_expr: Expr [18-22]: - ty: Bool(true) - kind: Lit: Bool(true) - [6] Symbol [14-15]: - name: x - type: Bool(false) - qsharp_type: bool - io_kind: Default - ClassicalDeclarationStmt [32-48]: - symbol_id: 7 - ty_span: [32-41] - init_expr: Expr [46-47]: - ty: Float(Some(32), false) - kind: Cast [0-0]: + 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) - expr: Expr [46-47]: - ty: Bool(false) - kind: SymbolId(6) - [7] Symbol [42-43]: - name: y - type: Float(Some(32), false) - qsharp_type: Double - io_kind: Default - "#]], + 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 index 1af9e85783..78ab02b6b9 100644 --- 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 @@ -21,27 +21,27 @@ fn to_bit_implicitly_fails() { Stmt [9-23]: annotations: kind: ClassicalDeclarationStmt [9-23]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [19-22]: - ty: Float(None, true) + ty: Float(None, false) kind: Lit: Float(42.0) Stmt [32-42]: annotations: kind: ClassicalDeclarationStmt [32-42]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-35] init_expr: Expr [40-41]: ty: Float(None, false) - kind: SymbolId(6) + kind: SymbolId(8) [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Float(None, false) to type Bit(false) - ,-[test:3:9] + ,-[test:3:17] 2 | float x = 42.; 3 | bit y = x; - : ^^^^^^^^^^ + : ^ 4 | `---- ]"#]], @@ -64,7 +64,7 @@ fn explicit_width_to_bit_implicitly_fails() { Stmt [9-27]: annotations: kind: ClassicalDeclarationStmt [9-27]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-18] init_expr: Expr [23-26]: ty: Float(Some(64), true) @@ -72,19 +72,19 @@ fn explicit_width_to_bit_implicitly_fails() { Stmt [36-46]: annotations: kind: ClassicalDeclarationStmt [36-46]: - symbol_id: 7 + symbol_id: 9 ty_span: [36-39] init_expr: Expr [44-45]: ty: Float(Some(64), false) - kind: SymbolId(6) + kind: SymbolId(8) [Qsc.Qasm3.Compile.CannotCast x Cannot cast expression of type Float(Some(64), false) to type Bit(false) - ,-[test:3:9] + ,-[test:3:17] 2 | float[64] x = 42.; 3 | bit y = x; - : ^^^^^^^^^^ + : ^ 4 | `---- ]"#]], @@ -100,33 +100,33 @@ fn to_bool_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Float(None, true) - kind: Lit: Float(42.0) - [6] Symbol [15-16]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [32-43]: - symbol_id: 7 - ty_span: [32-36] - init_expr: Expr [41-42]: - ty: Bool(false) - kind: Cast [0-0]: + 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) - expr: Expr [41-42]: - ty: Float(None, false) - kind: SymbolId(6) - [7] Symbol [37-38]: - name: y - type: Bool(false) - qsharp_type: bool - io_kind: Default - "#]], + 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 + "#]], ); } @@ -140,33 +140,33 @@ fn to_implicit_int_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Float(None, true) - kind: Lit: Float(42.0) - [6] Symbol [15-16]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [32-42]: - symbol_id: 7 - ty_span: [32-35] - init_expr: Expr [40-41]: - ty: Int(None, false) - kind: Cast [0-0]: + 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) - expr: Expr [40-41]: - ty: Float(None, false) - kind: SymbolId(6) - [7] Symbol [36-37]: - name: y - type: Int(None, false) - qsharp_type: Int - io_kind: Default - "#]], + 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 + "#]], ); } @@ -180,33 +180,33 @@ fn to_explicit_int_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Float(None, true) - kind: Lit: Float(42.0) - [6] Symbol [15-16]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [32-46]: - symbol_id: 7 - ty_span: [32-39] - init_expr: Expr [44-45]: - ty: Int(Some(32), false) - kind: Cast [0-0]: + 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) - expr: Expr [44-45]: - ty: Float(None, false) - kind: SymbolId(6) - [7] Symbol [40-41]: - name: y - type: Int(Some(32), false) - qsharp_type: Int - io_kind: Default - "#]], + 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 + "#]], ); } @@ -220,33 +220,33 @@ fn to_implicit_uint_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Float(None, true) - kind: Lit: Float(42.0) - [6] Symbol [15-16]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [32-43]: - symbol_id: 7 - ty_span: [32-36] - init_expr: Expr [41-42]: - ty: UInt(None, false) - kind: Cast [0-0]: + 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) - expr: Expr [41-42]: - ty: Float(None, false) - kind: SymbolId(6) - [7] Symbol [37-38]: - name: y - type: UInt(None, false) - qsharp_type: Int - io_kind: Default - "#]], + 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 + "#]], ); } @@ -261,22 +261,22 @@ fn negative_lit_to_implicit_uint_implicitly() { input, &expect![[r#" ClassicalDeclarationStmt [9-24]: - symbol_id: 6 + symbol_id: 8 ty_span: [9-14] init_expr: Expr [20-23]: - ty: Float(None, true) + ty: Float(None, false) kind: UnaryOpExpr [20-23]: op: Neg expr: Expr [20-23]: ty: Float(None, true) kind: Lit: Float(42.0) - [6] Symbol [15-16]: + [8] Symbol [15-16]: name: x type: Float(None, false) qsharp_type: Double io_kind: Default ClassicalDeclarationStmt [33-44]: - symbol_id: 7 + symbol_id: 9 ty_span: [33-37] init_expr: Expr [42-43]: ty: UInt(None, false) @@ -284,8 +284,8 @@ fn negative_lit_to_implicit_uint_implicitly() { ty: UInt(None, false) expr: Expr [42-43]: ty: Float(None, false) - kind: SymbolId(6) - [7] Symbol [38-39]: + kind: SymbolId(8) + [9] Symbol [38-39]: name: y type: UInt(None, false) qsharp_type: Int @@ -304,33 +304,33 @@ fn to_explicit_uint_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Float(None, true) - kind: Lit: Float(42.0) - [6] Symbol [15-16]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [32-47]: - symbol_id: 7 - ty_span: [32-40] - init_expr: Expr [45-46]: - ty: UInt(Some(32), false) - kind: Cast [0-0]: + 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) - expr: Expr [45-46]: - ty: Float(None, false) - kind: SymbolId(6) - [7] Symbol [41-42]: - name: y - type: UInt(Some(32), false) - qsharp_type: Int - io_kind: Default - "#]], + 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 + "#]], ); } @@ -344,33 +344,33 @@ fn to_explicit_bigint_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Float(None, true) - kind: Lit: Float(42.0) - [6] Symbol [15-16]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [32-46]: - symbol_id: 7 - ty_span: [32-39] - init_expr: Expr [44-45]: - ty: Int(Some(65), false) - kind: Cast [0-0]: + 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) - expr: Expr [44-45]: - ty: Float(None, false) - kind: SymbolId(6) - [7] Symbol [40-41]: - name: y - type: Int(Some(65), false) - qsharp_type: BigInt - io_kind: Default - "#]], + 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 + "#]], ); } @@ -384,29 +384,29 @@ fn to_implicit_float_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Float(None, true) - kind: Lit: Float(42.0) - [6] Symbol [15-16]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [32-44]: - symbol_id: 7 - ty_span: [32-37] - init_expr: Expr [42-43]: - ty: Float(None, false) - kind: SymbolId(6) - [7] Symbol [38-39]: - name: y - type: Float(None, false) - qsharp_type: Double - io_kind: Default - "#]], + 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 + "#]], ); } @@ -420,29 +420,29 @@ fn to_explicit_float_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Float(None, true) - kind: Lit: Float(42.0) - [6] Symbol [15-16]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [32-48]: - symbol_id: 7 - ty_span: [32-41] - init_expr: Expr [46-47]: - ty: Float(Some(32), false) - kind: SymbolId(6) - [7] Symbol [42-43]: - name: y - type: Float(Some(32), false) - qsharp_type: Double - io_kind: Default - "#]], + 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: SymbolId(8) + [9] Symbol [42-43]: + name: y + type: Float(Some(32), false) + qsharp_type: Double + io_kind: Default + "#]], ); } @@ -456,33 +456,33 @@ fn to_implicit_complex_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Float(None, true) - kind: Lit: Float(42.0) - [6] Symbol [15-16]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [32-53]: - symbol_id: 7 - ty_span: [32-46] - init_expr: Expr [51-52]: - ty: Complex(None, false) - kind: Cast [0-0]: + 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) - expr: Expr [51-52]: - ty: Float(None, false) - kind: SymbolId(6) - [7] Symbol [47-48]: - name: y - type: Complex(None, false) - qsharp_type: Complex - io_kind: Default - "#]], + 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 + "#]], ); } @@ -496,33 +496,33 @@ fn to_explicit_complex_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Float(None, true) - kind: Lit: Float(42.0) - [6] Symbol [15-16]: - name: x - type: Float(None, false) - qsharp_type: Double - io_kind: Default - ClassicalDeclarationStmt [32-57]: - symbol_id: 7 - ty_span: [32-50] - init_expr: Expr [55-56]: - ty: Complex(Some(32), false) - kind: Cast [0-0]: + 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) - expr: Expr [55-56]: - ty: Float(None, false) - kind: SymbolId(6) - [7] Symbol [51-52]: - name: y - type: Complex(Some(32), false) - qsharp_type: Complex - io_kind: Default - "#]], + 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 + "#]], ); } 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 index 1e8664c0bb..b1e756ab95 100644 --- 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 @@ -15,33 +15,33 @@ fn to_bit_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-20]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-19]: - ty: Int(None, true) - kind: Lit: Int(42) - [6] Symbol [13-14]: - name: x - type: Int(None, false) - qsharp_type: Int - io_kind: Default - ClassicalDeclarationStmt [29-39]: - symbol_id: 7 - ty_span: [29-32] - init_expr: Expr [37-38]: - ty: Bit(false) - kind: Cast [0-0]: + 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) - expr: Expr [37-38]: - ty: Int(None, false) - kind: SymbolId(6) - [7] Symbol [33-34]: - name: y - type: Bit(false) - qsharp_type: Result - io_kind: Default - "#]], + 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 + "#]], ); } @@ -55,33 +55,33 @@ fn to_bool_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-20]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-19]: - ty: Int(None, true) - kind: Lit: Int(42) - [6] Symbol [13-14]: - name: x - type: Int(None, false) - qsharp_type: Int - io_kind: Default - ClassicalDeclarationStmt [29-40]: - symbol_id: 7 - ty_span: [29-33] - init_expr: Expr [38-39]: - ty: Bool(false) - kind: Cast [0-0]: + 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) - expr: Expr [38-39]: - ty: Int(None, false) - kind: SymbolId(6) - [7] Symbol [34-35]: - name: y - type: Bool(false) - qsharp_type: bool - io_kind: Default - "#]], + 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 + "#]], ); } @@ -95,29 +95,29 @@ fn to_implicit_int_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-20]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-19]: - ty: Int(None, true) - kind: Lit: Int(42) - [6] Symbol [13-14]: - name: x - type: Int(None, false) - qsharp_type: Int - io_kind: Default - ClassicalDeclarationStmt [29-39]: - symbol_id: 7 - ty_span: [29-32] - init_expr: Expr [37-38]: - ty: Int(None, false) - kind: SymbolId(6) - [7] Symbol [33-34]: - name: y - type: Int(None, false) - qsharp_type: Int - io_kind: Default - "#]], + 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 + "#]], ); } @@ -131,33 +131,33 @@ fn to_explicit_int_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-20]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-19]: - ty: Int(None, true) - kind: Lit: Int(42) - [6] Symbol [13-14]: - name: x - type: Int(None, false) - qsharp_type: Int - io_kind: Default - ClassicalDeclarationStmt [29-43]: - symbol_id: 7 - ty_span: [29-36] - init_expr: Expr [41-42]: - ty: Int(Some(32), false) - kind: Cast [0-0]: + 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) - expr: Expr [41-42]: - ty: Int(None, false) - kind: SymbolId(6) - [7] Symbol [37-38]: - name: y - type: Int(Some(32), false) - qsharp_type: Int - io_kind: Default - "#]], + 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 + "#]], ); } @@ -171,33 +171,33 @@ fn to_implicit_uint_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-20]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-19]: - ty: Int(None, true) - kind: Lit: Int(42) - [6] Symbol [13-14]: - name: x - type: Int(None, false) - qsharp_type: Int - io_kind: Default - ClassicalDeclarationStmt [29-40]: - symbol_id: 7 - ty_span: [29-33] - init_expr: Expr [38-39]: - ty: UInt(None, false) - kind: Cast [0-0]: + 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) - expr: Expr [38-39]: - ty: Int(None, false) - kind: SymbolId(6) - [7] Symbol [34-35]: - name: y - type: UInt(None, false) - qsharp_type: Int - io_kind: Default - "#]], + 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 + "#]], ); } @@ -211,33 +211,33 @@ fn to_explicit_uint_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-20]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-19]: - ty: Int(None, true) - kind: Lit: Int(42) - [6] Symbol [13-14]: - name: x - type: Int(None, false) - qsharp_type: Int - io_kind: Default - ClassicalDeclarationStmt [29-44]: - symbol_id: 7 - ty_span: [29-37] - init_expr: Expr [42-43]: - ty: UInt(Some(32), false) - kind: Cast [0-0]: + 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) - expr: Expr [42-43]: - ty: Int(None, false) - kind: SymbolId(6) - [7] Symbol [38-39]: - name: y - type: UInt(Some(32), false) - qsharp_type: Int - io_kind: Default - "#]], + 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 + "#]], ); } @@ -251,33 +251,33 @@ fn to_explicit_bigint_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-20]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-19]: - ty: Int(None, true) - kind: Lit: Int(42) - [6] Symbol [13-14]: - name: x - type: Int(None, false) - qsharp_type: Int - io_kind: Default - ClassicalDeclarationStmt [29-43]: - symbol_id: 7 - ty_span: [29-36] - init_expr: Expr [41-42]: - ty: Int(Some(65), false) - kind: Cast [0-0]: + 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) - expr: Expr [41-42]: - ty: Int(None, false) - kind: SymbolId(6) - [7] Symbol [37-38]: - name: y - type: Int(Some(65), false) - qsharp_type: BigInt - io_kind: Default - "#]], + 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 + "#]], ); } @@ -291,33 +291,33 @@ fn to_implicit_float_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-20]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-19]: - ty: Int(None, true) - kind: Lit: Int(42) - [6] Symbol [13-14]: - name: x - type: Int(None, false) - qsharp_type: Int - io_kind: Default - ClassicalDeclarationStmt [29-41]: - symbol_id: 7 - ty_span: [29-34] - init_expr: Expr [39-40]: - ty: Float(None, false) - kind: Cast [0-0]: + 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) - expr: Expr [39-40]: - ty: Int(None, false) - kind: SymbolId(6) - [7] Symbol [35-36]: - name: y - type: Float(None, false) - qsharp_type: Double - io_kind: Default - "#]], + 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 + "#]], ); } @@ -331,33 +331,33 @@ fn to_explicit_float_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-20]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-19]: - ty: Int(None, true) - kind: Lit: Int(42) - [6] Symbol [13-14]: - name: x - type: Int(None, false) - qsharp_type: Int - io_kind: Default - ClassicalDeclarationStmt [29-45]: - symbol_id: 7 - ty_span: [29-38] - init_expr: Expr [43-44]: - ty: Float(Some(32), false) - kind: Cast [0-0]: + 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) - expr: Expr [43-44]: - ty: Int(None, false) - kind: SymbolId(6) - [7] Symbol [39-40]: - name: y - type: Float(Some(32), false) - qsharp_type: Double - io_kind: Default - "#]], + 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 + "#]], ); } @@ -371,33 +371,33 @@ fn to_implicit_complex_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-20]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-19]: - ty: Int(None, true) - kind: Lit: Int(42) - [6] Symbol [13-14]: - name: x - type: Int(None, false) - qsharp_type: Int - io_kind: Default - ClassicalDeclarationStmt [29-50]: - symbol_id: 7 - ty_span: [29-43] - init_expr: Expr [48-49]: - ty: Complex(None, false) - kind: Cast [0-0]: + 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) - expr: Expr [48-49]: - ty: Int(None, false) - kind: SymbolId(6) - [7] Symbol [44-45]: - name: y - type: Complex(None, false) - qsharp_type: Complex - io_kind: Default - "#]], + 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 + "#]], ); } @@ -411,32 +411,32 @@ fn to_explicit_complex_implicitly() { check_classical_decls( input, &expect![[r#" - ClassicalDeclarationStmt [9-20]: - symbol_id: 6 - ty_span: [9-12] - init_expr: Expr [17-19]: - ty: Int(None, true) - kind: Lit: Int(42) - [6] Symbol [13-14]: - name: x - type: Int(None, false) - qsharp_type: Int - io_kind: Default - ClassicalDeclarationStmt [29-54]: - symbol_id: 7 - ty_span: [29-47] - init_expr: Expr [52-53]: - ty: Complex(Some(32), false) - kind: Cast [0-0]: + 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) - expr: Expr [52-53]: - ty: Int(None, false) - kind: SymbolId(6) - [7] Symbol [48-49]: - name: y - type: Complex(Some(32), false) - qsharp_type: Complex - io_kind: Default - "#]], + 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/for_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/for_stmt.rs index 620d87a20e..147c328b78 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements/for_stmt.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/for_stmt.rs @@ -18,16 +18,16 @@ fn shadowing_loop_variable_in_single_stmt_body_fails() { Stmt [5-39]: annotations: kind: ForStmt [5-39]: - loop_variable: 6 + loop_variable: 8 iterable: DiscreteSet [18-20]: values: body: Stmt [29-39]: annotations: kind: ClassicalDeclarationStmt [29-39]: - symbol_id: 6 + symbol_id: 8 ty_span: [29-32] init_expr: Expr [37-38]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(2) [Qsc.Qasm3.Compile.RedefinedSymbol @@ -53,7 +53,7 @@ fn shadowing_loop_variable_in_block_body_succeeds() { ", &expect![[r#" ForStmt [5-47]: - loop_variable: 6 + loop_variable: 8 iterable: DiscreteSet [18-20]: values: body: Stmt [21-47]: @@ -62,10 +62,10 @@ fn shadowing_loop_variable_in_block_body_succeeds() { Stmt [31-41]: annotations: kind: ClassicalDeclarationStmt [31-41]: - symbol_id: 7 + symbol_id: 9 ty_span: [31-34] init_expr: Expr [39-40]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(2) "#]], ); @@ -84,22 +84,22 @@ fn loop_creates_its_own_scope() { ", &expect![[r#" ClassicalDeclarationStmt [5-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [5-8] init_expr: Expr [13-14]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(0) ForStmt [20-177]: - loop_variable: 7 + loop_variable: 9 iterable: DiscreteSet [33-35]: values: body: Stmt [167-177]: annotations: kind: ClassicalDeclarationStmt [167-177]: - symbol_id: 8 + symbol_id: 10 ty_span: [167-170] init_expr: Expr [175-176]: - ty: Int(None, true) + 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 index 1083cbb167..850228461a 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements/if_stmt.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/if_stmt.rs @@ -8,7 +8,7 @@ use expect_test::expect; fn if_branch_doesnt_create_its_own_scope() { check_stmt_kinds( " - int a = 0; + int a = 2; if (true) int a = 1; ", &expect![[r#" @@ -18,11 +18,11 @@ fn if_branch_doesnt_create_its_own_scope() { Stmt [5-15]: annotations: kind: ClassicalDeclarationStmt [5-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [5-8] init_expr: Expr [13-14]: - ty: Int(None, true) - kind: Lit: Int(0) + ty: Int(None, false) + kind: Lit: Int(2) Stmt [20-40]: annotations: kind: IfStmt [20-40]: @@ -32,10 +32,10 @@ fn if_branch_doesnt_create_its_own_scope() { if_body: Stmt [30-40]: annotations: kind: ClassicalDeclarationStmt [30-40]: - symbol_id: 6 + symbol_id: 8 ty_span: [30-33] init_expr: Expr [38-39]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(1) else_body: @@ -43,7 +43,7 @@ fn if_branch_doesnt_create_its_own_scope() { x Redefined symbol: a. ,-[test:3:19] - 2 | int a = 0; + 2 | int a = 2; 3 | if (true) int a = 1; : ^ 4 | @@ -56,7 +56,7 @@ fn if_branch_doesnt_create_its_own_scope() { fn else_branch_doesnt_create_its_own_scope() { check_stmt_kinds( " - int a = 0; + int a = 2; if (true) {} else int a = 1; ", @@ -67,11 +67,11 @@ fn else_branch_doesnt_create_its_own_scope() { Stmt [5-15]: annotations: kind: ClassicalDeclarationStmt [5-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [5-8] init_expr: Expr [13-14]: - ty: Int(None, true) - kind: Lit: Int(0) + ty: Int(None, false) + kind: Lit: Int(2) Stmt [20-52]: annotations: kind: IfStmt [20-52]: @@ -84,10 +84,10 @@ fn else_branch_doesnt_create_its_own_scope() { else_body: Stmt [42-52]: annotations: kind: ClassicalDeclarationStmt [42-52]: - symbol_id: 6 + symbol_id: 8 ty_span: [42-45] init_expr: Expr [50-51]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(1) [Qsc.Qasm3.Compile.RedefinedSymbol @@ -107,16 +107,16 @@ fn else_branch_doesnt_create_its_own_scope() { fn branch_block_creates_a_new_scope() { check_stmt_kinds( " - int a = 0; + int a = 2; if (true) { int a = 1; } ", &expect![[r#" ClassicalDeclarationStmt [5-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [5-8] init_expr: Expr [13-14]: - ty: Int(None, true) - kind: Lit: Int(0) + ty: Int(None, false) + kind: Lit: Int(2) IfStmt [20-44]: condition: Expr [24-28]: ty: Bool(true) @@ -127,10 +127,10 @@ fn branch_block_creates_a_new_scope() { Stmt [32-42]: annotations: kind: ClassicalDeclarationStmt [32-42]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-35] init_expr: Expr [40-41]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(1) else_body: "#]], @@ -141,17 +141,17 @@ fn branch_block_creates_a_new_scope() { fn if_scope_and_else_scope_are_different() { check_stmt_kinds( " - int a = 0; + int a = 2; if (true) { int a = 1; } else { int a = 2; } ", &expect![[r#" ClassicalDeclarationStmt [5-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [5-8] init_expr: Expr [13-14]: - ty: Int(None, true) - kind: Lit: Int(0) + ty: Int(None, false) + kind: Lit: Int(2) IfStmt [20-68]: condition: Expr [24-28]: ty: Bool(true) @@ -162,10 +162,10 @@ fn if_scope_and_else_scope_are_different() { Stmt [32-42]: annotations: kind: ClassicalDeclarationStmt [32-42]: - symbol_id: 7 + symbol_id: 9 ty_span: [32-35] init_expr: Expr [40-41]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(1) else_body: Stmt [54-68]: annotations: @@ -173,10 +173,10 @@ fn if_scope_and_else_scope_are_different() { Stmt [56-66]: annotations: kind: ClassicalDeclarationStmt [56-66]: - symbol_id: 8 + symbol_id: 10 ty_span: [56-59] init_expr: Expr [64-65]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(2) "#]], ); @@ -187,21 +187,21 @@ fn condition_cast() { check_stmt_kinds( "if (1) true;", &expect![[r#" - IfStmt [0-12]: - condition: Expr [4-5]: - ty: Bool(false) - kind: Cast [0-0]: - ty: Bool(false) - 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]: + IfStmt [0-12]: + condition: Expr [4-5]: + ty: Bool(true) + kind: Cast [0-0]: ty: Bool(true) - kind: Lit: Bool(true) - else_body: - "#]], + 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 index 6fd0950ff9..fd72b20b3b 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements/switch_stmt.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/switch_stmt.rs @@ -47,7 +47,7 @@ fn not_supported_before_version_3_1() { fn cases_introduce_their_own_scope() { check_stmt_kinds( r#" - int a = 0; + int a = 3; switch (1) { case 1 { int a = 1; } case 2, 3 { int a = 2; } @@ -55,11 +55,11 @@ fn cases_introduce_their_own_scope() { "#, &expect![[r#" ClassicalDeclarationStmt [5-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [5-8] init_expr: Expr [13-14]: - ty: Int(None, true) - kind: Lit: Int(0) + ty: Int(None, false) + kind: Lit: Int(3) SwitchStmt [20-101]: target: Expr [28-29]: ty: Int(None, true) @@ -74,10 +74,10 @@ fn cases_introduce_their_own_scope() { Stmt [50-60]: annotations: kind: ClassicalDeclarationStmt [50-60]: - symbol_id: 7 + symbol_id: 9 ty_span: [50-53] init_expr: Expr [58-59]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(1) SwitchCase [71-95]: labels: @@ -91,10 +91,10 @@ fn cases_introduce_their_own_scope() { Stmt [83-93]: annotations: kind: ClassicalDeclarationStmt [83-93]: - symbol_id: 8 + symbol_id: 10 ty_span: [83-86] init_expr: Expr [91-92]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(2) default_case: "#]], @@ -108,9 +108,9 @@ fn target_cast() { &expect![[r#" SwitchStmt [0-31]: target: Expr [8-12]: - ty: Int(None, false) + ty: Int(None, true) kind: Cast [0-0]: - ty: Int(None, false) + ty: Int(None, true) expr: Expr [8-12]: ty: Bool(true) kind: Lit: Bool(true) @@ -118,9 +118,9 @@ fn target_cast() { SwitchCase [16-29]: labels: Expr [21-26]: - ty: Int(None, false) + ty: Int(None, true) kind: Cast [0-0]: - ty: Int(None, false) + ty: Int(None, true) expr: Expr [21-26]: ty: Bool(true) kind: Lit: Bool(false) diff --git a/compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs index 91b9fdfb3e..8d926b8974 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs @@ -8,7 +8,7 @@ use expect_test::expect; fn single_stmt_body_doesnt_creates_its_own_scope() { check_stmt_kinds( " - int a = 0; + int a = 3; while(true) int a = 1; ", &expect![[r#" @@ -18,11 +18,11 @@ fn single_stmt_body_doesnt_creates_its_own_scope() { Stmt [5-15]: annotations: kind: ClassicalDeclarationStmt [5-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [5-8] init_expr: Expr [13-14]: - ty: Int(None, true) - kind: Lit: Int(0) + ty: Int(None, false) + kind: Lit: Int(3) Stmt [20-42]: annotations: kind: WhileLoop [20-42]: @@ -32,17 +32,17 @@ fn single_stmt_body_doesnt_creates_its_own_scope() { body: Stmt [32-42]: annotations: kind: ClassicalDeclarationStmt [32-42]: - symbol_id: 6 + symbol_id: 8 ty_span: [32-35] init_expr: Expr [40-41]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(1) [Qsc.Qasm3.Compile.RedefinedSymbol x Redefined symbol: a. ,-[test:3:21] - 2 | int a = 0; + 2 | int a = 3; 3 | while(true) int a = 1; : ^ 4 | @@ -55,16 +55,16 @@ fn single_stmt_body_doesnt_creates_its_own_scope() { fn block_body_creates_its_own_scope() { check_stmt_kinds( " - int a = 0; + int a = 3; while(true) { int a = 1; } ", &expect![[r#" ClassicalDeclarationStmt [5-15]: - symbol_id: 6 + symbol_id: 8 ty_span: [5-8] init_expr: Expr [13-14]: - ty: Int(None, true) - kind: Lit: Int(0) + ty: Int(None, false) + kind: Lit: Int(3) WhileLoop [20-46]: condition: Expr [26-30]: ty: Bool(true) @@ -75,10 +75,10 @@ fn block_body_creates_its_own_scope() { Stmt [34-44]: annotations: kind: ClassicalDeclarationStmt [34-44]: - symbol_id: 7 + symbol_id: 9 ty_span: [34-37] init_expr: Expr [42-43]: - ty: Int(None, true) + ty: Int(None, false) kind: Lit: Int(1) "#]], ); @@ -91,9 +91,9 @@ fn condition_cast() { &expect![[r#" WhileLoop [0-15]: condition: Expr [7-8]: - ty: Bool(false) + ty: Bool(true) kind: Cast [0-0]: - ty: Bool(false) + ty: Bool(true) expr: Expr [7-8]: ty: Int(None, true) kind: Lit: Int(1) diff --git a/compiler/qsc_qasm3/src/semantic/types.rs b/compiler/qsc_qasm3/src/semantic/types.rs index 62ccc95288..aa2ed00e38 100644 --- a/compiler/qsc_qasm3/src/semantic/types.rs +++ b/compiler/qsc_qasm3/src/semantic/types.rs @@ -177,187 +177,43 @@ impl Type { #[must_use] pub fn get_indexed_type(&self) -> Option { let ty = match self { - Type::BitArray(dims, is_const) => match dims { - ArrayDimensions::One(_) => Type::Bit(*is_const), - ArrayDimensions::Two(d1, _) => Type::BitArray(ArrayDimensions::One(*d1), *is_const), - ArrayDimensions::Three(d1, d2, _) => { - Type::BitArray(ArrayDimensions::Two(*d1, *d2), *is_const) - } - ArrayDimensions::Four(d1, d2, d3, _) => { - Type::BitArray(ArrayDimensions::Three(*d1, *d2, *d3), *is_const) - } - ArrayDimensions::Five(d1, d2, d3, d4, _) => { - Type::BitArray(ArrayDimensions::Four(*d1, *d2, *d3, *d4), *is_const) - } - ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { - Type::BitArray(ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5), *is_const) - } - ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => Type::BitArray( - ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6), - *is_const, - ), - ArrayDimensions::Err => Type::Err, - }, - Type::QubitArray(dims) => match dims { - ArrayDimensions::One(_) => Type::Qubit, - ArrayDimensions::Two(d1, _) => Type::QubitArray(ArrayDimensions::One(*d1)), - ArrayDimensions::Three(d1, d2, _) => { - Type::QubitArray(ArrayDimensions::Two(*d1, *d2)) - } - ArrayDimensions::Four(d1, d2, d3, _) => { - Type::QubitArray(ArrayDimensions::Three(*d1, *d2, *d3)) - } - ArrayDimensions::Five(d1, d2, d3, d4, _) => { - Type::QubitArray(ArrayDimensions::Four(*d1, *d2, *d3, *d4)) - } - ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { - Type::QubitArray(ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) - } - ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { - Type::QubitArray(ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) - } - ArrayDimensions::Err => Type::Err, - }, - Type::BoolArray(dims) => match dims { - ArrayDimensions::One(_) => Type::Bool(false), - ArrayDimensions::Two(d1, _) => Type::BoolArray(ArrayDimensions::One(*d1)), - ArrayDimensions::Three(d1, d2, _) => { - Type::BoolArray(ArrayDimensions::Two(*d1, *d2)) - } - ArrayDimensions::Four(d1, d2, d3, _) => { - Type::BoolArray(ArrayDimensions::Three(*d1, *d2, *d3)) - } - ArrayDimensions::Five(d1, d2, d3, d4, _) => { - Type::BoolArray(ArrayDimensions::Four(*d1, *d2, *d3, *d4)) - } - ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { - Type::BoolArray(ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) - } - ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { - Type::BoolArray(ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) - } - ArrayDimensions::Err => Type::Err, - }, - Type::AngleArray(size, dims) => match dims { - ArrayDimensions::One(_) => Type::Angle(*size, false), - ArrayDimensions::Two(d1, _) => Type::AngleArray(*size, ArrayDimensions::One(*d1)), - ArrayDimensions::Three(d1, d2, _) => { - Type::AngleArray(*size, ArrayDimensions::Two(*d1, *d2)) - } - ArrayDimensions::Four(d1, d2, d3, _) => { - Type::AngleArray(*size, ArrayDimensions::Three(*d1, *d2, *d3)) - } - ArrayDimensions::Five(d1, d2, d3, d4, _) => { - Type::AngleArray(*size, ArrayDimensions::Four(*d1, *d2, *d3, *d4)) - } - ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { - Type::AngleArray(*size, ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) - } - ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { - Type::AngleArray(*size, ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) - } - ArrayDimensions::Err => Type::Err, - }, - Type::ComplexArray(size, dims) => match dims { - ArrayDimensions::One(_) => Type::Complex(*size, false), - ArrayDimensions::Two(d1, _) => Type::ComplexArray(*size, ArrayDimensions::One(*d1)), - ArrayDimensions::Three(d1, d2, _) => { - Type::ComplexArray(*size, ArrayDimensions::Two(*d1, *d2)) - } - ArrayDimensions::Four(d1, d2, d3, _) => { - Type::ComplexArray(*size, ArrayDimensions::Three(*d1, *d2, *d3)) - } - ArrayDimensions::Five(d1, d2, d3, d4, _) => { - Type::ComplexArray(*size, ArrayDimensions::Four(*d1, *d2, *d3, *d4)) - } - ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { - Type::ComplexArray(*size, ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) - } - ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { - Type::ComplexArray(*size, ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) - } - ArrayDimensions::Err => Type::Err, - }, - Type::DurationArray(dims) => match dims { - ArrayDimensions::One(_) => Type::Duration(false), - ArrayDimensions::Two(d1, _) => Type::DurationArray(ArrayDimensions::One(*d1)), - ArrayDimensions::Three(d1, d2, _) => { - Type::DurationArray(ArrayDimensions::Two(*d1, *d2)) - } - ArrayDimensions::Four(d1, d2, d3, _) => { - Type::DurationArray(ArrayDimensions::Three(*d1, *d2, *d3)) - } - ArrayDimensions::Five(d1, d2, d3, d4, _) => { - Type::DurationArray(ArrayDimensions::Four(*d1, *d2, *d3, *d4)) - } - ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { - Type::DurationArray(ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) - } - ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { - Type::DurationArray(ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) - } - ArrayDimensions::Err => Type::Err, - }, - Type::FloatArray(size, dims) => match dims { - ArrayDimensions::One(_) => Type::Float(*size, false), - ArrayDimensions::Two(d1, _) => Type::FloatArray(*size, ArrayDimensions::One(*d1)), - ArrayDimensions::Three(d1, d2, _) => { - Type::FloatArray(*size, ArrayDimensions::Two(*d1, *d2)) - } - ArrayDimensions::Four(d1, d2, d3, _) => { - Type::FloatArray(*size, ArrayDimensions::Three(*d1, *d2, *d3)) - } - ArrayDimensions::Five(d1, d2, d3, d4, _) => { - Type::FloatArray(*size, ArrayDimensions::Four(*d1, *d2, *d3, *d4)) - } - ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { - Type::FloatArray(*size, ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) - } - ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { - Type::FloatArray(*size, ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) - } - ArrayDimensions::Err => Type::Err, - }, - Type::IntArray(size, dims) => match dims { - ArrayDimensions::One(_) => Type::Int(*size, false), - ArrayDimensions::Two(d1, _) => Type::IntArray(*size, ArrayDimensions::One(*d1)), - ArrayDimensions::Three(d1, d2, _) => { - Type::IntArray(*size, ArrayDimensions::Two(*d1, *d2)) - } - ArrayDimensions::Four(d1, d2, d3, _) => { - Type::IntArray(*size, ArrayDimensions::Three(*d1, *d2, *d3)) - } - ArrayDimensions::Five(d1, d2, d3, d4, _) => { - Type::IntArray(*size, ArrayDimensions::Four(*d1, *d2, *d3, *d4)) - } - ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { - Type::IntArray(*size, ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) - } - ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { - Type::IntArray(*size, ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) - } - ArrayDimensions::Err => Type::Err, - }, - Type::UIntArray(size, dims) => match dims { - ArrayDimensions::One(_) => Type::UInt(*size, false), - ArrayDimensions::Two(d1, _) => Type::UIntArray(*size, ArrayDimensions::One(*d1)), - ArrayDimensions::Three(d1, d2, _) => { - Type::UIntArray(*size, ArrayDimensions::Two(*d1, *d2)) - } - ArrayDimensions::Four(d1, d2, d3, _) => { - Type::UIntArray(*size, ArrayDimensions::Three(*d1, *d2, *d3)) - } - ArrayDimensions::Five(d1, d2, d3, d4, _) => { - Type::UIntArray(*size, ArrayDimensions::Four(*d1, *d2, *d3, *d4)) - } - ArrayDimensions::Six(d1, d2, d3, d4, d5, _) => { - Type::UIntArray(*size, ArrayDimensions::Five(*d1, *d2, *d3, *d4, *d5)) - } - ArrayDimensions::Seven(d1, d2, d3, d4, d5, d6, _) => { - Type::UIntArray(*size, ArrayDimensions::Six(*d1, *d2, *d3, *d4, *d5, *d6)) - } - ArrayDimensions::Err => Type::Err, - }, + 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) @@ -379,6 +235,22 @@ impl Type { } } + 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, @@ -387,7 +259,28 @@ impl Type { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +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), @@ -400,6 +293,12 @@ pub enum ArrayDimensions { 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 { @@ -499,8 +398,10 @@ pub(crate) fn promote_to_uint_ty( } fn get_uint_ty(ty: &Type) -> Option { - if matches!(ty, Type::UInt(..) | Type::Angle(..)) { + 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())), 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..b08eb71371 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 @@ -199,6 +199,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..3d879ca671 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 @@ -106,7 +106,7 @@ fn to_int_with_higher_width_implicitly_fails() { panic!("Expected error") }; - expect![r#"Cannot cast expression of type BitArray(D1(5), False) to type Int(Some(6), False)"#] + expect!["Cannot cast expression of type BitArray(One(5), false) to type Int(Some(6), false)"] .assert_eq(&error[0].to_string()); } @@ -121,7 +121,7 @@ fn to_int_with_higher_width_decl_implicitly_fails() { panic!("Expected error") }; - expect![r#"Cannot cast expression of type BitArray(D1(5), False) to type Int(Some(6), False)"#] + expect!["Cannot cast expression of type BitArray(One(5), false) to type Int(Some(6), false)"] .assert_eq(&error[0].to_string()); } @@ -137,7 +137,7 @@ fn to_int_with_lower_width_implicitly_fails() { panic!("Expected error") }; - expect![r#"Cannot cast expression of type BitArray(D1(5), False) to type Int(Some(4), False)"#] + expect!["Cannot cast expression of type BitArray(One(5), false) to type Int(Some(4), false)"] .assert_eq(&error[0].to_string()); } @@ -152,6 +152,6 @@ fn to_int_with_lower_width_decl_implicitly_fails() { panic!("Expected error") }; - expect![r#"Cannot cast expression of type BitArray(D1(5), False) to type Int(Some(4), False)"#] + expect!["Cannot cast expression of type BitArray(One(5), false) to type Int(Some(4), false)"] .assert_eq(&error[0].to_string()); } 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 d9280621ed..721778b5f7 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()); } 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/const_eval.rs b/compiler/qsc_qasm3/src/tests/statement/const_eval.rs new file mode 100644 index 0000000000..e6daded79f --- /dev/null +++ b/compiler/qsc_qasm3/src/tests/statement/const_eval.rs @@ -0,0 +1,1732 @@ +// 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#" + 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#" + 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 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 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#" + 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#" + 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 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#" + 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#" + let a = -1; + let b = -a; + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "casting float to angle is not yet supported"] +fn unary_op_neg_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle a = -1.0; + const bool b = a; + bit[b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#""#]].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#" + let a = 5; + let b = ~~~a; + mutable r = [Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "angles are not yet supported"] +fn unary_op_negb_angle() { + let source = r#" + const angle a = 1.0; + const bool b = ~a; + 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#""#]].assert_eq(&errs_string); +} + +#[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#" + 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#" + function __ResultArrayAsIntBE__(results : Result[]) : Int { + Microsoft.Quantum.Convert.ResultArrayAsInt(Microsoft.Quantum.Arrays.Reversed(results)) + } + 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#" + 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#" + let a = 1; + let b = a <<< 2; + mutable r = [Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "angles are not yet supported"] +fn binary_op_shl_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle a = 1; + const angle b = a << 2; + const bool c = b; + bit[c] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#""#]].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#" + function __ResultAsInt__(input : Result) : Int { + if Microsoft.Quantum.Convert.ResultAsBool(input) { + 1 + } else { + 0 + } + } + 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#" + 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)) + } + 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 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#" + let a = 5; + let b = a >>> 2; + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "angles are not yet supported"] +fn binary_op_shr_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle a = 1; + const angle b = a >> 2; + const bool c = b; + bit[c] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#""#]].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#" + function __ResultAsInt__(input : Result) : Int { + if Microsoft.Quantum.Convert.ResultAsBool(input) { + 1 + } else { + 0 + } + } + 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#" + 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)) + } + 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 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#" + let a = 5; + let b = a &&& 6; + mutable r = [Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "angles are not yet supported"] +fn binary_op_andb_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle a = 1; + const angle b = a & 2; + const bool c = b; + bit[c] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#""#]].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#" + function __ResultAsInt__(input : Result) : Int { + if Microsoft.Quantum.Convert.ResultAsBool(input) { + 1 + } else { + 0 + } + } + 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#" + 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)) + } + 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#" + let a = 5; + let b = a ||| 6; + mutable r = [Zero, Zero, Zero, Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "angles are not yet supported"] +fn binary_op_orb_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle a = 1.0; + const angle b = a | 2.0; + const bool c = b; + bit[c] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#""#]].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#" + function __ResultAsInt__(input : Result) : Int { + if Microsoft.Quantum.Convert.ResultAsBool(input) { + 1 + } else { + 0 + } + } + 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#" + 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)) + } + 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#" + let a = 5; + let b = a ^^^ 6; + mutable r = [Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "angles are not yet supported"] +fn binary_op_xorb_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle a = 1; + const angle b = a ^ 2; + const bool c = b; + bit[c] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#""#]].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#" + function __ResultAsInt__(input : Result) : Int { + if Microsoft.Quantum.Convert.ResultAsBool(input) { + 1 + } else { + 0 + } + } + 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#" + 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)) + } + 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#" + 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#" + 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#" + 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#" + let a = 2; + mutable r1 = [Zero]; + mutable r2 = []; + mutable r3 = []; + mutable r4 = [Zero]; + mutable r5 = []; + mutable r6 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "angles are not yet supported"] +fn binary_op_comparison_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle 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#""#]].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#" + 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#" + 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#" + 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#" + 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#" + let a = 1.; + let b = 2.; + mutable r = [Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "angles are not yet supported"] +fn binary_op_add_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle a = 1.0; + const angle b = 2.0; + bit[a + b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#""#]].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#" + 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#" + 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#" + let a = 3.; + let b = 2.; + mutable r = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "angles are not yet supported"] +fn binary_op_sub_angle() -> 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#""#]].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#" + 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#" + 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#" + let a = 3.; + let b = 2.; + mutable r = [Zero, Zero, Zero, Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "angles are not yet supported"] +fn binary_op_mul_angle() -> miette::Result<(), Vec> { + let source = r#" + const float a = 3.0; + const uint b = 2; + bit[a * b] r1; + bit[b * a] r2; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#""#]].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#" + 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#" + 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#" + let a = 6.; + let b = 2.; + mutable r = [Zero, Zero, Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +#[ignore = "angles are not yet supported"] +fn binary_op_div_angle() -> miette::Result<(), Vec> { + let source = r#" + const angle a = 6.0; + const uint b = 2; + bit[a / b] r; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#""#]].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#" + 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#" + 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#" + 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#" + 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#" + 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 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#" + function __ResultAsBool__(input : Result) : Bool { + Microsoft.Quantum.Convert.ResultAsBool(input) + } + let a = 0; + let b = 1; + let c = 2.; + 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 s5 = __ResultAsBool__(e); + mutable r1 = []; + mutable r2 = [Zero]; + mutable r3 = [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#" + function __BoolAsInt__(value : Bool) : Int { + if value { + 1 + } else { + 0 + } + } + function __ResultAsInt__(input : Result) : Int { + if Microsoft.Quantum.Convert.ResultAsBool(input) { + 1 + } else { + 0 + } + } + 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#" + function __BoolAsInt__(value : Bool) : Int { + if value { + 1 + } else { + 0 + } + } + function __ResultAsInt__(input : Result) : Int { + if Microsoft.Quantum.Convert.ResultAsBool(input) { + 1 + } else { + 0 + } + } + 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#" + function __BoolAsDouble__(value : Bool) : Double { + if value { + 1. + } else { + 0. + } + } + 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] +#[ignore = "angles are not yet supported"] +fn cast_to_angle() -> miette::Result<(), Vec> { + let source = r#" + const float a = 2.0; + const bit b = 1; + + const angle s1 = a; + const angle s2 = b; + + bit[s1] r1; + bit[s2] r2; + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#""#]].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 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#" + function __BoolAsResult__(input : Bool) : Result { + Microsoft.Quantum.Convert.BoolAsResult(input) + } + let a = false; + let b = 1; + let c = 2; + let s1 = __BoolAsResult__(a); + let s2 = if b == 0 { + One + } else { + Zero + }; + let s3 = if c == 0 { + One + } else { + Zero + }; + mutable r1 = []; + mutable r2 = [Zero]; + mutable r3 = [Zero]; + "#]] + .assert_eq(&qsharp); + Ok(()) +} diff --git a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs index 3165ccd847..4bd127bae9 100644 --- a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs +++ b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs @@ -6,6 +6,46 @@ 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#" + operation U(theta : Double, phi : Double, lambda : Double, qubit : Qubit) : Unit is Adj + Ctl { + body ... { + Rz(lambda, qubit); + Ry(theta, qubit); + Rz(phi, qubit); + R(PauliI, -lambda - phi - theta, qubit); + } + adjoint auto; + controlled auto; + controlled adjoint auto; + } + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + U(1., 2., 3., 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#""#].assert_eq(&qsharp); + Ok(()) +} + #[test] fn x_gate_can_be_called() -> miette::Result<(), Vec> { let source = r#" diff --git a/compiler/qsc_qasm3/src/tests/statement/if_stmt.rs b/compiler/qsc_qasm3/src/tests/statement/if_stmt.rs index 6dbd2129f0..9723bf3de8 100644 --- a/compiler/qsc_qasm3/src/tests/statement/if_stmt.rs +++ b/compiler/qsc_qasm3/src/tests/statement/if_stmt.rs @@ -147,6 +147,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/measure.rs b/compiler/qsc_qasm3/src/tests/statement/measure.rs index c21b8e6a87..4072d847e7 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> { @@ -24,7 +25,6 @@ fn single_qubit_can_be_measured_into_single_bit() -> miette::Result<(), Vec miette::Result<(), Vec> { let source = r#" bit c; @@ -87,17 +87,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] diff --git a/compiler/qsc_qasm3/src/tests/statement/switch.rs b/compiler/qsc_qasm3/src/tests/statement/switch.rs index 17aa7257bd..eaabadeb33 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 { @@ -33,7 +33,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 { @@ -53,7 +53,7 @@ fn default_as_only_case_causes_parse_error() { #[test] fn no_cases_causes_parse_error() { let source = r#" - OPENQASM 3.0; + OPENQASM 3.1; int i = 15; switch (i) { } @@ -70,7 +70,7 @@ fn no_cases_causes_parse_error() { #[test] fn spec_case_1() -> miette::Result<(), Vec> { let source = r#" - OPENQASM 3.0; + OPENQASM 3.1; include "stdgates.inc"; qubit q; @@ -115,7 +115,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; @@ -164,7 +164,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; @@ -217,7 +217,7 @@ fn spec_case_3() -> miette::Result<(), Vec> { #[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; @@ -260,7 +260,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"; diff --git a/compiler/qsc_qasm3/src/tests/statement/while_loop.rs b/compiler/qsc_qasm3/src/tests/statement/while_loop.rs index 2f92febde3..76e67f585a 100644 --- a/compiler/qsc_qasm3/src/tests/statement/while_loop.rs +++ b/compiler/qsc_qasm3/src/tests/statement/while_loop.rs @@ -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()); } From 2d67664aa2dc44b85830d2c4732ca17d0ec4a1ef Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Mon, 24 Mar 2025 16:57:32 -0700 Subject: [PATCH 65/98] Removing old test --- .../src/semantic/tests/expression.rs | 47 ------------------- 1 file changed, 47 deletions(-) diff --git a/compiler/qsc_qasm3/src/semantic/tests/expression.rs b/compiler/qsc_qasm3/src/semantic/tests/expression.rs index 6102868912..8a2f3511ad 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/expression.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/expression.rs @@ -8,50 +8,3 @@ mod implicit_cast_from_bitarray; mod implicit_cast_from_bool; mod implicit_cast_from_float; mod implicit_cast_from_int; - -use expect_test::expect; - -use super::check_stmt_kinds; - -#[test] -fn a() { - check_stmt_kinds( - r#" - true && false; - false || true; - !true; - "#, - &expect![[r#" - ExprStmt [9-23]: - expr: Expr [9-22]: - ty: Bool(false) - kind: BinaryOpExpr: - op: AndL - lhs: Expr [9-13]: - ty: Bool(true) - kind: Lit: Bool(true) - rhs: Expr [17-22]: - ty: Bool(true) - kind: Lit: Bool(false) - ExprStmt [32-46]: - expr: Expr [32-45]: - ty: Bool(false) - kind: BinaryOpExpr: - op: OrL - lhs: Expr [32-37]: - ty: Bool(true) - kind: Lit: Bool(false) - rhs: Expr [41-45]: - ty: Bool(true) - kind: Lit: Bool(true) - ExprStmt [55-61]: - expr: Expr [56-60]: - ty: Bool(true) - kind: UnaryOpExpr [56-60]: - op: NotL - expr: Expr [56-60]: - ty: Bool(true) - kind: Lit: Bool(true) - "#]], - ); -} From d93f2f8c365a2dfedf226b212667d0294385e9d9 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Tue, 25 Mar 2025 08:52:39 -0700 Subject: [PATCH 66/98] Parsing now bundles IO source loading errors into parse errors. (#2252) --- compiler/qsc/src/lib.rs | 1 - compiler/qsc_qasm3/src/compile/tests.rs | 4 +- compiler/qsc_qasm3/src/compiler.rs | 45 +-------- compiler/qsc_qasm3/src/io.rs | 22 ++--- compiler/qsc_qasm3/src/io/error.rs | 24 +++++ compiler/qsc_qasm3/src/lib.rs | 9 +- compiler/qsc_qasm3/src/parser.rs | 55 +++++++---- compiler/qsc_qasm3/src/parser/error.rs | 10 ++ compiler/qsc_qasm3/src/parser/prgm.rs | 10 +- compiler/qsc_qasm3/src/parser/tests.rs | 8 +- compiler/qsc_qasm3/src/semantic.rs | 25 +++-- compiler/qsc_qasm3/src/semantic/error.rs | 6 ++ compiler/qsc_qasm3/src/semantic/lowerer.rs | 9 -- compiler/qsc_qasm3/src/semantic/tests.rs | 16 ++-- compiler/qsc_qasm3/src/tests.rs | 93 +++++++++++++++---- compiler/qsc_qasm3/src/tests/declaration.rs | 7 +- compiler/qsc_qasm3/src/tests/output.rs | 11 +-- .../src/tests/sample_circuits/bell_pair.rs | 5 +- .../tests/sample_circuits/rgqft_multiplier.rs | 5 +- .../qsc_qasm3/src/tests/statement/include.rs | 6 +- .../qsc_qasm3/src/tests/statement/reset.rs | 5 +- .../qsc_qasm3/src/tests/statement/switch.rs | 4 +- pip/src/interop.rs | 8 +- 23 files changed, 235 insertions(+), 153 deletions(-) create mode 100644 compiler/qsc_qasm3/src/io/error.rs 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_qasm3/src/compile/tests.rs b/compiler/qsc_qasm3/src/compile/tests.rs index 8c5fb59d12..72b0210d14 100644 --- a/compiler/qsc_qasm3/src/compile/tests.rs +++ b/compiler/qsc_qasm3/src/compile/tests.rs @@ -18,7 +18,7 @@ fn programs_with_includes_with_includes_can_be_compiled() -> miette::Result<(), ("source2.qasm".into(), source2.into()), ]; - let unit = compile_all_fragments("source0.qasm", all_sources).map_err(|e| vec![e])?; + let unit = compile_all_fragments("source0.qasm", all_sources)?; print_compilation_errors(&unit); assert!(!unit.has_errors()); Ok(()) @@ -39,7 +39,7 @@ fn including_stdgates_multiple_times_causes_symbol_redifintion_errors( ("source2.qasm".into(), source2.into()), ]; - let unit = compile_all_fragments("source0.qasm", all_sources).map_err(|e| vec![e])?; + 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 index b23563aa43..35870469ca 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::{path::Path, rc::Rc, sync::Arc}; +use std::{path::Path, rc::Rc}; use num_bigint::BigInt; use qsc_data_structures::span::Span; @@ -25,7 +25,6 @@ use crate::{ build_wrapped_block_expr, managed_qubit_alloc_array, map_qsharp_type_to_ast_ty, wrap_expr_in_parens, }, - io::{InMemorySourceResolver, SourceResolver}, parser::ast::{list_from_iter, List}, runtime::{get_runtime_function_decls, RuntimeFunctions}, semantic::{ @@ -45,48 +44,12 @@ use crate::{ use crate::semantic::ast as semast; use qsc_ast::ast::{self as qsast, NodeId, Package}; -pub fn compile_anon_with_config( - source: S, - config: CompilerConfig, -) -> miette::Result -where - S: AsRef, -{ - let path = std::path::PathBuf::from("Test.qasm"); - let sources = [( - Arc::from(path.display().to_string().as_str()), - Arc::from(source.as_ref()), - )]; - let resolver = InMemorySourceResolver::from_iter(sources); - let source = resolver.resolve(&path)?.1; - compile_with_config(source, &path, &resolver, config) -} - -pub fn compile_all_with_config

( - path: P, - sources: impl IntoIterator, Arc)>, - config: CompilerConfig, -) -> miette::Result -where - P: AsRef, -{ - let resolver = InMemorySourceResolver::from_iter(sources); - let source = resolver.resolve(path.as_ref())?.1; - compile_with_config(source, path, &resolver, config) -} - -pub fn compile_with_config( - source: S, - path: P, - resolver: &R, - config: CompilerConfig, -) -> miette::Result +pub fn compile_with_config(source: S, path: P, config: CompilerConfig) -> QasmCompileUnit where S: AsRef, P: AsRef, - R: SourceResolver, { - let res = crate::semantic::parse_source(source, path, resolver)?; + let res = crate::semantic::parse(source, path); let program = res.program; let compiler = crate::compiler::QasmCompiler { @@ -98,7 +61,7 @@ where errors: res.errors, }; - Ok(compiler.compile(&program)) + compiler.compile(&program) } pub struct QasmCompiler { 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/lib.rs b/compiler/qsc_qasm3/src/lib.rs index a9b4e607d0..35f30b2622 100644 --- a/compiler/qsc_qasm3/src/lib.rs +++ b/compiler/qsc_qasm3/src/lib.rs @@ -9,6 +9,7 @@ mod ast_builder; mod compile; mod compiler; pub use compile::qasm_to_program; +pub use compiler::compile_with_config; pub mod io; mod keyword; mod lex; @@ -64,6 +65,9 @@ impl Error { #[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] #[error(transparent)] pub enum ErrorKind { + #[error(transparent)] + #[diagnostic(transparent)] + IO(#[from] crate::io::Error), #[error("QASM3 Parse Error: {0}")] Parse(String, #[label] Span), #[error(transparent)] @@ -75,17 +79,18 @@ pub enum ErrorKind { #[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::IO(error) => Self::IO(error), ErrorKind::Parse(error, span) => Self::Parse(error, span + offset), ErrorKind::Parser(error) => Self::Parser(error.with_offset(offset)), ErrorKind::Semantic(error) => Self::Semantic(error.with_offset(offset)), ErrorKind::NotFound(error) => Self::NotFound(error), - ErrorKind::IO(error) => Self::IO(error), + ErrorKind::OldIO(error) => Self::OldIO(error), } } } diff --git a/compiler/qsc_qasm3/src/parser.rs b/compiler/qsc_qasm3/src/parser.rs index 6ec86a8472..69b9e9204b 100644 --- a/compiler/qsc_qasm3/src/parser.rs +++ b/compiler/qsc_qasm3/src/parser.rs @@ -110,14 +110,14 @@ fn update_offsets(source_map: &SourceMap, source: &mut QasmSource) { /// This function will resolve includes using the provided resolver. /// If an include file cannot be resolved, an error will be returned. /// If a file is included recursively, a stack overflow occurs. -pub fn parse_source(source: S, path: P, resolver: &R) -> miette::Result +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)?; - Ok(QasmParseResult::new(res)) + let res = parse_qasm_source(source, path, resolver); + QasmParseResult::new(res) } /// Creates a Q# source map from a QASM parse output. The `QasmSource` @@ -230,38 +230,54 @@ impl QasmSource { /// This function is the start of a recursive process that will resolve all /// includes in the QASM file. Any includes are parsed as if their contents /// were defined where the include statement is. -fn parse_qasm_file(path: P, resolver: &R) -> miette::Result +fn parse_qasm_file(path: P, resolver: &R) -> QasmSource where P: AsRef, R: SourceResolver, { - let (path, source) = resolver.resolve(&path)?; - parse_qasm_source(source, path, resolver) + 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) -> miette::Result +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)?; - Ok(QasmSource::new(source, path, program, errors, includes)) + 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, -) -> miette::Result<(Program, Vec, Vec)> +) -> (Program, Vec, Vec) where R: SourceResolver, { - let (program, errors) = parse(source.as_ref())?; - let included = parse_includes(&program, resolver)?; - Ok((program, errors, included)) + let (program, errors) = parse(source.as_ref()); + let included = parse_includes(&program, resolver); + (program, errors, included) } -fn parse_includes(program: &Program, resolver: &R) -> miette::Result> +fn parse_includes(program: &Program, resolver: &R) -> Vec where R: SourceResolver, { @@ -274,12 +290,12 @@ where if file_path.to_lowercase() == "stdgates.inc" { continue; } - let source = parse_qasm_file(file_path, resolver)?; + let source = parse_qasm_file(file_path, resolver); includes.push(source); } } - Ok(includes) + includes } pub(crate) type Result = std::result::Result; @@ -288,8 +304,9 @@ pub(crate) trait Parser: FnMut(&mut ParserContext) -> Result {} impl Result> Parser for F {} -pub fn parse(input: &str) -> Result<(Program, Vec)> { +#[must_use] +pub fn parse(input: &str) -> (Program, Vec) { let mut scanner = ParserContext::new(input); - let program = prgm::parse(&mut scanner)?; - Ok((program, scanner.into_errors())) + let program = prgm::parse(&mut scanner); + (program, scanner.into_errors()) } diff --git a/compiler/qsc_qasm3/src/parser/error.rs b/compiler/qsc_qasm3/src/parser/error.rs index da16aa0353..30c4f81d55 100644 --- a/compiler/qsc_qasm3/src/parser/error.rs +++ b/compiler/qsc_qasm3/src/parser/error.rs @@ -137,6 +137,9 @@ pub enum ErrorKind { #[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 { @@ -160,6 +163,13 @@ impl ErrorKind { 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/prgm.rs b/compiler/qsc_qasm3/src/parser/prgm.rs index d87859a59e..b9ca8586cd 100644 --- a/compiler/qsc_qasm3/src/parser/prgm.rs +++ b/compiler/qsc_qasm3/src/parser/prgm.rs @@ -15,12 +15,12 @@ use super::ast::{Program, Stmt, StmtKind, Version}; use super::ParserContext; /// Grammar: `version? statementOrScope* EOF`. -pub(super) fn parse(s: &mut ParserContext) -> Result { +pub(super) fn parse(s: &mut ParserContext) -> Program { let lo = s.peek().span.lo; - let version = opt(s, parse_version)?; - let stmts = parse_top_level_nodes(s)?; + let version = opt(s, parse_version).unwrap_or_default(); + let stmts = parse_top_level_nodes(s).unwrap_or_default(); - Ok(Program { + Program { span: s.span(lo), version, statements: stmts @@ -28,7 +28,7 @@ pub(super) fn parse(s: &mut ParserContext) -> Result { .map(Box::new) .collect::>() .into_boxed_slice(), - }) + } } /// Grammar: `OPENQASM VersionSpecifier SEMICOLON`. diff --git a/compiler/qsc_qasm3/src/parser/tests.rs b/compiler/qsc_qasm3/src/parser/tests.rs index acf6acb26f..30bd4e45d7 100644 --- a/compiler/qsc_qasm3/src/parser/tests.rs +++ b/compiler/qsc_qasm3/src/parser/tests.rs @@ -24,8 +24,10 @@ where P: AsRef, { let resolver = InMemorySourceResolver::from_iter(sources); - let source = resolver.resolve(path.as_ref()).map_err(|e| vec![e])?.1; - let res = crate::parser::parse_source(source, path, &resolver).map_err(|e| vec![e])?; + 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() @@ -43,7 +45,7 @@ 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 res = parse_source(source, "test", &resolver); if res.source.has_errors() { let errors = res .errors() diff --git a/compiler/qsc_qasm3/src/semantic.rs b/compiler/qsc_qasm3/src/semantic.rs index 6b470711b6..e99dac9906 100644 --- a/compiler/qsc_qasm3/src/semantic.rs +++ b/compiler/qsc_qasm3/src/semantic.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::io::InMemorySourceResolver; use crate::io::SourceResolver; use crate::parser::QasmSource; @@ -90,29 +91,37 @@ impl QasmSemanticParseResult { } } +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, -) -> miette::Result +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 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(); - Ok(QasmSemanticParseResult { + 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/error.rs b/compiler/qsc_qasm3/src/semantic/error.rs index 6a6652456a..01b800c9c9 100644 --- a/compiler/qsc_qasm3/src/semantic/error.rs +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -380,3 +380,9 @@ impl SemanticErrorKind { } } } + +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 index dd4e054af3..7ce669bee2 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -1386,15 +1386,6 @@ impl Lowerer { let target_ty = Type::Int(None, target.ty.is_const()); let target = self.cast_expr_to_type(&target_ty, &target); - // It is a parse error to have a switch statement with no cases, - // even if the default block is present. Getting here means the - // parser is broken or they changed the grammar. - if cases.is_empty() { - self.push_semantic_error(SemanticErrorKind::SwitchStatementMustHaveAtLeastOneCase( - stmt.span, - )); - } - // 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 { diff --git a/compiler/qsc_qasm3/src/semantic/tests.rs b/compiler/qsc_qasm3/src/semantic/tests.rs index ce82fc5ff6..583875c50a 100644 --- a/compiler/qsc_qasm3/src/semantic/tests.rs +++ b/compiler/qsc_qasm3/src/semantic/tests.rs @@ -30,8 +30,10 @@ 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 (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() @@ -49,7 +51,7 @@ 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 res = parse_source(source, "test", &resolver); if res.source.has_errors() { let errors = res .errors() @@ -138,9 +140,7 @@ fn check_map( S: AsRef, { let resolver = InMemorySourceResolver::from_iter([("test".into(), input.as_ref().into())]); - let res = parse_source(input, "test", &resolver) - .map_err(|e| vec![e]) - .expect("failed to parse"); + let res = parse_source(input, "test", &resolver); let errors = res.all_errors(); @@ -188,9 +188,7 @@ fn check_map_all

( .map_err(|e| vec![e]) .expect("could not load source") .1; - let res = parse_source(source, path, &resolver) - .map_err(|e| vec![e]) - .expect("failed to parse"); + let res = parse_source(source, path, &resolver); let errors = res.all_errors(); diff --git a/compiler/qsc_qasm3/src/tests.rs b/compiler/qsc_qasm3/src/tests.rs index 1c2a10bee5..c98591dac4 100644 --- a/compiler/qsc_qasm3/src/tests.rs +++ b/compiler/qsc_qasm3/src/tests.rs @@ -1,7 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::compiler::compile_anon_with_config; +use crate::runtime::RuntimeFunctions; +use crate::semantic::symbols::SymbolTable; use crate::{CompilerConfig, OutputSemantics, ProgramType, QasmCompileUnit, QubitSemantics}; use miette::Report; use qsc::interpret::Error; @@ -67,7 +68,7 @@ pub(crate) fn generate_qir_from_ast( ) } -fn compile(source: S) -> miette::Result +fn compile(source: S) -> miette::Result> where S: AsRef, { @@ -78,13 +79,42 @@ where Some("Test".into()), None, ); - compile_anon_with_config(source, config) + compile_with_config(source, config) +} + +fn compile_with_config( + source: S, + config: CompilerConfig, +) -> miette::Result> +where + S: AsRef, +{ + let res = parse(source)?; + if res.has_syntax_errors() { + for e in res.sytax_errors() { + println!("{:?}", Report::new(e.clone())); + } + } + assert!(!res.has_syntax_errors()); + let program = res.program; + + let compiler = crate::compiler::QasmCompiler { + source_map: res.source_map, + config, + stmts: vec![], + runtime: RuntimeFunctions::empty(), + 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 +) -> miette::Result> where P: AsRef, { @@ -95,13 +125,13 @@ where Some("Test".into()), None, ); - crate::compiler::compile_all_with_config(path, sources, config) + compile_all_with_config(path, sources, config) } pub fn compile_all_fragments

( path: P, sources: impl IntoIterator, Arc)>, -) -> miette::Result +) -> miette::Result> where P: AsRef, { @@ -112,7 +142,7 @@ where None, None, ); - crate::compiler::compile_all_with_config(path, sources, config) + compile_all_with_config(path, sources, config) } fn compile_fragments(source: S) -> miette::Result> @@ -126,11 +156,36 @@ where None, None, ); - compile_anon_with_config(source, config).map_err(|e| vec![e]) + 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![], + runtime: RuntimeFunctions::empty(), + symbols: SymbolTable::default(), + errors: res.errors, + }; + + let unit = compiler.compile(&program); + Ok(unit) } fn compile_qasm_to_qir(source: &str, profile: Profile) -> Result> { - let unit = compile(source).map_err(|e| vec![e])?; + 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| { @@ -158,8 +213,9 @@ pub(crate) fn parse(source: S) -> miette::Result, { - 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() @@ -179,8 +235,11 @@ 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() @@ -201,7 +260,7 @@ pub fn compile_qasm_to_qsharp_file(source: &str) -> miette::Result miette::Result miette::Result<(), Vec> { None, None, ); - let unit = compile_anon_with_config(source, config).expect("parse failed"); + let unit = compile_with_config(source, config).expect("parse failed"); println!("{:?}", unit.errors); assert_eq!(unit.errors.len(), 5); for error in &unit.errors { @@ -95,7 +94,7 @@ fn stretch() { None, None, ); - let unit = compile_anon_with_config(source, config).expect("parse failed"); + let unit = compile_with_config(source, config).expect("parse failed"); assert!(unit.has_errors()); println!("{:?}", unit.errors); assert!(unit.errors.len() == 2); diff --git a/compiler/qsc_qasm3/src/tests/output.rs b/compiler/qsc_qasm3/src/tests/output.rs index b991b88e51..4f6fdbd3fd 100644 --- a/compiler/qsc_qasm3/src/tests/output.rs +++ b/compiler/qsc_qasm3/src/tests/output.rs @@ -2,7 +2,6 @@ // Licensed under the MIT License. use crate::{ - compiler::compile_anon_with_config, tests::{fail_on_compilation_errors, gen_qsharp}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; @@ -10,7 +9,7 @@ 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> { @@ -36,7 +35,7 @@ fn using_re_semantics_removes_output() -> miette::Result<(), Vec> { Some("Test".into()), None, ); - let unit = compile_anon_with_config(source, config).expect("parse failed"); + 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#" @@ -84,7 +83,7 @@ fn using_qasm_semantics_captures_all_classical_decls_as_output() -> miette::Resu Some("Test".into()), None, ); - let unit = compile_anon_with_config(source, config).expect("parse failed"); + 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#" @@ -132,7 +131,7 @@ fn using_qiskit_semantics_only_bit_array_is_captured_and_reversed( Some("Test".into()), None, ); - let unit = compile_anon_with_config(source, config).expect("parse failed"); + 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#" @@ -187,7 +186,7 @@ c2[2] = measure q[4]; Some("Test".into()), None, ); - let unit = compile_anon_with_config(source, config).expect("parse failed"); + 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()); 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 e0643fb9c2..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::{ - compiler::compile_anon_with_config, - tests::{gen_qsharp, print_compilation_errors}, + tests::{compile_with_config, gen_qsharp, print_compilation_errors}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; @@ -30,7 +29,7 @@ fn it_compiles() { Some("Test".into()), None, ); - let unit = compile_anon_with_config(source, config).expect("parse failed"); + let unit = compile_with_config(source, config).expect("parse failed"); print_compilation_errors(&unit); assert!(!unit.has_errors()); 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 e4ca4ace56..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::{ - compiler::compile_anon_with_config, - tests::{gen_qsharp, print_compilation_errors}, + tests::{compile_with_config, gen_qsharp, print_compilation_errors}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; @@ -18,7 +17,7 @@ fn it_compiles() { Some("Test".into()), None, ); - let unit = compile_anon_with_config(source, config).expect("parse failed"); + let unit = compile_with_config(source, config).expect("parse failed"); print_compilation_errors(&unit); assert!(!unit.has_errors()); diff --git a/compiler/qsc_qasm3/src/tests/statement/include.rs b/compiler/qsc_qasm3/src/tests/statement/include.rs index 6d3879f262..fc21457b00 100644 --- a/compiler/qsc_qasm3/src/tests/statement/include.rs +++ b/compiler/qsc_qasm3/src/tests/statement/include.rs @@ -2,8 +2,8 @@ // Licensed under the MIT License. use crate::{ - compiler::compile_all_with_config, tests::qsharp_from_qasm_compilation, CompilerConfig, - OutputSemantics, ProgramType, QubitSemantics, + tests::{compile_all_with_config, qsharp_from_qasm_compilation}, + CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; use expect_test::expect; use miette::Report; @@ -36,7 +36,7 @@ fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { Some("Test".into()), None, ); - let r = compile_all_with_config("source0.qasm", all_sources, config).map_err(|e| vec![e])?; + let r = compile_all_with_config("source0.qasm", all_sources, config)?; let qsharp = qsharp_from_qasm_compilation(r)?; expect![[r#" namespace qasm3_import { diff --git a/compiler/qsc_qasm3/src/tests/statement/reset.rs b/compiler/qsc_qasm3/src/tests/statement/reset.rs index 48ffa34f49..97698f9e2d 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::{ - compiler::compile_anon_with_config, - tests::{fail_on_compilation_errors, gen_qsharp}, + tests::{compile_with_config, fail_on_compilation_errors, gen_qsharp}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; use expect_test::expect; @@ -31,7 +30,7 @@ fn reset_calls_are_generated_from_qasm() -> miette::Result<(), Vec> { Some("Test".into()), None, ); - let unit = compile_anon_with_config(source, config).map_err(|e| vec![e])?; + let unit = compile_with_config(source, config)?; fail_on_compilation_errors(&unit); let qsharp = gen_qsharp(&unit.package.expect("no package found")); expect![ diff --git a/compiler/qsc_qasm3/src/tests/statement/switch.rs b/compiler/qsc_qasm3/src/tests/statement/switch.rs index eaabadeb33..81083c13f8 100644 --- a/compiler/qsc_qasm3/src/tests/statement/switch.rs +++ b/compiler/qsc_qasm3/src/tests/statement/switch.rs @@ -47,7 +47,7 @@ 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] @@ -64,7 +64,7 @@ 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] diff --git a/pip/src/interop.rs b/pip/src/interop.rs index b109061dd8..c6296cb807 100644 --- a/pip/src/interop.rs +++ b/pip/src/interop.rs @@ -11,6 +11,7 @@ use pyo3::types::{PyDict, PyList}; 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, }; @@ -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(), From 313b7afc9de6b2ee87bdaaeaf65a6d9db20f4de8 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 27 Mar 2025 09:25:25 -0700 Subject: [PATCH 67/98] Compile gphase, gatedef, and control flow statements (#2254) This PR completes the lowering and compilation for: - [x] gate definitions - [x] gphase - [x] for stmt - [x] if stmt - [x] switch stmt - [x] return stmt - [x] classical function calls - [x] classical function decls It also does some partial work to make the compiler infallible, by returning `ExprKind::Err` instead of None when compiling expressions. --- compiler/qsc_qasm3/src/ast_builder.rs | 78 ++- compiler/qsc_qasm3/src/compile.rs | 33 +- compiler/qsc_qasm3/src/compiler.rs | 653 +++++++++++------- compiler/qsc_qasm3/src/parser/ast.rs | 2 + compiler/qsc_qasm3/src/parser/mut_visit.rs | 1 + compiler/qsc_qasm3/src/parser/stmt.rs | 3 + .../qsc_qasm3/src/parser/stmt/tests/gphase.rs | 8 + compiler/qsc_qasm3/src/semantic/ast.rs | 99 +-- compiler/qsc_qasm3/src/semantic/error.rs | 10 + compiler/qsc_qasm3/src/semantic/lowerer.rs | 356 ++++++++-- compiler/qsc_qasm3/src/semantic/types.rs | 2 + compiler/qsc_qasm3/src/tests/declaration.rs | 1 + .../qsc_qasm3/src/tests/declaration/def.rs | 72 ++ compiler/qsc_qasm3/src/tests/expression.rs | 1 + .../src/tests/expression/function_call.rs | 229 ++++++ .../qsc_qasm3/src/tests/expression/ident.rs | 30 +- .../src/tests/statement/gate_call.rs | 134 +++- 17 files changed, 1309 insertions(+), 403 deletions(-) create mode 100644 compiler/qsc_qasm3/src/tests/declaration/def.rs create mode 100644 compiler/qsc_qasm3/src/tests/expression/function_call.rs diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm3/src/ast_builder.rs index 5f65251db3..8cc6f70b74 100644 --- a/compiler/qsc_qasm3/src/ast_builder.rs +++ b/compiler/qsc_qasm3/src/ast_builder.rs @@ -810,7 +810,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 +838,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), } } @@ -1186,8 +1217,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 { @@ -1197,15 +1230,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, @@ -1371,7 +1404,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)>, @@ -1379,6 +1413,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() @@ -1413,15 +1449,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() } @@ -1438,29 +1474,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() diff --git a/compiler/qsc_qasm3/src/compile.rs b/compiler/qsc_qasm3/src/compile.rs index f9d53b0714..1801c3e8dd 100644 --- a/compiler/qsc_qasm3/src/compile.rs +++ b/compiler/qsc_qasm3/src/compile.rs @@ -9,18 +9,17 @@ use crate::ast_builder::{ build_barrier_call, build_binary_expr, build_cast_call, build_cast_call_two_params, build_classical_decl, build_complex_binary_expr, build_complex_from_expr, build_convert_call_expr, build_default_result_array_expr, build_expr_array_expr, - build_gate_call_param_expr, build_gate_decl_lambda, build_if_expr_then_block, - build_if_expr_then_block_else_block, build_if_expr_then_block_else_expr, - build_if_expr_then_expr_else_expr, build_implicit_return_stmt, - build_indexed_assignment_statement, build_lit_bigint_expr, build_lit_bool_expr, - build_lit_complex_expr, build_lit_double_expr, build_lit_int_expr, - build_lit_result_array_expr_from_bitstring, build_lit_result_expr, build_managed_qubit_alloc, - build_math_call_no_params, build_measure_call, build_operation_with_stmts, - build_path_ident_expr, build_range_expr, build_reset_call, build_stmt_semi_from_expr, - build_stmt_wrapped_block_expr, build_top_level_ns_with_item, build_tuple_expr, - build_unary_op_expr, build_unmanaged_qubit_alloc, build_unmanaged_qubit_alloc_array, - build_wrapped_block_expr, is_complex_binop_supported, managed_qubit_alloc_array, - map_qsharp_type_to_ast_ty, + build_gate_call_param_expr, 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_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, build_path_ident_expr, build_range_expr, build_reset_call, + build_stmt_semi_from_expr, build_stmt_wrapped_block_expr, build_top_level_ns_with_item, + build_tuple_expr, build_unary_op_expr, build_unmanaged_qubit_alloc, + build_unmanaged_qubit_alloc_array, build_wrapped_block_expr, is_complex_binop_supported, + managed_qubit_alloc_array, map_qsharp_type_to_ast_ty, }; use crate::oqasm_helpers::{ @@ -2318,8 +2317,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, )) @@ -2472,7 +2473,7 @@ impl QasmCompiler { gate_span, )) } else { - Some(build_gate_decl_lambda( + Some(build_lambda( name.to_string(), cargs, qargs, @@ -2480,6 +2481,8 @@ impl QasmCompiler { name_span, body_span, gate_span, + None, + ast::CallableKind::Operation, )) } } diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index 35870469ca..d4cd79fa3d 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -10,28 +10,29 @@ use qsc_frontend::{compile::SourceMap, error::WithSource}; use crate::{ ast_builder::{ build_arg_pat, build_array_reverse_expr, build_assignment_statement, build_barrier_call, - build_binary_expr, build_cast_call, build_cast_call_two_params, build_classical_decl, - build_complex_from_expr, build_convert_call_expr, build_expr_array_expr, - build_gate_call_param_expr, build_gate_call_with_params_and_callee, + build_binary_expr, build_call_no_params, build_call_with_param, build_call_with_params, + build_cast_call, build_cast_call_two_params, build_classical_decl, build_complex_from_expr, + build_convert_call_expr, build_expr_array_expr, build_for_stmt, build_gate_call_param_expr, + build_gate_call_with_params_and_callee, build_if_expr_then_block, + build_if_expr_then_block_else_block, build_if_expr_then_block_else_expr, build_if_expr_then_expr_else_expr, build_implicit_return_stmt, - build_indexed_assignment_statement, build_lit_bigint_expr, build_lit_bool_expr, - build_lit_complex_expr, build_lit_double_expr, build_lit_int_expr, + build_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_from_exprs, build_math_call_no_params, - build_measure_call, build_operation_with_stmts, build_path_ident_expr, build_reset_call, - build_stmt_semi_from_expr, build_stmt_semi_from_expr_with_span, - build_top_level_ns_with_item, 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, + build_measure_call, build_operation_with_stmts, build_path_ident_expr, 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_item, 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}, runtime::{get_runtime_function_decls, RuntimeFunctions}, semantic::{ ast::{ - BinaryOpExpr, Cast, DiscreteSet, Expr, FunctionCall, GateOperand, GateOperandKind, - IndexElement, IndexExpr, IndexSet, IndexedIdent, LiteralKind, MeasureExpr, TimeUnit, - UnaryOpExpr, + BinaryOpExpr, Cast, DiscreteSet, Expr, GateOperand, GateOperandKind, IndexElement, + IndexExpr, IndexSet, IndexedIdent, LiteralKind, MeasureExpr, TimeUnit, UnaryOpExpr, }, symbols::{IOKind, Symbol, SymbolId, SymbolTable}, types::{ArrayDimensions, Type}, @@ -44,6 +45,16 @@ use crate::{ 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, @@ -322,7 +333,6 @@ impl QasmCompiler { 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::GPhase(stmt) => self.compile_gphase_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), @@ -353,9 +363,9 @@ impl QasmCompiler { let name = &symbol.name; let stmt_span = stmt.span; - let name_span = stmt.name_span; + let name_span = stmt.lhs_span; - let rhs = self.compile_expr(&stmt.rhs)?; + let rhs = self.compile_expr(&stmt.rhs); let stmt = build_assignment_statement(name_span, name, rhs, stmt_span); Some(stmt) @@ -370,7 +380,7 @@ impl QasmCompiler { let indices: Vec<_> = stmt .indices .iter() - .filter_map(|elem| self.compile_index_element(elem)) + .map(|elem| self.compile_index_element(elem)) .collect(); let rhs = self.compile_expr(&stmt.rhs); @@ -383,12 +393,7 @@ impl QasmCompiler { return None; } - if indices.len() != stmt.indices.len() { - return None; - } - - // Use the `?` operator after compiling checking all other errors. - let (rhs, index_expr) = (rhs?, indices[0].clone()); + let index_expr = indices[0].clone(); let stmt = build_indexed_assignment_statement( symbol.span, @@ -402,23 +407,16 @@ impl QasmCompiler { } fn compile_assign_op_stmt(&mut self, stmt: &semast::AssignOpStmt) -> Option { - self.push_unimplemented_error_message("assignment op statements", stmt.span); - None + 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(&mut self, stmt: &semast::BarrierStmt) -> Option { - let qubits: Vec<_> = stmt - .qubits - .iter() - .filter_map(|q| self.compile_gate_operand(q)) - .collect(); - - if stmt.qubits.len() != qubits.len() { - // if any of the qubit arguments failed to compile we can't proceed. - // This can happen if the qubit is not defined. - return None; - } - + // we don't support barrier, but we can insert a runtime function + // which will generate a barrier call in QIR self.runtime.insert(RuntimeFunctions::Barrier); Some(build_barrier_call(stmt.span)) } @@ -467,7 +465,7 @@ impl QasmCompiler { let qsharp_ty = &symbol.qsharp_ty; let expr = decl.init_expr.as_ref(); - let expr = self.compile_expr(expr)?; + let expr = self.compile_expr(expr); let stmt = build_classical_decl( name, is_const, ty_span, decl_span, name_span, qsharp_ty, expr, ); @@ -476,8 +474,45 @@ impl QasmCompiler { } fn compile_def_stmt(&mut self, stmt: &semast::DefStmt) -> Option { - self.push_unimplemented_error_message("def statements", stmt.span); - None + 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 = stmt.return_type.as_ref().map(map_qsharp_type_to_ast_ty); + let kind = if stmt.has_qubit_params { + qsast::CallableKind::Operation + } else { + qsast::CallableKind::Function + }; + + // 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_lambda( + name, + cargs, + vec![], + body, + symbol.span, + stmt.body.span, + stmt.span, + return_type, + kind, + )) } fn compile_def_cal_stmt(&mut self, stmt: &semast::DefCalStmt) -> Option { @@ -496,7 +531,7 @@ impl QasmCompiler { } fn compile_expr_stmt(&mut self, stmt: &semast::ExprStmt) -> Option { - let expr = self.compile_expr(&stmt.expr)?; + let expr = self.compile_expr(&stmt.expr); Some(build_stmt_semi_from_expr_with_span(expr, stmt.span)) } @@ -506,13 +541,68 @@ impl QasmCompiler { } fn compile_for_stmt(&mut self, stmt: &semast::ForStmt) -> Option { - self.push_unimplemented_error_message("for statements", stmt.span); - None + 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 { - self.push_unimplemented_error_message("if statements", stmt.span); - None + 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 { @@ -520,23 +610,19 @@ impl QasmCompiler { if symbol.name == "U" { self.runtime |= RuntimeFunctions::U; } + if symbol.name == "gphase" { + self.runtime |= RuntimeFunctions::Gphase; + } let mut qubits: Vec<_> = stmt .qubits .iter() - .filter_map(|q| self.compile_gate_operand(q)) + .map(|q| self.compile_gate_operand(q)) .collect(); - let args: Vec<_> = stmt - .args - .iter() - .filter_map(|arg| self.compile_expr(arg)) - .collect(); - - if qubits.len() != stmt.qubits.len() || args.len() != stmt.args.len() { - return None; - } + 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() - stmt.quantum_arity as usize); + 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(); @@ -553,7 +639,7 @@ impl QasmCompiler { ); } semast::GateModifierKind::Pow(expr) => { - let exponent_expr = self.compile_expr(expr)?; + let exponent_expr = self.compile_expr(expr); self.runtime |= RuntimeFunctions::Pow; args = build_tuple_expr(vec![exponent_expr, callee, args]); callee = build_path_ident_expr("__Pow__", modifier.span, stmt.span); @@ -569,7 +655,7 @@ impl QasmCompiler { self.push_semantic_error(kind); return None; } - let ctrl = qubits.split_off(qubits.len() - *num_ctrls as usize); + 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( @@ -589,7 +675,7 @@ impl QasmCompiler { self.push_semantic_error(kind); return None; } - let ctrl = qubits.split_off(qubits.len() - *num_ctrls as usize); + 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]); @@ -599,22 +685,10 @@ impl QasmCompiler { } } - // This should never be reached, since semantic analysis during lowering - // makes sure the arities match. - if !qubits.is_empty() { - return None; - } - let expr = build_gate_call_with_params_and_callee(args, callee, stmt.span); Some(build_stmt_semi_from_expr(expr)) } - fn compile_gphase_stmt(&mut self, stmt: &semast::GPhase) -> Option { - self.runtime |= RuntimeFunctions::Gphase; - self.push_unimplemented_error_message("gphase statements", stmt.span); - None - } - fn compile_include_stmt(&mut self, stmt: &semast::IncludeStmt) -> Option { self.push_unimplemented_error_message("include statements", stmt.span); None @@ -650,7 +724,7 @@ impl QasmCompiler { let expr = stmt.init_expr.as_ref(); - let expr = self.compile_expr(expr)?; + let expr = self.compile_expr(expr); let stmt = build_classical_decl( name, is_const, ty_span, decl_span, name_span, qsharp_ty, expr, ); @@ -672,8 +746,52 @@ impl QasmCompiler { &mut self, stmt: &semast::QuantumGateDefinition, ) -> Option { - self.push_unimplemented_error_message("gate decl statements", stmt.span); - None + 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)); + + Some(build_lambda( + name, + cargs, + qargs, + body, + symbol.span, + stmt.body.span, + stmt.span, + None, + qsast::CallableKind::Operation, + )) } fn compile_qubit_decl_stmt(&mut self, stmt: &semast::QubitDeclaration) -> Option { @@ -712,24 +830,82 @@ impl QasmCompiler { } fn compile_reset_stmt(&mut self, stmt: &semast::ResetStmt) -> Option { - let operand = self.compile_gate_operand(&stmt.operand)?; + 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 { - self.push_unimplemented_error_message("return statements", stmt.span); - None + 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 { - self.push_unimplemented_error_message("switch statements", stmt.span); - None + // 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)?; + let condition = self.compile_expr(&stmt.condition); match &*stmt.body.kind { semast::StmtKind::Block(block) => { let block = self.compile_block(block); @@ -752,13 +928,12 @@ impl QasmCompiler { } } - fn compile_expr(&mut self, expr: &semast::Expr) -> Option { + fn compile_expr(&mut self, expr: &semast::Expr) -> qsast::Expr { match expr.kind.as_ref() { - semast::ExprKind::Err => { - // todo: determine if we should push an error here - // Are we going to allow trying to compile a program with semantic errors? - None - } + 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) @@ -770,7 +945,9 @@ impl QasmCompiler { semast::ExprKind::Lit(literal_kind) => { self.compile_literal_expr(literal_kind, expr.span) } - semast::ExprKind::FunctionCall(function_call) => self.compile_call_expr(function_call), + 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), @@ -778,10 +955,10 @@ impl QasmCompiler { } } - fn compile_ident_expr(&mut self, symbol_id: SymbolId) -> Option { + fn compile_ident_expr(&mut self, symbol_id: SymbolId) -> qsast::Expr { let symbol = &self.symbols[symbol_id]; let span = symbol.span; - let expr = match symbol.name.as_str() { + match symbol.name.as_str() { "euler" | "ℇ" => build_math_call_no_params("E", span), "pi" | "π" => build_math_call_no_params("PI", span), "tau" | "τ" => { @@ -797,18 +974,17 @@ impl QasmCompiler { } } _ => build_path_ident_expr(&symbol.name, span, span), - }; - Some(expr) + } } /// 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) -> Option { + fn compile_indexed_ident_expr(&mut self, indexed_ident: &IndexedIdent) -> qsast::Expr { let span = indexed_ident.span; let index: Vec<_> = indexed_ident .indices .iter() - .filter_map(|elem| self.compile_index_element(elem)) + .map(|elem| self.compile_index_element(elem)) .collect(); if index.len() != 1 { @@ -816,62 +992,54 @@ impl QasmCompiler { "multi-dimensional array index expressions", span, ); - return None; + 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); - let expr = qsast::Expr { + qsast::Expr { id: qsast::NodeId::default(), span, kind: Box::new(qsast::ExprKind::Index( Box::new(ident), Box::new(index[0].clone()), )), - }; - Some(expr) + } } - fn compile_unary_op_expr(&mut self, unary: &UnaryOpExpr) -> Option { + 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) -> Option { - let expr = self.compile_expr(expr)?; - Some(build_unary_op_expr(qsast::UnOp::Neg, expr, span)) + fn compile_neg_expr(&mut self, expr: &Expr, span: Span) -> qsast::Expr { + let expr = self.compile_expr(expr); + build_unary_op_expr(qsast::UnOp::Neg, expr, span) } - fn compile_bitwise_not_expr(&mut self, expr: &Expr, span: Span) -> Option { - let expr = self.compile_expr(expr)?; - Some(build_unary_op_expr(qsast::UnOp::NotB, expr, span)) + fn compile_bitwise_not_expr(&mut self, expr: &Expr, span: Span) -> qsast::Expr { + let expr = self.compile_expr(expr); + build_unary_op_expr(qsast::UnOp::NotB, expr, span) } - fn compile_logical_not_expr(&mut self, expr: &Expr, span: Span) -> Option { - let expr = self.compile_expr(expr)?; - Some(build_unary_op_expr(qsast::UnOp::NotL, 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) -> Option { + fn compile_binary_op_expr(&mut self, binary: &BinaryOpExpr) -> qsast::Expr { let lhs = self.compile_expr(&binary.lhs); let rhs = self.compile_expr(&binary.rhs); - let (lhs, rhs) = (lhs?, rhs?); let op = Self::map_bin_op(binary.op); let is_assignment = false; - Some(build_binary_expr( - is_assignment, - op, - lhs, - rhs, - binary.span(), - )) + build_binary_expr(is_assignment, op, lhs, rhs, binary.span()) } - fn compile_literal_expr(&mut self, lit: &LiteralKind, span: Span) -> Option { + fn compile_literal_expr(&mut self, lit: &LiteralKind, span: Span) -> qsast::Expr { match lit { LiteralKind::Array(value) => self.compile_array_literal(value, span), LiteralKind::Bitstring(big_int, width) => { @@ -890,13 +1058,8 @@ impl QasmCompiler { } } - fn compile_call_expr(&mut self, call: &FunctionCall) -> Option { - self.push_unimplemented_error_message("function call expresssions", call.span); - None - } - - fn compile_cast_expr(&mut self, cast: &Cast) -> Option { - let expr = self.compile_expr(&cast.expr)?; + 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) @@ -922,9 +1085,9 @@ impl QasmCompiler { crate::semantic::types::Type::BitArray(ArrayDimensions::One(size), _) => { self.cast_bit_array_expr_to_ty(expr, &cast.expr.ty, &cast.ty, size, cast.span) } - _ => None, + _ => err_expr(cast.span), }; - if cast_expr.is_none() { + if matches!(*cast_expr.kind, qsast::ExprKind::Err) { self.push_unsupported_error_message( format!("casting {} to {} type", cast.expr.ty, cast.ty), cast.span, @@ -933,26 +1096,25 @@ impl QasmCompiler { cast_expr } - fn compile_index_expr(&mut self, index: &IndexExpr) -> Option { + fn compile_index_expr(&mut self, index: &IndexExpr) -> qsast::Expr { self.push_unimplemented_error_message("index expressions", index.span); - None + err_expr(index.span) } - fn compile_paren_expr(&mut self, paren: &Expr, span: Span) -> Option { - let expr = self.compile_expr(paren)?; - Some(wrap_expr_in_parens(expr, span)) + 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) -> Option { + 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 arg = self.compile_gate_operand(&expr.operand); let operand_span = expr.operand.span; - let expr = build_measure_call(arg, name_span, operand_span, call_span); - Some(expr) + build_measure_call(arg, name_span, operand_span, call_span) } - fn compile_gate_operand(&mut self, op: &GateOperand) -> Option { + 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 @@ -961,27 +1123,31 @@ impl QasmCompiler { // catch any other errors let message = "Hardware qubit operands"; self.push_unsupported_error_message(message, op.span); - let ident = build_path_ident_expr(hw.name.clone(), hw.span, op.span); - Some(ident) + build_path_ident_expr(hw.name.clone(), hw.span, op.span) } GateOperandKind::Expr(expr) => self.compile_expr(expr), - GateOperandKind::Err => None, + GateOperandKind::Err => err_expr(op.span), } } - fn compile_index_element(&mut self, elem: &IndexElement) -> Option { + 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) -> Option { - self.push_unimplemented_error_message("discrete set expressions", set.span); - None + 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) -> Option { + 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 { @@ -991,20 +1157,44 @@ impl QasmCompiler { } self.push_unsupported_error_message("index set expressions with multiple values", set.span); - None + 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) -> Option { + fn compile_array_literal(&mut self, _value: &List, span: Span) -> qsast::Expr { self.push_unimplemented_error_message("array literals", span); - None + err_expr(span) } - fn compile_bit_literal(value: bool, span: Span) -> Option { - Some(build_lit_result_expr(value.into(), 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) -> Option { - Some(build_lit_bool_expr(value, span)) + fn compile_bool_literal(value: bool, span: Span) -> qsast::Expr { + build_lit_bool_expr(value, span) } fn compile_duration_literal( @@ -1012,43 +1202,40 @@ impl QasmCompiler { _value: f64, _unit: TimeUnit, span: Span, - ) -> Option { + ) -> qsast::Expr { self.push_unsupported_error_message("timing literals", span); - None + err_expr(span) } - fn compile_bitstring_literal(value: &BigInt, width: u32, span: Span) -> Option { + 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)) }; - Some(build_lit_result_array_expr_from_bitstring(bitstring, span)) + build_lit_result_array_expr_from_bitstring(bitstring, span) } - fn compile_complex_literal(real: f64, imag: f64, span: Span) -> Option { - Some(build_lit_complex_expr( - crate::types::Complex::new(real, imag), - 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) -> Option { - Some(build_lit_double_expr(value, 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) -> Option { - Some(build_lit_int_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) -> Option { - Some(build_lit_bigint_expr(value.clone(), 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) -> Option { + fn compile_string_literal(&mut self, _value: &Rc, span: Span) -> qsast::Expr { self.push_unimplemented_error_message("string literal expressions", span); - None + err_expr(span) } /// Pushes an unsupported error with the supplied message. @@ -1083,33 +1270,33 @@ impl QasmCompiler { expr: &qsast::Expr, expr_ty: &crate::semantic::types::Type, ty: &crate::semantic::types::Type, - _span: Span, - ) -> Option { + span: Span, + ) -> qsast::Expr { assert!(matches!(expr_ty, Type::Angle(..))); // https://openqasm.com/language/types.html#casting-from-angle match ty { Type::Angle(..) => { let msg = "Cast angle to angle"; self.push_unimplemented_error_message(msg, expr.span); - None + err_expr(span) } Type::Bit(..) => { let msg = "Cast angle to bit"; self.push_unimplemented_error_message(msg, expr.span); - None + err_expr(span) } Type::BitArray(..) => { let msg = "Cast angle to bit array"; self.push_unimplemented_error_message(msg, expr.span); - None + err_expr(span) } Type::Bool(..) => { let msg = "Cast angle to bool"; self.push_unimplemented_error_message(msg, expr.span); - None + err_expr(span) } - _ => None, + _ => err_expr(span), } } @@ -1126,32 +1313,27 @@ impl QasmCompiler { expr_ty: &crate::semantic::types::Type, ty: &crate::semantic::types::Type, span: Span, - ) -> Option { + ) -> 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(..) => { - let msg = "Cast bit to angle"; - self.push_unimplemented_error_message(msg, expr.span); - None - } &Type::Bool(..) => { self.runtime |= RuntimeFunctions::ResultAsBool; - Some(build_cast_call( + build_cast_call( RuntimeFunctions::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. - None + err_expr(span) } &Type::Int(w, _) | &Type::UInt(w, _) => { let function = if let Some(width) = w { @@ -1164,11 +1346,9 @@ impl QasmCompiler { RuntimeFunctions::ResultAsInt }; self.runtime |= function; - let expr = build_cast_call(function, expr, name_span, operand_span); - Some(expr) + build_cast_call(function, expr, name_span, operand_span) } - - _ => None, + _ => err_expr(span), } } @@ -1179,7 +1359,7 @@ impl QasmCompiler { ty: &crate::semantic::types::Type, size: u32, span: Span, - ) -> Option { + ) -> qsast::Expr { assert!(matches!( expr_ty, Type::BitArray(ArrayDimensions::One(_), _) @@ -1189,7 +1369,7 @@ impl QasmCompiler { let operand_span = span; if !matches!(ty, Type::Int(..) | Type::UInt(..)) { - return None; + return err_expr(span); } // we know we have a bit array being cast to an int/uint // verfiy widths @@ -1197,15 +1377,14 @@ impl QasmCompiler { if int_width.is_none() || (int_width == Some(size)) { self.runtime |= RuntimeFunctions::ResultArrayAsIntBE; - let expr = build_cast_call( + build_cast_call( RuntimeFunctions::ResultArrayAsIntBE, expr, name_span, operand_span, - ); - Some(expr) + ) } else { - None + err_expr(span) } } @@ -1222,31 +1401,28 @@ impl QasmCompiler { expr_ty: &crate::semantic::types::Type, ty: &crate::semantic::types::Type, span: Span, - ) -> Option { + ) -> qsast::Expr { assert!(matches!(expr_ty, Type::Bool(..))); - let name_span = expr.span; let operand_span = span; match ty { Type::Bit(..) => { self.runtime |= RuntimeFunctions::BoolAsResult; - let expr = build_cast_call( + build_cast_call( RuntimeFunctions::BoolAsResult, expr, name_span, operand_span, - ); - Some(expr) + ) } Type::Float(..) => { self.runtime |= RuntimeFunctions::BoolAsDouble; - let expr = build_cast_call( + build_cast_call( RuntimeFunctions::BoolAsDouble, expr, name_span, operand_span, - ); - Some(expr) + ) } Type::Int(w, _) | Type::UInt(w, _) => { let function = if let Some(width) = w { @@ -1259,10 +1435,9 @@ impl QasmCompiler { RuntimeFunctions::BoolAsInt }; self.runtime |= function; - let expr = build_cast_call(function, expr, name_span, operand_span); - Some(expr) + build_cast_call(function, expr, name_span, operand_span) } - _ => None, + _ => err_expr(span), } } @@ -1272,9 +1447,9 @@ impl QasmCompiler { _expr_ty: &crate::semantic::types::Type, _ty: &crate::semantic::types::Type, span: Span, - ) -> Option { + ) -> qsast::Expr { self.push_unimplemented_error_message("cast complex expressions", span); - None + err_expr(span) } fn cast_duration_expr_to_ty( @@ -1283,9 +1458,9 @@ impl QasmCompiler { _expr_ty: &crate::semantic::types::Type, _ty: &crate::semantic::types::Type, span: Span, - ) -> Option { + ) -> qsast::Expr { self.push_unimplemented_error_message("cast duration expressions", span); - None + err_expr(span) } /// +----------------+-------------------------------------------------------------+ @@ -1303,21 +1478,18 @@ impl QasmCompiler { expr_ty: &crate::semantic::types::Type, ty: &crate::semantic::types::Type, span: Span, - ) -> Option { + ) -> qsast::Expr { assert!(matches!(expr_ty, Type::Float(..))); match ty { - &Type::Complex(..) => { - let expr = build_complex_from_expr(expr); - Some(expr) - } + &Type::Complex(..) => build_complex_from_expr(expr), &Type::Angle(..) => { let msg = "Cast float to angle"; self.push_unimplemented_error_message(msg, expr.span); - None + err_expr(span) } &Type::Int(w, _) | &Type::UInt(w, _) => { let expr = build_math_call_from_exprs("Truncate", vec![expr], span); - let expr = if let Some(w) = w { + if let Some(w) = w { if w > 64 { build_convert_call_expr(expr, "IntAsBigInt") } else { @@ -1325,9 +1497,7 @@ impl QasmCompiler { } } else { expr - }; - - Some(expr) + } } &Type::Bool(..) => { let span = expr.span; @@ -1335,15 +1505,14 @@ impl QasmCompiler { 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); - let coerce_expr = build_if_expr_then_expr_else_expr( + build_if_expr_then_expr_else_expr( cond, build_lit_bool_expr(false, span), build_lit_bool_expr(true, span), span, - ); - Some(coerce_expr) + ) } - _ => None, + _ => err_expr(span), } } @@ -1368,7 +1537,7 @@ impl QasmCompiler { expr_ty: &crate::semantic::types::Type, ty: &crate::semantic::types::Type, span: Span, - ) -> Option { + ) -> qsast::Expr { assert!(matches!(expr_ty, Type::Int(..) | Type::UInt(..))); let name_span = expr.span; let operand_span = span; @@ -1376,35 +1545,30 @@ impl QasmCompiler { Type::BitArray(dims, _) => { self.runtime |= RuntimeFunctions::IntAsResultArrayBE; let ArrayDimensions::One(size) = dims else { - return None; + return err_expr(span); }; let size = i64::from(*size); let size_expr = build_lit_int_expr(size, Span::default()); - let expr = build_cast_call_two_params( + build_cast_call_two_params( RuntimeFunctions::IntAsResultArrayBE, expr, size_expr, name_span, operand_span, - ); - Some(expr) - } - Type::Float(..) => { - let expr = build_convert_call_expr(expr, "IntAsDouble"); - Some(expr) + ) } + 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 { - let expr = build_convert_call_expr(expr, "IntAsBigInt"); - Some(expr) + build_convert_call_expr(expr, "IntAsBigInt") } else { - Some(expr) + expr } } else { - Some(expr) + expr } } Type::Bool(..) => { @@ -1412,33 +1576,30 @@ impl QasmCompiler { 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); - let coerce_expr = build_if_expr_then_expr_else_expr( + build_if_expr_then_expr_else_expr( cond, build_lit_bool_expr(false, expr_span), build_lit_bool_expr(true, expr_span), expr_span, - ); - Some(coerce_expr) + ) } 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); - let coerce_expr = build_if_expr_then_expr_else_expr( + 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, - ); - Some(coerce_expr) + ) } Type::Complex(..) => { let expr = build_convert_call_expr(expr, "IntAsDouble"); - let expr = build_complex_from_expr(expr); - Some(expr) + build_complex_from_expr(expr) } - _ => None, + _ => err_expr(span), } } diff --git a/compiler/qsc_qasm3/src/parser/ast.rs b/compiler/qsc_qasm3/src/parser/ast.rs index cdcd499e30..442a13e7aa 100644 --- a/compiler/qsc_qasm3/src/parser/ast.rs +++ b/compiler/qsc_qasm3/src/parser/ast.rs @@ -1090,6 +1090,7 @@ impl Display for GateCall { #[derive(Clone, Debug)] pub struct GPhase { pub span: Span, + pub gphase_token_span: Span, pub modifiers: List, pub args: List, pub qubits: List, @@ -1099,6 +1100,7 @@ pub struct GPhase { 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())?; diff --git a/compiler/qsc_qasm3/src/parser/mut_visit.rs b/compiler/qsc_qasm3/src/parser/mut_visit.rs index 838523708f..f14543d1ad 100644 --- a/compiler/qsc_qasm3/src/parser/mut_visit.rs +++ b/compiler/qsc_qasm3/src/parser/mut_visit.rs @@ -515,6 +515,7 @@ fn walk_gate_call_stmt(vis: &mut impl MutVisitor, stmt: &mut GateCall) { 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)); diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index 27be880edc..a172af1be2 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -1540,7 +1540,9 @@ fn parse_gphase( 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| { @@ -1563,6 +1565,7 @@ fn parse_gphase( Ok(GPhase { span: s.span(lo), + gphase_token_span, modifiers, args, qubits, diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs index f2b5cb94f0..ca70bdef4f 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/gphase.rs @@ -14,6 +14,7 @@ fn gphase() { Stmt [0-15]: annotations: kind: GPhase [0-15]: + gphase_token_span: [0-6] modifiers: args: Expr [7-13]: BinaryOpExpr: @@ -34,6 +35,7 @@ fn gphase_qubit_ident() { Stmt [0-13]: annotations: kind: GPhase [0-13]: + gphase_token_span: [0-6] modifiers: args: Expr [7-8]: Ident [7-8] "a" @@ -56,6 +58,7 @@ fn gphase_qubit_register() { Stmt [0-15]: annotations: kind: GPhase [0-15]: + gphase_token_span: [0-6] modifiers: args: Expr [7-8]: Ident [7-8] "a" @@ -81,6 +84,7 @@ fn gphase_multiple_qubits() { Stmt [0-19]: annotations: kind: GPhase [0-19]: + gphase_token_span: [0-6] modifiers: args: Expr [7-8]: Ident [7-8] "a" @@ -111,6 +115,7 @@ fn gphase_no_arguments() { Stmt [0-7]: annotations: kind: GPhase [0-7]: + gphase_token_span: [0-6] modifiers: args: duration: @@ -138,6 +143,7 @@ fn gphase_with_parameters() { Stmt [0-15]: annotations: kind: GPhase [0-15]: + gphase_token_span: [0-6] modifiers: args: Expr [7-13]: BinaryOpExpr: @@ -158,6 +164,7 @@ fn gphase_inv_modifier() { Stmt [0-16]: annotations: kind: GPhase [0-16]: + gphase_token_span: [6-12] modifiers: QuantumGateModifier [0-5]: Inv args: @@ -176,6 +183,7 @@ fn gphase_ctrl_inv_modifiers() { Stmt [0-31]: annotations: kind: GPhase [0-31]: + gphase_token_span: [13-19] modifiers: QuantumGateModifier [0-6]: Ctrl None QuantumGateModifier [7-12]: Inv diff --git a/compiler/qsc_qasm3/src/semantic/ast.rs b/compiler/qsc_qasm3/src/semantic/ast.rs index f1bde908fe..af17ba9830 100644 --- a/compiler/qsc_qasm3/src/semantic/ast.rs +++ b/compiler/qsc_qasm3/src/semantic/ast.rs @@ -357,7 +357,6 @@ pub enum StmtKind { For(ForStmt), If(IfStmt), GateCall(GateCall), - GPhase(GPhase), Include(IncludeStmt), InputDeclaration(InputDeclaration), OutputDeclaration(OutputDeclaration), @@ -394,7 +393,6 @@ impl Display for StmtKind { 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::IndexedAssign(assign) => write!(f, "{assign}"), @@ -1043,16 +1041,16 @@ impl Display for QubitArrayDeclaration { #[derive(Clone, Debug)] pub struct QuantumGateDefinition { pub span: Span, - pub ident: Box, - pub params: List, - pub qubits: List, - pub body: Box, + 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, "ident", &self.ident)?; + 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) @@ -1084,8 +1082,8 @@ pub struct GateCall { pub args: List, pub qubits: List, pub duration: Option, + pub classical_arity: u32, pub quantum_arity: u32, - pub quantum_arity_with_modifiers: u32, } impl Display for GateCall { @@ -1094,33 +1092,10 @@ impl Display for GateCall { writeln_list_field(f, "modifiers", &self.modifiers)?; writeln_field(f, "symbol_id", &self.symbol_id)?; writeln_list_field(f, "args", &self.args)?; - writeln_opt_field(f, "duration", self.duration.as_ref())?; writeln_list_field(f, "qubits", &self.qubits)?; - writeln_field(f, "quantum_arity", &self.quantum_arity)?; - write_field( - f, - "quantum_arity_with_modifiers", - &self.quantum_arity_with_modifiers, - ) - } -} - -#[derive(Clone, Debug)] -pub struct GPhase { - pub 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_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) + writeln_field(f, "classical_arity", &self.classical_arity)?; + write_field(f, "quantum_arity", &self.quantum_arity) } } @@ -1216,42 +1191,6 @@ impl Display for OutputDeclaration { } } -#[derive(Clone, Debug)] -pub enum TypedParameter { - Scalar(ScalarTypedParameter), - Quantum(QuantumTypedParameter), - ArrayReference(ArrayTypedParameter), -} - -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, @@ -1321,16 +1260,18 @@ impl WithSpan for ArrayTypedParameter { #[derive(Clone, Debug)] pub struct DefStmt { pub span: Span, - pub name: Box, - pub params: List, - pub body: Box, - pub return_type: Option, + pub symbol_id: SymbolId, + pub has_qubit_params: bool, + pub params: Box<[SymbolId]>, + 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_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_opt_field(f, "return_type", self.return_type.as_ref())?; write_field(f, "body", &self.body) @@ -1472,8 +1413,8 @@ impl Display for ExprKind { #[derive(Clone, Debug)] pub struct AssignStmt { pub span: Span, - pub name_span: Span, pub symbol_id: SymbolId, + pub lhs_span: Span, pub rhs: Expr, } @@ -1481,7 +1422,7 @@ 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, "name_span", &self.name_span)?; + writeln_field(f, "lhs_span", &self.lhs_span)?; write_field(f, "rhs", &self.rhs) } } @@ -1507,6 +1448,7 @@ impl Display for IndexedAssignStmt { pub struct AssignOpStmt { pub span: Span, pub symbol_id: SymbolId, + pub indices: List, pub op: BinOp, pub lhs: Expr, pub rhs: Expr, @@ -1516,6 +1458,7 @@ 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) @@ -1565,14 +1508,14 @@ impl Display for BinaryOpExpr { #[derive(Clone, Debug)] pub struct FunctionCall { pub span: Span, - pub name: Ident, + 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, "name", &self.name)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; write_list_field(f, "args", &self.args) } } diff --git a/compiler/qsc_qasm3/src/semantic/error.rs b/compiler/qsc_qasm3/src/semantic/error.rs index 01b800c9c9..75ee54ff56 100644 --- a/compiler/qsc_qasm3/src/semantic/error.rs +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -50,6 +50,9 @@ pub enum SemanticErrorKind { #[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), @@ -84,6 +87,9 @@ pub enum SemanticErrorKind { #[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), @@ -258,6 +264,7 @@ impl SemanticErrorKind { 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) => { @@ -273,6 +280,9 @@ impl SemanticErrorKind { Self::ArraySizeMustBeNonNegativeConstExpr(span) => { Self::ArraySizeMustBeNonNegativeConstExpr(span + offset) } + Self::DefDeclarationInNonGlobalScope(span) => { + Self::DefDeclarationInNonGlobalScope(span + offset) + } Self::DesignatorTooLarge(span) => Self::DesignatorTooLarge(span + offset), Self::FailedToCompileExpressionList(span) => { Self::FailedToCompileExpressionList(span + offset) diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index 7ce669bee2..afd39c6f5e 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -201,8 +201,8 @@ impl Lowerer { 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), - syntax::StmtKind::GPhase(stmt) => self.lower_gphase(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), @@ -269,15 +269,15 @@ impl Lowerer { &mut self, name: S, symbol: Symbol, - span: Span, ) -> 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(), span); + self.push_redefined_symbol_error(name.as_ref(), symbol_span); symbol_id } }; @@ -325,7 +325,7 @@ impl Lowerer { IOKind::Default, ); - let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol, alias.ident.span()); + 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 @@ -382,7 +382,7 @@ impl Lowerer { semantic::StmtKind::Assign(semantic::AssignStmt { symbol_id, - name_span: ident.span, + lhs_span: ident.span, rhs, span, }) @@ -398,16 +398,15 @@ impl Lowerer { let (symbol_id, symbol) = self.try_get_existing_or_insert_err_symbol(&ident.name, ident.span); - let indexed_ty = &symbol - .ty - .get_indexed_type() - .expect("we only get here if there is at least one index"); + let indexed_ty = + &self.get_indexed_type(&symbol.ty, index_expr.name.span, index_expr.indices.len()); - let indices = index_expr - .indices - .iter() - .map(|index| self.lower_index_element(index)); - let indices = list_from_iter(indices); + let indices = list_from_iter( + index_expr + .indices + .iter() + .map(|index| self.lower_index_element(index)), + ); let rhs = match rhs { syntax::ValueExpr::Expr(expr) => { @@ -443,7 +442,16 @@ impl Lowerer { let (symbol_id, symbol) = self.try_get_existing_or_insert_err_symbol(&ident.name, ident.span); - let ty = &symbol.ty; + 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 = @@ -466,6 +474,7 @@ impl Lowerer { semantic::StmtKind::AssignOp(semantic::AssignOpStmt { span: stmt.span, symbol_id, + indices, op, lhs, rhs, @@ -484,10 +493,7 @@ impl Lowerer { err_expr!(Type::Err, expr.span) } syntax::ExprKind::Err => err_expr!(Type::Err, expr.span), - syntax::ExprKind::FunctionCall(_) => { - self.push_unimplemented_error_message("function call expr", expr.span); - 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), @@ -886,8 +892,7 @@ impl Lowerer { 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, stmt.identifier.span); + let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol); semantic::StmtKind::ClassicalDecl(semantic::ClassicalDeclarationStmt { span: stmt_span, @@ -923,8 +928,7 @@ impl Lowerer { symbol = symbol.with_const_expr(Rc::new(init_expr.clone())); } - let symbol_id = - self.try_insert_or_get_existing_symbol_id(name, symbol, stmt.identifier.span); + 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( @@ -947,8 +951,143 @@ impl Lowerer { } fn lower_def(&mut self, stmt: &syntax::DefStmt) -> semantic::StmtKind { - self.push_unimplemented_error_message("def stmt", stmt.span); - semantic::StmtKind::Err + // 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); + } + + 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); + (Some(ty), Some(qsharp_ty)) + } else { + (None, None) + }; + + // 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(arity, return_ty.map(Box::new)); + + 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); + + let params = stmt + .params + .iter() + .map(|param| { + let symbol = self.lower_typed_parameter(param); + 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 { @@ -1008,7 +1147,7 @@ impl Lowerer { // symbol error. let body = self.lower_stmt(&stmt.body); - // pop the scope where the loop variable lives + // Pop the scope where the loop variable lives. self.symbols.pop_scope(); semantic::StmtKind::For(semantic::ForStmt { @@ -1037,7 +1176,51 @@ impl Lowerer { }) } - fn lower_gate_call(&mut self, stmt: &syntax::GateCall) -> semantic::StmtKind { + fn lower_function_call_expr(&mut self, expr: &syntax::FunctionCall) -> semantic::Expr { + // 1. Lower the args. + let args = expr.args.iter().map(|arg| self.lower_expr(arg)); + let args = list_from_iter(args); + + // 2. 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 return_ty = if let Type::Function(arity, return_ty) = &symbol.ty { + // 2. Check that function classical arity matches the number of classical args. + if *arity as usize != args.len() { + self.push_semantic_error(SemanticErrorKind::InvalidNumberOfClassicalArgs( + *arity as usize, + args.len(), + expr.span, + )); + } + + if let Some(ty) = return_ty { + *ty.clone() + } else { + crate::semantic::types::Type::Err + } + } else { + self.push_semantic_error(SemanticErrorKind::CannotCallNonFunction(symbol.span)); + crate::semantic::types::Type::Err + }; + + 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 @@ -1135,8 +1318,8 @@ impl Lowerer { args, qubits, duration, + classical_arity, quantum_arity, - quantum_arity_with_modifiers, }) // The compiler will be left to do all things that need explicit Q# knowledge. @@ -1146,6 +1329,23 @@ impl Lowerer { // 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, @@ -1206,11 +1406,6 @@ impl Lowerer { Some(n) } - fn lower_gphase(&mut self, stmt: &syntax::GPhase) -> semantic::StmtKind { - self.push_unimplemented_error_message("gphase stmt", stmt.span); - semantic::StmtKind::Err - } - /// 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. @@ -1243,7 +1438,7 @@ impl Lowerer { 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, stmt.ident.span); + let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol); if io_kind == IOKind::Input { return semantic::StmtKind::InputDeclaration(semantic::InputDeclaration { @@ -1287,8 +1482,69 @@ impl Lowerer { } fn lower_gate_def(&mut self, stmt: &syntax::QuantumGateDefinition) -> semantic::StmtKind { - self.push_unimplemented_error_message("gate def stmt", stmt.span); - semantic::StmtKind::Err + // 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 { @@ -1331,7 +1587,7 @@ impl Lowerer { IOKind::Default, ); - let symbol_id = self.try_insert_or_get_existing_symbol_id(name, symbol, stmt.qubit.span); + 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 { @@ -1358,8 +1614,19 @@ impl Lowerer { } fn lower_return(&mut self, stmt: &syntax::ReturnStmt) -> semantic::StmtKind { - self.push_unimplemented_error_message("return stmt", stmt.span); - semantic::StmtKind::Err + let 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); + + semantic::StmtKind::Return(semantic::ReturnStmt { + span: stmt.span, + expr, + }) } fn lower_switch(&mut self, stmt: &syntax::SwitchStmt) -> semantic::StmtKind { @@ -1775,7 +2042,12 @@ impl Lowerer { self.push_unimplemented_error_message("uint array default value", span); None } - Type::Duration(_) | Type::Gate(_, _) | Type::Range | Type::Set | Type::Void => { + Type::Duration(_) + | Type::Gate(_, _) + | Type::Function(..) + | Type::Range + | Type::Set + | Type::Void => { let message = format!("Default values for {ty:?} are unsupported."); self.push_unsupported_error_message(message, span); None @@ -2802,6 +3074,8 @@ fn try_get_qsharp_name_and_implicit_modifiers>( // ch, crx, cry, crz, sdg, and tdg 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)))), diff --git a/compiler/qsc_qasm3/src/semantic/types.rs b/compiler/qsc_qasm3/src/semantic/types.rs index aa2ed00e38..1728165e69 100644 --- a/compiler/qsc_qasm3/src/semantic/types.rs +++ b/compiler/qsc_qasm3/src/semantic/types.rs @@ -43,6 +43,7 @@ pub enum Type { // realistically the sizes could be u3 Gate(u32, u32), + Function(u32, Option>), Range, Set, Void, @@ -74,6 +75,7 @@ impl Display for Type { 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(cargs, return_ty) => write!(f, "Function({cargs}) -> {return_ty:?}"), Type::Range => write!(f, "Range"), Type::Set => write!(f, "Set"), Type::Void => write!(f, "Void"), diff --git a/compiler/qsc_qasm3/src/tests/declaration.rs b/compiler/qsc_qasm3/src/tests/declaration.rs index 6d742ed4e3..1c189a4482 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; 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..6fa2418435 --- /dev/null +++ b/compiler/qsc_qasm3/src/tests/declaration/def.rs @@ -0,0 +1,72 @@ +// 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#" + let 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#" + let square : (Int) -> Int = (x) -> { + 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#" + let square : (Qubit) => Int = (q) => { + 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#" + let square : (Qubit[]) => Int = (qs) => { + return 1; + }; + "#]] + .assert_eq(&qsharp); + Ok(()) +} 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/function_call.rs b/compiler/qsc_qasm3/src/tests/expression/function_call.rs new file mode 100644 index 0000000000..aa74fcc2cf --- /dev/null +++ b/compiler/qsc_qasm3/src/tests/expression/function_call.rs @@ -0,0 +1,229 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::tests::compile_qasm_to_qsharp; +use expect_test::expect; +use miette::Report; + +#[test] +fn 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#" + let empty : () -> Unit = () -> {}; + empty(); + "#]] + .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#" + let square : (Int) -> Int = (x) -> { + 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#" + let sum : (Int, Int) -> Int = (x, y) -> { + 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; + } + + bit p = parity(2); + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + function __ResultAsInt__(input : Result) : Int { + if Microsoft.Quantum.Convert.ResultAsBool(input) { + 1 + } else { + 0 + } + } + let parity : (Qubit[]) => Result = (qs) => { + 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 + }; + }; + mutable p = parity(2); + "#]] + .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#" + let h_wrapper : (Qubit) => Unit = (q) => { + 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#" + let square : (Int) -> Int = (x) -> { + 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:?}")); +} diff --git a/compiler/qsc_qasm3/src/tests/expression/ident.rs b/compiler/qsc_qasm3/src/tests/expression/ident.rs index 86d689015b..f17a9d86f2 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 diff --git a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs index 4bd127bae9..353556c7e6 100644 --- a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs +++ b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs @@ -42,7 +42,18 @@ fn gphase_gate_can_be_called() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![r#""#].assert_eq(&qsharp); + expect![[r#" + operation gphase(theta : Double) : Unit is Adj + Ctl { + body ... { + Exp([], theta, []) + } + adjoint auto; + controlled auto; + controlled adjoint auto; + } + gphase(2.); + "#]] + .assert_eq(&qsharp); Ok(()) } @@ -184,3 +195,124 @@ fn barrier_can_be_called_on_two_qubit() -> miette::Result<(), Vec> { .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#" + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + Rx(2., 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:?}")); +} From 0fd5cd577662b60a70dd42c83b7bcde316d10a97 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 27 Mar 2025 11:37:37 -0700 Subject: [PATCH 68/98] QASM3 - Add implicit casts to function and gate arguments (#2257) Add implicit casts to function and gate arguments. --- compiler/qsc_qasm3/src/semantic/lowerer.rs | 60 ++++++++++---- compiler/qsc_qasm3/src/semantic/types.rs | 12 +-- .../src/tests/expression/function_call.rs | 83 ++++++++++++++++++- .../src/tests/statement/gate_call.rs | 19 +++++ 4 files changed, 151 insertions(+), 23 deletions(-) diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index afd39c6f5e..28337a3e01 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -958,6 +958,17 @@ impl Lowerer { 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()); @@ -973,7 +984,7 @@ impl Lowerer { 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(arity, return_ty.map(Box::new)); + let ty = crate::semantic::types::Type::Function(param_types.into(), return_ty.map(Rc::new)); let has_qubit_params = stmt .params @@ -1177,36 +1188,51 @@ impl Lowerer { } fn lower_function_call_expr(&mut self, expr: &syntax::FunctionCall) -> semantic::Expr { - // 1. Lower the args. - let args = expr.args.iter().map(|arg| self.lower_expr(arg)); - let args = list_from_iter(args); - - // 2. Check that the function name actually refers to a function + // 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 return_ty = if let Type::Function(arity, return_ty) = &symbol.ty { + 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 as usize != args.len() { + if arity != expr.args.len() { self.push_semantic_error(SemanticErrorKind::InvalidNumberOfClassicalArgs( - *arity as usize, - args.len(), + arity, + expr.args.len(), expr.span, )); } - if let Some(ty) = return_ty { - *ty.clone() + if let Some(return_ty) = return_ty { + (params_ty.clone(), (**return_ty).clone()) } else { - crate::semantic::types::Type::Err + (Rc::default(), crate::semantic::types::Type::Err) } } else { self.push_semantic_error(SemanticErrorKind::CannotCallNonFunction(symbol.span)); - crate::semantic::types::Type::Err + (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, @@ -1235,7 +1261,10 @@ impl Lowerer { } // 1.3. Lower the args. - let args = stmt.args.iter().map(|arg| self.lower_expr(arg)); + 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)); @@ -2831,7 +2860,6 @@ impl Lowerer { 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 { diff --git a/compiler/qsc_qasm3/src/semantic/types.rs b/compiler/qsc_qasm3/src/semantic/types.rs index 1728165e69..2187a37442 100644 --- a/compiler/qsc_qasm3/src/semantic/types.rs +++ b/compiler/qsc_qasm3/src/semantic/types.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::cmp::max; +use std::{cmp::max, rc::Rc}; use core::fmt; use std::fmt::{Display, Formatter}; @@ -43,7 +43,7 @@ pub enum Type { // realistically the sizes could be u3 Gate(u32, u32), - Function(u32, Option>), + Function(Rc<[Type]>, Option>), Range, Set, Void, @@ -75,7 +75,9 @@ impl Display for Type { 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(cargs, return_ty) => write!(f, "Function({cargs}) -> {return_ty:?}"), + 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"), @@ -719,7 +721,7 @@ fn try_promote_bitarray_to_int(left_type: &Type, right_type: &Type) -> Option Option miette::Result<(), Ve 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#" + let f : (Int) -> Unit = (x) -> {}; + f(2); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + #[test] fn funcall_with_one_argument_generates_correct_qsharp() -> miette::Result<(), Vec> { let source = r#" @@ -72,7 +88,8 @@ fn funcall_with_qubit_argument() -> miette::Result<(), Vec> { return a ^ b; } - bit p = parity(2); + qubit[2] qs; + bit p = parity(qs); "#; let qsharp = compile_qasm_to_qsharp(source)?; @@ -93,7 +110,8 @@ fn funcall_with_qubit_argument() -> miette::Result<(), Vec> { Zero }; }; - mutable p = parity(2); + let qs = QIR.Runtime.AllocateQubitArray(2); + mutable p = parity(qs); "#]] .assert_eq(&qsharp); Ok(()) @@ -227,3 +245,64 @@ fn classical_decl_initialized_with_incompatible_funcall_errors() { ]"#]] .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#" + 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) + } + let parity : (Result[]) -> Result = (arr) -> { + return 1; + }; + 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:?}")); +} diff --git a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs index 353556c7e6..b2941fdf86 100644 --- a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs +++ b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs @@ -316,3 +316,22 @@ fn rx_gate_with_too_many_angles_generates_error() { ]"#]] .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#" + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + let a = 2.0; + Rx(2., q); + "#]] + .assert_eq(&qsharp); + Ok(()) +} From d74fe1beba67b5e702eee286860229d321f48604 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Thu, 27 Mar 2025 13:42:13 -0700 Subject: [PATCH 69/98] QASM3 - simple index expr (#2258) --- compiler/qsc_qasm3/src/compiler.rs | 12 +++-- .../qsc_qasm3/src/tests/expression/indexed.rs | 49 ++++++++++++++++++- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index d4cd79fa3d..b453d9c568 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -1096,9 +1096,15 @@ impl QasmCompiler { cast_expr } - fn compile_index_expr(&mut self, index: &IndexExpr) -> qsast::Expr { - self.push_unimplemented_error_message("index expressions", index.span); - err_expr(index.span) + 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 { diff --git a/compiler/qsc_qasm3/src/tests/expression/indexed.rs b/compiler/qsc_qasm3/src/tests/expression/indexed.rs index 6ca7873af5..c76ee6b0ff 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![r#"Cannot cast expression of type Bit(false) to type Float(None, false)"#] .assert_eq(&errors[0].to_string()); } @@ -99,7 +99,7 @@ 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]; "; @@ -113,3 +113,48 @@ fn bool_indexed_ty_is_same_as_element_ty() -> miette::Result<(), Vec> { .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(()) +} From 9ebe1f086a97e960e3938aa48f933521706f1bfb Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Mon, 31 Mar 2025 14:29:31 -0700 Subject: [PATCH 70/98] Add angle support to the new compiler (#2267) This PR adds angle support to the new compiler. --------- Co-authored-by: Ian Davis --- Cargo.lock | 2 + compiler/qsc/src/codegen.rs | 1 + compiler/qsc_codegen/src/qsharp.rs | 16 + compiler/qsc_qasm3/Cargo.toml | 2 + compiler/qsc_qasm3/src/ast_builder.rs | 160 ++++- compiler/qsc_qasm3/src/compile.rs | 33 +- compiler/qsc_qasm3/src/compiler.rs | 342 ++++++--- compiler/qsc_qasm3/src/lib.rs | 3 +- compiler/qsc_qasm3/src/runtime.rs | 93 +-- compiler/qsc_qasm3/src/semantic/ast.rs | 13 +- .../qsc_qasm3/src/semantic/ast/const_eval.rs | 246 +++++-- compiler/qsc_qasm3/src/semantic/error.rs | 6 + compiler/qsc_qasm3/src/semantic/lowerer.rs | 194 +++-- compiler/qsc_qasm3/src/semantic/tests.rs | 48 +- .../qsc_qasm3/src/semantic/tests/decls.rs | 2 +- .../src/semantic/tests/decls/angle.rs | 146 +++- .../expression/implicit_cast_from_angle.rs | 111 ++- .../expression/implicit_cast_from_bit.rs | 66 +- .../expression/implicit_cast_from_float.rs | 6 +- compiler/qsc_qasm3/src/semantic/types.rs | 32 +- compiler/qsc_qasm3/src/stdlib.rs | 7 + .../qsc_qasm3/src/stdlib/QasmStd/qsharp.json | 9 + .../src/stdlib/QasmStd/src/QasmStd/Angle.qs | 273 +++++++ .../src/stdlib/QasmStd/src/QasmStd/Convert.qs | 100 +++ .../stdlib/QasmStd/src/QasmStd/Intrinsic.qs | 150 ++++ compiler/qsc_qasm3/src/{ => stdlib}/angle.rs | 154 +++- .../qsc_qasm3/src/{ => stdlib}/angle/tests.rs | 101 ++- compiler/qsc_qasm3/src/stdlib/compile.rs | 149 ++++ compiler/qsc_qasm3/src/tests.rs | 35 +- compiler/qsc_qasm3/src/tests/declaration.rs | 1 - .../qsc_qasm3/src/tests/declaration/gate.rs | 28 +- .../tests/declaration/io/explicit_input.rs | 144 ++-- .../tests/declaration/io/explicit_output.rs | 246 ++++--- .../tests/declaration/io/implicit_output.rs | 244 ++++--- .../src/tests/declaration/unsigned_integer.rs | 4 +- .../qsc_qasm3/src/tests/expression/binary.rs | 1 + .../src/tests/expression/binary/angle.rs | 270 +++++++ .../binary/arithmetic_conversions.rs | 54 +- .../src/tests/expression/binary/comparison.rs | 69 +- .../src/tests/expression/binary/ident.rs | 27 +- .../binary/literal/multiplication.rs | 10 +- .../qsc_qasm3/src/tests/expression/bits.rs | 42 +- .../src/tests/expression/function_call.rs | 45 +- .../qsc_qasm3/src/tests/expression/ident.rs | 3 + .../expression/implicit_cast_from_bit.rs | 109 +-- .../expression/implicit_cast_from_bitarray.rs | 48 +- .../expression/implicit_cast_from_bool.rs | 139 ++-- .../expression/implicit_cast_from_float.rs | 94 +-- .../expression/implicit_cast_from_int.rs | 99 +-- .../qsc_qasm3/src/tests/expression/indexed.rs | 51 +- .../qsc_qasm3/src/tests/expression/unary.rs | 103 ++- compiler/qsc_qasm3/src/tests/output.rs | 130 ++-- compiler/qsc_qasm3/src/tests/scopes.rs | 11 +- .../src/tests/statement/annotation.rs | 3 + .../src/tests/statement/const_eval.rs | 674 ++++++++++++------ .../qsc_qasm3/src/tests/statement/for_loop.rs | 60 +- .../src/tests/statement/gate_call.rs | 95 ++- .../qsc_qasm3/src/tests/statement/if_stmt.rs | 16 +- .../statement/implicit_modified_gate_call.rs | 206 ++++-- .../qsc_qasm3/src/tests/statement/include.rs | 10 +- .../qsc_qasm3/src/tests/statement/measure.rs | 23 +- .../src/tests/statement/modified_gate_call.rs | 141 ++-- .../qsc_qasm3/src/tests/statement/reset.rs | 11 +- .../qsc_qasm3/src/tests/statement/switch.rs | 103 +-- .../src/tests/statement/while_loop.rs | 8 +- compiler/qsc_qasm3/src/types.rs | 2 + pip/src/interop.rs | 14 +- 67 files changed, 3987 insertions(+), 1851 deletions(-) create mode 100644 compiler/qsc_qasm3/src/stdlib.rs create mode 100644 compiler/qsc_qasm3/src/stdlib/QasmStd/qsharp.json create mode 100644 compiler/qsc_qasm3/src/stdlib/QasmStd/src/QasmStd/Angle.qs create mode 100644 compiler/qsc_qasm3/src/stdlib/QasmStd/src/QasmStd/Convert.qs create mode 100644 compiler/qsc_qasm3/src/stdlib/QasmStd/src/QasmStd/Intrinsic.qs rename compiler/qsc_qasm3/src/{ => stdlib}/angle.rs (57%) rename compiler/qsc_qasm3/src/{ => stdlib}/angle/tests.rs (59%) create mode 100644 compiler/qsc_qasm3/src/stdlib/compile.rs create mode 100644 compiler/qsc_qasm3/src/tests/expression/binary/angle.rs diff --git a/Cargo.lock b/Cargo.lock index c202b2f70b..1e8fde3103 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1491,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_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 0884792b61..8f95afc821 100644 --- a/compiler/qsc_qasm3/Cargo.toml +++ b/compiler/qsc_qasm3/Cargo.toml @@ -17,7 +17,9 @@ 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 } diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm3/src/ast_builder.rs index 8cc6f70b74..f69686fe17 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, + 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, 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, @@ -926,6 +987,98 @@ pub(crate) fn build_expr_wrapped_block_expr(expr: Expr) -> Block { } } +pub(crate) fn build_qasm_import_decl() -> 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() -> Stmt { + 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); + let item = Item { + id: NodeId::default(), + span: Span::default(), + kind: Box::new(ItemKind::ImportOrExport(decl)), + doc: "".into(), + attrs: Box::new([]), + }; + Stmt { + kind: Box::new(StmtKind::Item(Box::new(item))), + span: Span::default(), + id: NodeId::default(), + } +} + +pub(crate) fn build_qasm_import_decl_convert() -> Stmt { + 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); + let item = Item { + id: NodeId::default(), + span: Span::default(), + kind: Box::new(ItemKind::ImportOrExport(decl)), + doc: "".into(), + attrs: Box::new([]), + }; + Stmt { + kind: Box::new(StmtKind::Item(Box::new(item))), + span: Span::default(), + id: NodeId::default(), + } +} + +pub(crate) fn build_qasm_import_decl_intrinsic() -> Stmt { + 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); + let item = Item { + id: NodeId::default(), + span: Span::default(), + kind: Box::new(ItemKind::ImportOrExport(decl)), + doc: "".into(), + attrs: Box::new([]), + }; + Stmt { + kind: Box::new(StmtKind::Item(Box::new(item))), + span: Span::default(), + id: NodeId::default(), + } +} + pub(crate) fn build_classical_decl( name: S, is_const: bool, @@ -1154,6 +1307,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"), diff --git a/compiler/qsc_qasm3/src/compile.rs b/compiler/qsc_qasm3/src/compile.rs index 1801c3e8dd..d7d0c36824 100644 --- a/compiler/qsc_qasm3/src/compile.rs +++ b/compiler/qsc_qasm3/src/compile.rs @@ -6,20 +6,21 @@ 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_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_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, build_path_ident_expr, build_range_expr, build_reset_call, - build_stmt_semi_from_expr, build_stmt_wrapped_block_expr, build_top_level_ns_with_item, - build_tuple_expr, build_unary_op_expr, build_unmanaged_qubit_alloc, - build_unmanaged_qubit_alloc_array, build_wrapped_block_expr, is_complex_binop_supported, - managed_qubit_alloc_array, map_qsharp_type_to_ast_ty, + 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_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, + build_path_ident_expr, build_range_expr, build_reset_call, build_stmt_semi_from_expr, + build_stmt_wrapped_block_expr, build_top_level_ns_with_item, build_tuple_expr, + build_unary_op_expr, build_unmanaged_qubit_alloc, build_unmanaged_qubit_alloc_array, + build_wrapped_block_expr, is_complex_binop_supported, managed_qubit_alloc_array, + map_qsharp_type_to_ast_ty, }; use crate::oqasm_helpers::{ @@ -3785,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, diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index b453d9c568..d3f36066cc 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use core::f64; use std::{path::Path, rc::Rc}; use num_bigint::BigInt; @@ -11,31 +12,31 @@ use crate::{ ast_builder::{ build_arg_pat, build_array_reverse_expr, build_assignment_statement, build_barrier_call, build_binary_expr, build_call_no_params, build_call_with_param, build_call_with_params, - build_cast_call, build_cast_call_two_params, build_classical_decl, build_complex_from_expr, + build_cast_call_by_name, build_classical_decl, build_complex_from_expr, build_convert_call_expr, build_expr_array_expr, build_for_stmt, build_gate_call_param_expr, - build_gate_call_with_params_and_callee, 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_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_from_exprs, build_math_call_no_params, - build_measure_call, build_operation_with_stmts, build_path_ident_expr, build_range_expr, - build_reset_call, build_return_expr, build_return_unit, build_stmt_semi_from_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_lambda, + 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_qasm_import_decl, 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_item, 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}, - runtime::{get_runtime_function_decls, RuntimeFunctions}, semantic::{ ast::{ BinaryOpExpr, Cast, DiscreteSet, Expr, GateOperand, GateOperandKind, IndexElement, IndexExpr, IndexSet, IndexedIdent, LiteralKind, MeasureExpr, TimeUnit, UnaryOpExpr, }, symbols::{IOKind, Symbol, SymbolId, SymbolTable}, - types::{ArrayDimensions, Type}, + types::{promote_types, ArrayDimensions, Type}, SemanticErrorKind, }, CompilerConfig, OperationSignature, OutputSemantics, ProgramType, QasmCompileUnit, @@ -67,7 +68,6 @@ where source_map: res.source_map, config, stmts: vec![], - runtime: RuntimeFunctions::empty(), symbols: res.symbols, errors: res.errors, }; @@ -85,9 +85,6 @@ pub struct QasmCompiler { pub config: CompilerConfig, /// The compiled statments accumulated during compilation. pub stmts: Vec, - /// The runtime functions that need to be included at the end of - /// compilation - pub runtime: RuntimeFunctions, pub symbols: SymbolTable, pub errors: Vec>, } @@ -97,8 +94,8 @@ impl QasmCompiler { /// source file and build the appropriate package based on the /// configuration. pub fn compile(mut self, program: &crate::semantic::ast::Program) -> QasmCompileUnit { + self.append_runtime_import_decls(); self.compile_stmts(&program.statements); - self.prepend_runtime_decls(); let program_ty = self.config.program_ty.clone(); let (package, signature) = match program_ty { ProgramType::File => self.build_file(), @@ -290,16 +287,11 @@ impl QasmCompiler { ) } - /// Prepends the runtime declarations to the beginning of the statements. - /// Any runtime functions that are required by the compiled code are set - /// in the `self.runtime` field during compilation. - /// - /// We could declare these as top level functions when compiling to - /// `ProgramType::File`, but prepending them to the statements is the - /// most flexible approach. - fn prepend_runtime_decls(&mut self) { - let mut runtime = get_runtime_function_decls(self.runtime); - self.stmts.splice(0..0, runtime.drain(..)); + /// 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]) { @@ -317,7 +309,7 @@ impl QasmCompiler { 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::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::CalibrationGrammar(stmt) => { @@ -407,17 +399,77 @@ impl QasmCompiler { } 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 angles correctly. + if matches!(&stmt.lhs.ty, Type::Angle(..)) { + 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(&mut self, stmt: &semast::BarrierStmt) -> Option { - // we don't support barrier, but we can insert a runtime function - // which will generate a barrier call in QIR - self.runtime.insert(RuntimeFunctions::Barrier); + fn compile_barrier_stmt(stmt: &semast::BarrierStmt) -> Option { Some(build_barrier_call(stmt.span)) } @@ -607,12 +659,6 @@ impl QasmCompiler { fn compile_gate_call_stmt(&mut self, stmt: &semast::GateCall) -> Option { let symbol = self.symbols[stmt.symbol_id].clone(); - if symbol.name == "U" { - self.runtime |= RuntimeFunctions::U; - } - if symbol.name == "gphase" { - self.runtime |= RuntimeFunctions::Gphase; - } let mut qubits: Vec<_> = stmt .qubits .iter() @@ -640,7 +686,6 @@ impl QasmCompiler { } semast::GateModifierKind::Pow(expr) => { let exponent_expr = self.compile_expr(expr); - self.runtime |= RuntimeFunctions::Pow; args = build_tuple_expr(vec![exponent_expr, callee, args]); callee = build_path_ident_expr("__Pow__", modifier.span, stmt.span); } @@ -1017,13 +1062,23 @@ impl QasmCompiler { } } fn compile_neg_expr(&mut self, expr: &Expr, span: Span) -> qsast::Expr { - let expr = self.compile_expr(expr); - build_unary_op_expr(qsast::UnOp::Neg, expr, span) + 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 expr = self.compile_expr(expr); - build_unary_op_expr(qsast::UnOp::NotB, expr, span) + 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 { @@ -1032,15 +1087,84 @@ impl QasmCompiler { } 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); - let op = Self::map_bin_op(binary.op); + + 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); + } + 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_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) @@ -1062,28 +1186,28 @@ impl QasmCompiler { 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) + 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) + 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) + 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) + 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) + 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) + Self::cast_bit_array_expr_to_ty(expr, &cast.expr.ty, &cast.ty, size, cast.span) } _ => err_expr(cast.span), }; @@ -1272,8 +1396,7 @@ impl QasmCompiler { /// | angle | Yes | No | No | No | - | Yes | No | No | /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ fn cast_angle_expr_to_ty( - &mut self, - expr: &qsast::Expr, + expr: qsast::Expr, expr_ty: &crate::semantic::types::Type, ty: &crate::semantic::types::Type, span: Span, @@ -1282,26 +1405,29 @@ impl QasmCompiler { // https://openqasm.com/language/types.html#casting-from-angle match ty { Type::Angle(..) => { - let msg = "Cast angle to angle"; - self.push_unimplemented_error_message(msg, expr.span); - err_expr(span) + // 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(..) => { - let msg = "Cast angle to bit"; - self.push_unimplemented_error_message(msg, expr.span); - err_expr(span) + build_call_with_param("__AngleAsResult__", &[], expr, span, span, span) } Type::BitArray(..) => { - let msg = "Cast angle to bit array"; - self.push_unimplemented_error_message(msg, expr.span); - err_expr(span) - } - Type::Bool(..) => { - let msg = "Cast angle to bool"; - self.push_unimplemented_error_message(msg, expr.span); - err_expr(span) + build_call_with_param("__AngleAsResultArray__", &[], expr, span, span, span) } - + Type::Bool(..) => build_call_with_param("__AngleAsBool__", &[], expr, span, span, span), _ => err_expr(span), } } @@ -1314,7 +1440,6 @@ impl QasmCompiler { /// | bit | Yes | Yes | Yes | No | Yes | - | No | No | /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ fn cast_bit_expr_to_ty( - &mut self, expr: qsast::Expr, expr_ty: &crate::semantic::types::Type, ty: &crate::semantic::types::Type, @@ -1326,14 +1451,11 @@ impl QasmCompiler { 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(..) => { - self.runtime |= RuntimeFunctions::ResultAsBool; - build_cast_call( - RuntimeFunctions::ResultAsBool, - expr, - name_span, - operand_span, - ) + build_cast_call_by_name("__ResultAsBool__", expr, name_span, operand_span) } &Type::Float(..) => { // The spec says that this cast isn't supported, but it @@ -1344,22 +1466,21 @@ impl QasmCompiler { &Type::Int(w, _) | &Type::UInt(w, _) => { let function = if let Some(width) = w { if width > 64 { - RuntimeFunctions::ResultAsBigInt + "__ResultAsBigInt__" } else { - RuntimeFunctions::ResultAsInt + "__ResultAsInt__" } } else { - RuntimeFunctions::ResultAsInt + "__ResultAsInt__" }; - self.runtime |= function; - build_cast_call(function, expr, name_span, operand_span) + + build_cast_call_by_name(function, expr, name_span, operand_span) } _ => err_expr(span), } } fn cast_bit_array_expr_to_ty( - &mut self, expr: qsast::Expr, expr_ty: &crate::semantic::types::Type, ty: &crate::semantic::types::Type, @@ -1382,13 +1503,7 @@ impl QasmCompiler { let int_width = ty.width(); if int_width.is_none() || (int_width == Some(size)) { - self.runtime |= RuntimeFunctions::ResultArrayAsIntBE; - build_cast_call( - RuntimeFunctions::ResultArrayAsIntBE, - expr, - name_span, - operand_span, - ) + build_cast_call_by_name("__ResultArrayAsIntBE__", expr, name_span, operand_span) } else { err_expr(span) } @@ -1402,7 +1517,6 @@ impl QasmCompiler { /// | bool | - | Yes | Yes | Yes | No | Yes | No | No | /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ fn cast_bool_expr_to_ty( - &mut self, expr: qsast::Expr, expr_ty: &crate::semantic::types::Type, ty: &crate::semantic::types::Type, @@ -1413,35 +1527,22 @@ impl QasmCompiler { let operand_span = span; match ty { Type::Bit(..) => { - self.runtime |= RuntimeFunctions::BoolAsResult; - build_cast_call( - RuntimeFunctions::BoolAsResult, - expr, - name_span, - operand_span, - ) + build_cast_call_by_name("__BoolAsResult__", expr, name_span, operand_span) } Type::Float(..) => { - self.runtime |= RuntimeFunctions::BoolAsDouble; - build_cast_call( - RuntimeFunctions::BoolAsDouble, - expr, - name_span, - operand_span, - ) + 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 { - RuntimeFunctions::BoolAsBigInt + "__BoolAsBigInt__" } else { - RuntimeFunctions::BoolAsInt + "__BoolAsInt__" } } else { - RuntimeFunctions::BoolAsInt + "__BoolAsInt__" }; - self.runtime |= function; - build_cast_call(function, expr, name_span, operand_span) + build_cast_call_by_name(function, expr, name_span, operand_span) } _ => err_expr(span), } @@ -1479,19 +1580,26 @@ impl QasmCompiler { /// /// Additional cast to complex fn cast_float_expr_to_ty( - &mut self, 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(..) => { - let msg = "Cast float to angle"; - self.push_unimplemented_error_message(msg, expr.span); - err_expr(span) + &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); @@ -1505,6 +1613,8 @@ impl QasmCompiler { 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); @@ -1538,7 +1648,6 @@ impl QasmCompiler { /// `OpenQASM` support this will need to be fleshed out. #[allow(clippy::too_many_lines)] fn cast_int_expr_to_ty( - &mut self, expr: qsast::Expr, expr_ty: &crate::semantic::types::Type, ty: &crate::semantic::types::Type, @@ -1549,15 +1658,14 @@ impl QasmCompiler { let operand_span = span; match ty { Type::BitArray(dims, _) => { - self.runtime |= RuntimeFunctions::IntAsResultArrayBE; 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_cast_call_two_params( - RuntimeFunctions::IntAsResultArrayBE, + build_global_call_with_two_params( + "__IntAsResultArrayBE__", expr, size_expr, name_span, diff --git a/compiler/qsc_qasm3/src/lib.rs b/compiler/qsc_qasm3/src/lib.rs index 35f30b2622..72890f4e92 100644 --- a/compiler/qsc_qasm3/src/lib.rs +++ b/compiler/qsc_qasm3/src/lib.rs @@ -4,12 +4,13 @@ // while we work through the conversion, allow dead code to avoid warnings #![allow(dead_code)] -mod angle; 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; diff --git a/compiler/qsc_qasm3/src/runtime.rs b/compiler/qsc_qasm3/src/runtime.rs index 0f1b0a4541..d1bf332285 100644 --- a/compiler/qsc_qasm3/src/runtime.rs +++ b/compiler/qsc_qasm3/src/runtime.rs @@ -17,38 +17,6 @@ use bitflags::bitflags; use qsc_ast::ast::{Stmt, TopLevelNode}; use qsc_data_structures::language_features::LanguageFeatures; -/// Implement the `gphase` operation for QASM3. -const GPHASE_GATE: &str = " -operation gphase(theta : Double) : Unit is Adj + Ctl { - body ... { - Exp([], theta, []) - } - adjoint auto; - controlled auto; - controlled adjoint auto; -} -"; - -/// Implement the `U` operation for QASM3. -/// We need to apply a global phase, but rather than require gphase to be called, -/// we can use the `R` gate since we have a qubit parameter (though it is ignored). -/// `R(PauliI, 4. * PI() - (lambda + phi + theta), qubit);` -/// Since `U` is periodic to `2pi`, we can use the following: -/// `R(PauliI, -(lambda + phi + theta), qubit);` -const U_GATE: &str = " -operation U(theta : Double, phi : Double, lambda : Double, qubit : Qubit) : Unit is Adj + Ctl { - body ... { - Rz(lambda, qubit); - Ry(theta, qubit); - Rz(phi, qubit); - R(PauliI, -lambda - phi - theta, qubit); - } - adjoint auto; - controlled auto; - controlled adjoint auto; -} -"; - /// 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 { @@ -170,20 +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 Gphase = 0b100_000_000_000; - const U = 0b1_000_000_000_000; + const IntAsResultArrayBE = 0b10_0000_0000 | 0b100; + const ResultArrayAsIntBE = 0b100_0000_0000; + const GATES = 0b1000_0000_0000; } } @@ -237,20 +204,12 @@ pub(crate) fn get_result_array_as_int_be_decl() -> Stmt { parse_stmt(RESULT_ARRAY_AS_INT_BE) } -pub(crate) fn get_gphase_decl() -> Stmt { - parse_stmt(GPHASE_GATE) -} - -pub(crate) fn get_u_decl() -> Stmt { - parse_stmt(U_GATE) -} - /// 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 {:?}", @@ -258,12 +217,28 @@ 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![]; @@ -311,13 +286,5 @@ pub(crate) fn get_runtime_function_decls(runtime: RuntimeFunctions) -> Vec let stmt = crate::runtime::get_result_array_as_int_be_decl(); stmts.push(stmt); } - if runtime.contains(RuntimeFunctions::Gphase) { - let stmt = crate::runtime::get_gphase_decl(); - stmts.push(stmt); - } - if runtime.contains(RuntimeFunctions::U) { - let stmt = crate::runtime::get_u_decl(); - stmts.push(stmt); - } stmts } diff --git a/compiler/qsc_qasm3/src/semantic/ast.rs b/compiler/qsc_qasm3/src/semantic/ast.rs index af17ba9830..625a7e91e5 100644 --- a/compiler/qsc_qasm3/src/semantic/ast.rs +++ b/compiler/qsc_qasm3/src/semantic/ast.rs @@ -21,6 +21,7 @@ use crate::{ List, }, semantic::symbols::SymbolId, + stdlib::angle::Angle, }; use crate::parser::ast as syntax; @@ -1552,6 +1553,7 @@ impl Display for IndexExpr { #[derive(Clone, Debug)] pub enum LiteralKind { + Angle(Angle), Array(List), Bitstring(BigInt, u32), Bool(bool), @@ -1572,6 +1574,7 @@ impl Display for LiteralKind { 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:?})"), @@ -1579,7 +1582,6 @@ impl Display for LiteralKind { 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:?})"), @@ -1639,6 +1641,15 @@ pub enum IndexElement { 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 { diff --git a/compiler/qsc_qasm3/src/semantic/ast/const_eval.rs b/compiler/qsc_qasm3/src/semantic/ast/const_eval.rs index 15f4ca2344..a899186e55 100644 --- a/compiler/qsc_qasm3/src/semantic/ast/const_eval.rs +++ b/compiler/qsc_qasm3/src/semantic/ast/const_eval.rs @@ -6,15 +6,17 @@ //! and sizes of arrays. Therefore, those are the only const evaluation //! paths that are implemented. -use std::f64; - use super::{ BinOp, BinaryOpExpr, Cast, Expr, ExprKind, FunctionCall, IndexExpr, IndexedIdent, LiteralKind, SymbolId, UnaryOp, UnaryOpExpr, }; -use crate::semantic::{ - symbols::SymbolTable, - types::{ArrayDimensions, Type}, +use crate::stdlib::angle; +use crate::{ + oqasm_helpers::safe_i64_to_f64, + semantic::{ + symbols::SymbolTable, + types::{ArrayDimensions, Type}, + }, }; use num_bigint::BigInt; @@ -74,7 +76,7 @@ macro_rules! rewrap_lit { impl UnaryOpExpr { fn const_eval(&self, symbols: &SymbolTable) -> Option { - use LiteralKind::{Bit, Bitstring, Bool, Float, Int}; + use LiteralKind::{Angle, Bit, Bitstring, Bool, Float, Int}; let operand_ty = &self.expr.ty; let lit = self.expr.const_eval(symbols)?; @@ -82,7 +84,7 @@ impl UnaryOpExpr { 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, Float(val), Float(f64::consts::TAU - val)), + Type::Angle(..) => rewrap_lit!(lit, Angle(val), Angle(-val)), _ => None, }, UnaryOp::NotB => match operand_ty { @@ -90,6 +92,7 @@ impl UnaryOpExpr { 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), { @@ -136,7 +139,7 @@ fn assert_binary_op_ty_invariant(op: BinOp, lhs_ty: &Type, rhs_ty: &Type) { impl BinaryOpExpr { #[allow(clippy::too_many_lines)] fn const_eval(&self, symbols: &SymbolTable) -> Option { - use LiteralKind::{Bit, Bitstring, Bool, Float, Int}; + 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(symbols)?; @@ -147,29 +150,70 @@ impl BinaryOpExpr { // Bit Shifts BinOp::Shl => match lhs_ty { Type::UInt(Some(size), _) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), { + if rhs < 0 { + return None; + } let mask = (1 << size) - 1; Int((lhs << rhs) & mask) }), - Type::UInt(..) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs << rhs)), + Type::UInt(..) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), { + if rhs < 0 { + return None; + } + Int(lhs << rhs) + }), + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Int(rhs)), { + if rhs < 0 { + return None; + } + Angle(lhs << rhs) + }) + } Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Int(rhs)), { + if rhs < 0 { + return None; + } // 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, rhs), (Bitstring(lhs, size), Int(rhs)), { + if rhs < 0 { + return None; + } let mask = BigInt::from((1 << size) - 1); Bitstring((lhs << rhs) & mask, size) }), _ => None, }, BinOp::Shr => match lhs_ty { - Type::UInt(..) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs >> rhs)), + Type::UInt(..) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), { + if rhs < 0 { + return None; + } + Int(lhs >> rhs) + }), + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Int(rhs)), { + if rhs < 0 { + return None; + } + Angle(lhs >> rhs) + }) + } Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Int(rhs)), { + if rhs < 0 { + return None; + } // 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, rhs), (Bitstring(lhs, size), Int(rhs)), { + if rhs < 0 { + return None; + } Bitstring(lhs >> rhs, size) }), _ => None, @@ -178,6 +222,9 @@ impl BinaryOpExpr { // 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), @@ -188,6 +235,9 @@ impl BinaryOpExpr { }, 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), @@ -198,6 +248,9 @@ impl BinaryOpExpr { }, 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), @@ -222,13 +275,16 @@ impl BinaryOpExpr { Type::Int(..) | Type::UInt(..) => { rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Bool(lhs == rhs)) } - Type::Float(..) | Type::Angle(..) => { + 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), @@ -241,13 +297,16 @@ impl BinaryOpExpr { Type::Int(..) | Type::UInt(..) => { rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Bool(lhs != rhs)) } - Type::Float(..) | Type::Angle(..) => { + 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), @@ -260,9 +319,12 @@ impl BinaryOpExpr { Type::Int(..) | Type::UInt(..) => { rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Bool(lhs > rhs)) } - Type::Float(..) | Type::Angle(..) => { + 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!( @@ -276,9 +338,12 @@ impl BinaryOpExpr { Type::Int(..) | Type::UInt(..) => { rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Bool(lhs >= rhs)) } - Type::Float(..) | Type::Angle(..) => { + 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), @@ -291,9 +356,12 @@ impl BinaryOpExpr { Type::Int(..) | Type::UInt(..) => { rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Bool(lhs < rhs)) } - Type::Float(..) | Type::Angle(..) => { + 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!( @@ -307,9 +375,12 @@ impl BinaryOpExpr { Type::Int(..) | Type::UInt(..) => { rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Bool(lhs <= rhs)) } - Type::Float(..) | Type::Angle(..) => { + 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), @@ -327,13 +398,9 @@ impl BinaryOpExpr { Type::Float(..) => { rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Float(lhs + rhs)) } - Type::Angle(..) => rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), { - let mut ans = lhs + rhs; - if ans >= f64::consts::TAU { - ans -= f64::consts::TAU; - } - Float(ans) - }), + Type::Angle(..) => { + rewrap_lit!((lhs, rhs), (Angle(lhs), Angle(rhs)), Angle(lhs + rhs)) + } _ => None, }, BinOp::Sub => match lhs_ty { @@ -343,42 +410,37 @@ impl BinaryOpExpr { Type::Float(..) => { rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Float(lhs - rhs)) } - Type::Angle(..) => rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), { - let mut ans = lhs - rhs; - if ans < 0.0 { - ans += f64::consts::TAU; - } - Float(ans) - }), + 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), Float(rhs)), { - // allow reason: angles are in [0, 2π) - #[allow(clippy::cast_precision_loss)] - let mut ans = (lhs as f64) * rhs; - while ans >= f64::consts::TAU { - ans -= f64::consts::TAU; + 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 { + return None; } - Float(ans) + #[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), (Float(lhs), Int(rhs)), { - // allow reason: angles are in [0, 2π) - #[allow(clippy::cast_precision_loss)] - let mut ans = lhs * (rhs as f64); - while ans >= f64::consts::TAU { - ans -= f64::consts::TAU; - } - Float(ans) - }), + Type::Angle(..) => { + rewrap_lit!( + (lhs, rhs), + (Angle(lhs), Int(rhs)), + Angle(lhs * u64::try_from(rhs).ok()?) + ) + } _ => None, }, BinOp::Div => match lhs_ty { @@ -388,11 +450,23 @@ impl BinaryOpExpr { Type::Float(..) => { rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Float(lhs / rhs)) } - Type::Angle(..) => rewrap_lit!((lhs, rhs), (Float(lhs), Int(rhs)), { - // allow reason: angles are in [0, 2π) - #[allow(clippy::cast_precision_loss)] - Float(lhs / (rhs as f64)) - }), + 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 { @@ -435,16 +509,16 @@ impl IndexExpr { impl Cast { fn const_eval(&self, symbols: &SymbolTable, ty: &Type) -> Option { let lit = self.expr.const_eval(symbols)?; - let from_ty = &self.expr.ty; + let lit_ty = &self.expr.ty; match ty { - Type::Bool(_) => cast_to_bool(from_ty, lit), - Type::Int(_, _) => cast_to_int(from_ty, lit), - Type::UInt(_, _) => cast_to_uint(from_ty, lit), - Type::Float(_, _) => cast_to_float(from_ty, lit), - Type::Angle(_, _) => cast_to_angle(from_ty, lit), - Type::Bit(_) => cast_to_bit(from_ty, lit), - Type::BitArray(dims, _) => cast_to_bitarray(from_ty, lit, dims), + Type::Bool(_) => cast_to_bool(lit_ty, lit), + Type::Int(_, _) => cast_to_int(lit_ty, lit), + Type::UInt(_, _) => cast_to_uint(lit_ty, lit), + Type::Float(_, _) => cast_to_float(lit_ty, lit), + Type::Angle(_, _) => cast_to_angle(lit, lit_ty, ty), + Type::Bit(_) => cast_to_bit(lit_ty, lit), + Type::BitArray(dims, _) => cast_to_bitarray(lit_ty, lit, dims), _ => None, } } @@ -458,14 +532,15 @@ impl Cast { /// | bool | - | Yes | Yes | Yes | Yes | Yes | /// +---------------+------+-----+------+-------+-------+-----+ fn cast_to_bool(ty: &Type, lit: LiteralKind) -> Option { - use LiteralKind::{Bit, Bitstring, Bool, Float, Int}; + use LiteralKind::{Angle, Bit, Bitstring, Bool, Float, Int}; match 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(..) | Type::Angle(..) => rewrap_lit!(lit, Float(val), Bool(val != 0.0)), + Type::Float(..) => rewrap_lit!(lit, Float(val), Bool(val != 0.0)), + Type::Angle(..) => rewrap_lit!(lit, Angle(val), Bool(val.into())), _ => None, } } @@ -502,7 +577,7 @@ fn cast_to_int(ty: &Type, lit: LiteralKind) -> Option { /// +---------------+-----------------------------------------+ /// | Allowed casts | Casting from | /// +---------------+------+-----+------+-------+-------+-----+ -/// | Casting from | bool | int | uint | float | angle | bit | +/// | Casting to | bool | int | uint | float | angle | bit | /// +---------------+------+-----+------+-------+-------+-----+ /// | uint | Yes | Yes | - | Yes | No | Yes | /// +---------------+------+-----+------+-------+-------+-----+ @@ -544,7 +619,7 @@ fn cast_to_float(ty: &Type, lit: LiteralKind) -> Option { 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(val as f64) + Float(safe_i64_to_f64(val)?) }), Type::Float(..) => Some(lit), _ => None, @@ -558,9 +633,35 @@ fn cast_to_float(ty: &Type, lit: LiteralKind) -> Option { /// +---------------+------+-----+------+-------+-------+-----+ /// | angle | No | No | No | Yes | - | Yes | /// +---------------+------+-----+------+-------+-------+-----+ -fn cast_to_angle(ty: &Type, lit: LiteralKind) -> Option { - match ty { - Type::Float(..) | Type::Angle(..) => Some(lit), +fn cast_to_angle(lit: LiteralKind, lit_ty: &Type, target_ty: &Type) -> Option { + use LiteralKind::{Angle, Bit, Bitstring, Float}; + match lit_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(target_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, } } @@ -573,12 +674,13 @@ fn cast_to_angle(ty: &Type, lit: LiteralKind) -> Option { /// | bit | Yes | Yes | Yes | No | Yes | - | /// +---------------+------+-----+------+-------+-------+-----+ fn cast_to_bit(ty: &Type, lit: LiteralKind) -> Option { - use LiteralKind::{Bit, Bool, Int}; + use LiteralKind::{Angle, Bit, Bool, Int}; match 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, } } @@ -591,7 +693,7 @@ fn cast_to_bit(ty: &Type, lit: LiteralKind) -> Option { /// | bitarray | Yes | Yes | Yes | No | Yes | - | /// +---------------+------+-----+------+-------+-------+-----+ fn cast_to_bitarray(ty: &Type, lit: LiteralKind, dims: &ArrayDimensions) -> Option { - use LiteralKind::{Bit, Bitstring, Bool, Int}; + use LiteralKind::{Angle, Bit, Bitstring, Bool, Int}; let ArrayDimensions::One(size) = dims else { return None; @@ -600,9 +702,13 @@ fn cast_to_bitarray(ty: &Type, lit: LiteralKind, dims: &ArrayDimensions) -> Opti match 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 { + if rhs_size > size { return None; } Bitstring(val, size) diff --git a/compiler/qsc_qasm3/src/semantic/error.rs b/compiler/qsc_qasm3/src/semantic/error.rs index 75ee54ff56..404ae890b3 100644 --- a/compiler/qsc_qasm3/src/semantic/error.rs +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -204,6 +204,9 @@ pub enum SemanticErrorKind { #[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), @@ -371,6 +374,9 @@ impl SemanticErrorKind { 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) => { diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index 28337a3e01..f9d2755877 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -5,10 +5,12 @@ use std::ops::ShlAssign; use std::rc::Rc; 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_int_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; @@ -28,7 +30,7 @@ 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::types::get_qsharp_gate_name; +use crate::stdlib::angle::Angle; use super::ast as semantic; use crate::parser::ast as syntax; @@ -238,24 +240,33 @@ impl Lowerer { ) } let gates = vec![ - gate_symbol("X", 0, 1), - gate_symbol("Y", 0, 1), - gate_symbol("Z", 0, 1), - gate_symbol("H", 0, 1), - gate_symbol("S", 0, 1), - gate_symbol("T", 0, 1), - gate_symbol("Rx", 1, 1), - gate_symbol("Rxx", 1, 2), - gate_symbol("Ry", 1, 1), - gate_symbol("Ryy", 1, 2), - gate_symbol("Rz", 1, 1), - gate_symbol("Rzz", 1, 2), - gate_symbol("CNOT", 0, 2), - gate_symbol("CY", 0, 2), - gate_symbol("CZ", 0, 2), - gate_symbol("I", 0, 1), - gate_symbol("SWAP", 0, 2), - gate_symbol("CCNOT", 0, 3), + 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", 0, 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(); @@ -739,7 +750,8 @@ impl Lowerer { crate::types::Type::Int(is_const) } } - Type::Float(_, _) | Type::Angle(_, _) => crate::types::Type::Double(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(_) => { @@ -1005,11 +1017,9 @@ impl Lowerer { // Push the scope where the def lives. self.symbols.push_scope(ScopeKind::Function); - let params = stmt - .params - .iter() - .map(|param| { - let symbol = self.lower_typed_parameter(param); + let params = param_symbols + .into_iter() + .map(|symbol| { let name = symbol.name.clone(); self.try_insert_or_get_existing_symbol_id(name, symbol) }) @@ -1287,8 +1297,6 @@ impl Lowerer { // Q: Do we need this during lowering? // A: Yes, we need it to check the gate_call arity. modifiers.push(implicit_modifier); - } else { - name = get_qsharp_gate_name(&name).unwrap_or(&name).to_string(); } // 3. Check that the gate_name actually refers to a gate in the symbol table @@ -1408,7 +1416,7 @@ impl Lowerer { 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 { + 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; }; @@ -1581,7 +1589,7 @@ impl Lowerer { 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); + 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.symbols)) @@ -1872,7 +1880,17 @@ impl Lowerer { let Some(size) = self.const_eval_type_width_designator_from_expr(size) else { return crate::semantic::types::Type::Err; }; - crate::semantic::types::Type::Float(Some(size), is_const) + 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), }, @@ -1894,7 +1912,18 @@ impl Lowerer { let Some(size) = self.const_eval_type_width_designator_from_expr(size) else { return crate::semantic::types::Type::Err; }; - crate::semantic::types::Type::Angle(Some(size), is_const) + + 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), }, @@ -2005,10 +2034,11 @@ impl Lowerer { } }; 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::Angle(_, _) | Type::Float(_, _) => Some(from_lit_kind(LiteralKind::Float(0.0))), + 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"; @@ -2211,7 +2241,19 @@ impl Lowerer { } None } - (Type::Angle(..) | Type::Float(..), Type::Float(..)) => { + (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, @@ -2335,18 +2377,14 @@ impl Lowerer { } 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 { + 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( - &mut self, - ty: &Type, - expr: &semantic::Expr, - ) -> Option { + 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 @@ -2372,19 +2410,11 @@ impl Lowerer { | (Type::Float(w1, _), Type::Float(w2, _)) | (Type::Complex(w1, _), Type::Complex(w2, _)) => { if w1.is_none() && w2.is_some() { - return Some(semantic::Expr { - span: expr.span, - kind: expr.kind.clone(), - ty: ty.clone(), - }); + return Some(wrap_expr_in_implicit_cast_expr(ty.clone(), expr.clone())); } if *w1 >= *w2 { - return Some(semantic::Expr { - span: expr.span, - kind: expr.kind.clone(), - ty: ty.clone(), - }); + return Some(wrap_expr_in_implicit_cast_expr(ty.clone(), expr.clone())); } } _ => {} @@ -2394,7 +2424,7 @@ impl Lowerer { // 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::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), @@ -2428,23 +2458,18 @@ impl Lowerer { /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ /// | bit | Yes | Yes | Yes | No | Yes | - | No | No | /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ - fn cast_bit_expr_to_type(&mut self, ty: &Type, rhs: &semantic::Expr) -> Option { + 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::Angle(..) => { - let msg = "Cast bit to angle"; - self.push_unimplemented_error_message(msg, rhs.span); - None - } &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::Bool(_) | &Type::Int(_, _) | &Type::UInt(_, _) => { + &Type::Angle(..) | &Type::Bool(_) | &Type::Int(_, _) | &Type::UInt(_, _) => { Some(wrap_expr_in_implicit_cast_expr(ty.clone(), rhs.clone())) } @@ -2642,6 +2667,30 @@ impl Lowerer { 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); @@ -2747,20 +2796,14 @@ impl Lowerer { syntax::BinOp::AndB | syntax::BinOp::OrB | syntax::BinOp::XorB ) && matches!( left_type, - Type::Bit(..) - | Type::UInt(..) - | Type::Angle(..) - | Type::BitArray(ArrayDimensions::One(_), _) + 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::Angle(..) - | Type::BitArray(ArrayDimensions::One(_), _) + Type::Bit(..) | Type::UInt(..) | Type::BitArray(ArrayDimensions::One(_), _) )) { return true; @@ -3100,16 +3143,19 @@ fn try_get_qsharp_name_and_implicit_modifiers>( kind, }; - // ch, crx, cry, crz, sdg, and tdg 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)))), - "sdg" => Some(("S".to_string(), make_modifier(Inv))), - "tdg" => Some(("T".to_string(), make_modifier(Inv))), + "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/tests.rs b/compiler/qsc_qasm3/src/semantic/tests.rs index 583875c50a..d2eb3f29a0 100644 --- a/compiler/qsc_qasm3/src/semantic/tests.rs +++ b/compiler/qsc_qasm3/src/semantic/tests.rs @@ -221,10 +221,10 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { bit c = r; // undefined symbol r "#; let source1 = r#"include "source2.qasm"; - angle z = 7.0; - float k = z + false; // invalid cast"#; - let source2 = "bit x = 1; - bool x = y && x; // undefined y, redefine x"; + 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()), @@ -241,7 +241,7 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { Stmt [196-206]: annotations: kind: ClassicalDeclarationStmt [196-206]: - symbol_id: 26 + symbol_id: 35 ty_span: [196-199] init_expr: Expr [204-205]: ty: Bit(true) @@ -249,7 +249,7 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { Stmt [211-227]: annotations: kind: ClassicalDeclarationStmt [211-227]: - symbol_id: 26 + symbol_id: 35 ty_span: [211-215] init_expr: Expr [220-226]: ty: Bool(false) @@ -257,26 +257,26 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { op: AndL lhs: Expr [220-221]: ty: Err - kind: SymbolId(27) + 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(26) + kind: SymbolId(35) Stmt [140-154]: annotations: kind: ClassicalDeclarationStmt [140-154]: - symbol_id: 28 + symbol_id: 37 ty_span: [140-145] init_expr: Expr [150-153]: ty: Angle(None, true) - kind: Lit: Float(7.0) + kind: Lit: Angle(0.7168146928204138) Stmt [159-179]: annotations: kind: ClassicalDeclarationStmt [159-179]: - symbol_id: 29 + symbol_id: 38 ty_span: [159-164] init_expr: Expr [169-178]: ty: Float(None, false) @@ -284,7 +284,7 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { op: Add lhs: Expr [169-170]: ty: Angle(None, false) - kind: SymbolId(28) + kind: SymbolId(37) rhs: Expr [173-178]: ty: Float(None, false) kind: Cast [0-0]: @@ -295,34 +295,34 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { Stmt [74-84]: annotations: kind: ClassicalDeclarationStmt [74-84]: - symbol_id: 31 + symbol_id: 40 ty_span: [74-77] init_expr: Expr [82-83]: ty: Err - kind: SymbolId(30) + kind: SymbolId(39) [Qsc.Qasm3.Compile.UndefinedSymbol - x Undefined symbol: y. + x Undefined symbol: v. ,-[source2.qasm:2:14] - 1 | bit x = 1; - 2 | bool x = y && x; // undefined y, redefine x + 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 x = 1; - 2 | bool x = y && x; // undefined y, redefine x + 1 | bit l = 1; + 2 | bool l = v && l; // undefined y, redefine l : ^ `---- , Qsc.Qasm3.Compile.RedefinedSymbol - x Redefined symbol: x. + x Redefined symbol: l. ,-[source2.qasm:2:10] - 1 | bit x = 1; - 2 | bool x = y && x; // undefined y, redefine x + 1 | bit l = 1; + 2 | bool l = v && l; // undefined y, redefine l : ^ `---- , Qsc.Qasm3.Compile.CannotCast @@ -330,8 +330,8 @@ fn semantic_errors_map_to_their_corresponding_file_specific_spans() { x Cannot cast expression of type Angle(None, false) to type Float(None, | false) ,-[source1.qasm:3:15] - 2 | angle z = 7.0; - 3 | float k = z + false; // invalid cast + 2 | angle j = 7.0; + 3 | float k = j + false; // invalid cast : ^ `---- , Qsc.Qasm3.Compile.UndefinedSymbol diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls.rs b/compiler/qsc_qasm3/src/semantic/tests/decls.rs index daf0381d85..94629a0e1a 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls.rs @@ -93,7 +93,7 @@ fn scalar_ty_designator_must_be_castable_to_const_int() { ty_span: [6-11] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(2.0) + kind: Lit: Angle(2.0000000000000004) Stmt [24-36]: annotations: kind: ClassicalDeclarationStmt [24-36]: diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs index 74363762e3..e64a0ff65a 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/angle.rs @@ -15,11 +15,11 @@ fn implicit_bitness_default() { ty_span: [0-5] init_expr: Expr [0-0]: ty: Angle(None, true) - kind: Lit: Float(0.0) + kind: Lit: Angle(0) [8] Symbol [6-7]: name: x type: Angle(None, false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -34,11 +34,11 @@ fn lit() { ty_span: [0-5] init_expr: Expr [10-14]: ty: Angle(None, true) - kind: Lit: Float(42.1) + kind: Lit: Angle(4.400888156922484) [8] Symbol [6-7]: name: x type: Angle(None, false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -53,11 +53,11 @@ fn const_lit() { ty_span: [6-11] init_expr: Expr [16-20]: ty: Angle(None, true) - kind: Lit: Float(42.1) + kind: Lit: Angle(4.400888156922484) [8] Symbol [12-13]: name: x type: Angle(None, true) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -72,11 +72,11 @@ fn lit_explicit_width() { ty_span: [0-9] init_expr: Expr [14-18]: ty: Angle(Some(64), true) - kind: Lit: Float(42.1) + kind: Lit: Angle(4.400888156922484) [8] Symbol [10-11]: name: x type: Angle(Some(64), false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -91,11 +91,11 @@ fn const_explicit_width_lit() { ty_span: [6-15] init_expr: Expr [20-24]: ty: Angle(Some(64), true) - kind: Lit: Float(42.1) + kind: Lit: Angle(4.400888156922484) [8] Symbol [16-17]: name: x type: Angle(Some(64), true) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -110,11 +110,11 @@ fn lit_decl_leading_dot() { ty_span: [0-5] init_expr: Expr [10-14]: ty: Angle(None, true) - kind: Lit: Float(0.421) + kind: Lit: Angle(0.4210000000000001) [8] Symbol [6-7]: name: x type: Angle(None, false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -129,11 +129,11 @@ fn const_lit_decl_leading_dot() { ty_span: [6-11] init_expr: Expr [16-20]: ty: Angle(None, true) - kind: Lit: Float(0.421) + kind: Lit: Angle(0.4210000000000001) [8] Symbol [12-13]: name: x type: Angle(None, true) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -148,11 +148,11 @@ fn const_lit_decl_leading_dot_scientific() { ty_span: [6-11] init_expr: Expr [16-22]: ty: Angle(None, true) - kind: Lit: Float(42.1) + kind: Lit: Angle(4.400888156922484) [8] Symbol [12-13]: name: x type: Angle(None, true) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -167,11 +167,11 @@ fn lit_decl_trailing_dot() { ty_span: [0-5] init_expr: Expr [10-14]: ty: Angle(None, true) - kind: Lit: Float(421.0) + kind: Lit: Angle(0.02658441896772248) [8] Symbol [6-7]: name: x type: Angle(None, false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -186,11 +186,11 @@ fn const_lit_decl_trailing_dot() { ty_span: [6-11] init_expr: Expr [16-20]: ty: Angle(None, true) - kind: Lit: Float(421.0) + kind: Lit: Angle(0.02658441896772248) [8] Symbol [12-13]: name: x type: Angle(None, true) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -205,11 +205,11 @@ fn lit_decl_scientific() { ty_span: [0-5] init_expr: Expr [10-16]: ty: Angle(None, true) - kind: Lit: Float(42.1) + kind: Lit: Angle(4.400888156922484) [8] Symbol [6-7]: name: x type: Angle(None, false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -224,11 +224,11 @@ fn const_lit_decl_scientific() { ty_span: [6-11] init_expr: Expr [16-22]: ty: Angle(None, true) - kind: Lit: Float(42.1) + kind: Lit: Angle(4.400888156922484) [8] Symbol [12-13]: name: x type: Angle(None, true) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -243,11 +243,11 @@ fn lit_decl_scientific_signed_pos() { ty_span: [0-5] init_expr: Expr [10-17]: ty: Angle(None, true) - kind: Lit: Float(42.1) + kind: Lit: Angle(4.400888156922484) [8] Symbol [6-7]: name: x type: Angle(None, false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -262,11 +262,11 @@ fn const_lit_decl_scientific_signed_pos() { ty_span: [6-11] init_expr: Expr [16-23]: ty: Angle(None, true) - kind: Lit: Float(42.1) + kind: Lit: Angle(4.400888156922484) [8] Symbol [12-13]: name: x type: Angle(None, true) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -281,11 +281,11 @@ fn lit_decl_scientific_cap_e() { ty_span: [0-5] init_expr: Expr [10-16]: ty: Angle(None, true) - kind: Lit: Float(42.1) + kind: Lit: Angle(4.400888156922484) [8] Symbol [6-7]: name: x type: Angle(None, false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -300,11 +300,11 @@ fn const_lit_decl_scientific_cap_e() { ty_span: [6-11] init_expr: Expr [16-22]: ty: Angle(None, true) - kind: Lit: Float(42.1) + kind: Lit: Angle(4.400888156922484) [8] Symbol [12-13]: name: x type: Angle(None, true) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -319,11 +319,11 @@ fn lit_decl_scientific_signed_neg() { ty_span: [0-5] init_expr: Expr [10-18]: ty: Angle(None, true) - kind: Lit: Float(42.1) + kind: Lit: Angle(4.400888156922484) [8] Symbol [6-7]: name: x type: Angle(None, false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -338,11 +338,11 @@ fn const_lit_decl_scientific_signed_neg() { ty_span: [6-11] init_expr: Expr [16-24]: ty: Angle(None, true) - kind: Lit: Float(42.1) + kind: Lit: Angle(4.400888156922484) [8] Symbol [12-13]: name: x type: Angle(None, true) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -369,7 +369,7 @@ fn const_lit_decl_signed_float_lit_cast_neg() { [8] Symbol [12-13]: name: x type: Angle(None, true) - qsharp_type: Double + qsharp_type: Angle io_kind: Default"#]], ); } @@ -405,3 +405,75 @@ fn const_lit_decl_signed_int_lit_cast_neg_fails() { ]"#]], ); } + +#[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/expression/implicit_cast_from_angle.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_angle.rs index fb6601199e..3936536762 100644 --- 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 @@ -20,11 +20,11 @@ fn to_bit_implicitly() { ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) [8] Symbol [15-16]: name: x type: Angle(None, false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default ClassicalDeclarationStmt [32-42]: symbol_id: 9 @@ -60,11 +60,11 @@ fn explicit_width_to_bit_implicitly_fails() { ty_span: [9-18] init_expr: Expr [23-26]: ty: Angle(Some(64), true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) [8] Symbol [19-20]: name: x type: Angle(Some(64), false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default ClassicalDeclarationStmt [36-46]: symbol_id: 9 @@ -99,11 +99,11 @@ fn to_bool_implicitly() { ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) [8] Symbol [15-16]: name: x type: Angle(None, false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default ClassicalDeclarationStmt [32-43]: symbol_id: 9 @@ -144,7 +144,7 @@ fn to_implicit_int_implicitly_fails() { ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) Stmt [32-42]: annotations: kind: ClassicalDeclarationStmt [32-42]: @@ -187,7 +187,7 @@ fn to_explicit_int_implicitly_fails() { ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) Stmt [32-46]: annotations: kind: ClassicalDeclarationStmt [32-46]: @@ -231,7 +231,7 @@ fn to_implicit_uint_implicitly_fails() { ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) Stmt [32-43]: annotations: kind: ClassicalDeclarationStmt [32-43]: @@ -327,7 +327,7 @@ fn to_explicit_uint_implicitly_fails() { ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) Stmt [32-47]: annotations: kind: ClassicalDeclarationStmt [32-47]: @@ -371,7 +371,7 @@ fn to_explicit_bigint_implicitly_fails() { ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) Stmt [32-46]: annotations: kind: ClassicalDeclarationStmt [32-46]: @@ -415,7 +415,7 @@ fn to_implicit_float_implicitly_fails() { ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) Stmt [32-44]: annotations: kind: ClassicalDeclarationStmt [32-44]: @@ -459,7 +459,7 @@ fn to_explicit_float_implicitly_fails() { ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) Stmt [32-48]: annotations: kind: ClassicalDeclarationStmt [32-48]: @@ -503,7 +503,7 @@ fn to_implicit_complex_implicitly_fails() { ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) Stmt [32-53]: annotations: kind: ClassicalDeclarationStmt [32-53]: @@ -547,7 +547,7 @@ fn to_explicit_complex_implicitly_fails() { ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) Stmt [32-57]: annotations: kind: ClassicalDeclarationStmt [32-57]: @@ -586,11 +586,11 @@ fn to_angle_implicitly() { ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) [8] Symbol [15-16]: name: x type: Angle(None, false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default ClassicalDeclarationStmt [32-44]: symbol_id: 9 @@ -601,7 +601,7 @@ fn to_angle_implicitly() { [9] Symbol [38-39]: name: y type: Angle(None, false) - qsharp_type: Double + qsharp_type: Angle io_kind: Default "#]], ); @@ -622,22 +622,89 @@ fn to_explicit_angle_implicitly() { ty_span: [9-14] init_expr: Expr [19-22]: ty: Angle(None, true) - kind: Lit: Float(42.0) + kind: Lit: Angle(4.300888156922483) [8] Symbol [15-16]: name: x type: Angle(None, false) - qsharp_type: Double + 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: SymbolId(8) + 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: Double + 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 index 9e207073e3..b0f0e96648 100644 --- 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 @@ -6,25 +6,83 @@ use expect_test::expect; use crate::semantic::tests::check_classical_decls; #[test] -#[ignore = "not yet implemented"] fn to_angle_implicitly() { let input = r#" bit x = 1; angle y = x; "#; - check_classical_decls(input, &expect![[r#""#]]); + 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] -#[ignore = "not yet implemented"] fn to_explicit_angle_implicitly() { let input = r#" bit x = 1; angle[4] y = x; "#; - check_classical_decls(input, &expect![[r#""#]]); + 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] 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 index 78ab02b6b9..706646a53b 100644 --- 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 @@ -436,7 +436,11 @@ fn to_explicit_float_implicitly() { ty_span: [32-41] init_expr: Expr [46-47]: ty: Float(Some(32), false) - kind: SymbolId(8) + 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) diff --git a/compiler/qsc_qasm3/src/semantic/types.rs b/compiler/qsc_qasm3/src/semantic/types.rs index 2187a37442..e05dadc203 100644 --- a/compiler/qsc_qasm3/src/semantic/types.rs +++ b/compiler/qsc_qasm3/src/semantic/types.rs @@ -623,6 +623,34 @@ pub(crate) fn unary_op_can_be_applied_to_type(op: syntax::UnaryOp, ty: &Type) -> } } +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, @@ -688,8 +716,8 @@ pub(crate) fn try_promote_with_casting(left_type: &Type, right_type: &Type) -> T } // simple promotion failed, try a lossless cast // each side to double - let promoted_rhs = promote_types(&Type::Float(None, false), right_type); - let promoted_lhs = promote_types(left_type, &Type::Float(None, false)); + 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), 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 c98591dac4..e47f0d4a30 100644 --- a/compiler/qsc_qasm3/src/tests.rs +++ b/compiler/qsc_qasm3/src/tests.rs @@ -1,16 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::runtime::RuntimeFunctions; -use crate::semantic::symbols::SymbolTable; +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}; @@ -54,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, @@ -102,7 +104,6 @@ where source_map: res.source_map, config, stmts: vec![], - runtime: RuntimeFunctions::empty(), symbols: res.symbols, errors: res.errors, }; @@ -175,8 +176,7 @@ where source_map: res.source_map, config, stmts: vec![], - runtime: RuntimeFunctions::empty(), - symbols: SymbolTable::default(), + symbols: res.symbols, errors: res.errors, }; @@ -346,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/declaration.rs b/compiler/qsc_qasm3/src/tests/declaration.rs index 1c189a4482..c23f0af07d 100644 --- a/compiler/qsc_qasm3/src/tests/declaration.rs +++ b/compiler/qsc_qasm3/src/tests/declaration.rs @@ -35,7 +35,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; diff --git a/compiler/qsc_qasm3/src/tests/declaration/gate.rs b/compiler/qsc_qasm3/src/tests/declaration/gate.rs index de4614d2d8..7795720a1b 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/gate.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/gate.rs @@ -18,7 +18,7 @@ fn single_qubit() -> miette::Result<(), Vec> { expect![ r#" let my_h : (Qubit) => Unit = (q) => { - H(q); + h(q); }; "# ] @@ -40,8 +40,8 @@ fn two_qubits() -> miette::Result<(), Vec> { expect![ r#" let my_h : (Qubit, Qubit) => Unit = (q, q2) => { - H(q2); - H(q); + h(q2); + h(q); }; "# ] @@ -59,13 +59,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#" + let my_h : (__Angle__, Qubit) => Unit = (θ, q) => { + rx(θ, q); }; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -81,14 +79,12 @@ 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#" + let my_h : (__Angle__, __Angle__, Qubit, Qubit) => Unit = (θ, φ, q, q2) => { + rx(θ, q2); + ry(φ, q); }; - "# - ] + "#]] .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 38cd18c777..39c4fe404d 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(()) } @@ -213,11 +235,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..ce241820d9 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,16 @@ 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#" + @EntryPoint() + operation Test() : Result[] { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = [Zero, Zero]; + c + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -32,15 +33,16 @@ output bit c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Result { - mutable c = Zero; - c -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Result { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = Zero; + c + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -52,15 +54,16 @@ output bool c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Bool { - mutable c = false; - c -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Bool { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = false; + c + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -72,15 +75,16 @@ 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#" + @EntryPoint() + 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 +96,16 @@ output float f; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Double { - mutable f = 0.; - f -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable f = 0.; + f + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -112,15 +117,16 @@ output float[42] f; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Double { - mutable f = 0.; - f -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable f = 0.; + f + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -132,15 +138,16 @@ output int[42] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -152,15 +159,16 @@ output int i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -172,15 +180,16 @@ output uint i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -192,15 +201,16 @@ output uint[42] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -212,15 +222,16 @@ output int[65] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : BigInt { - mutable i = 0L; - i -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : BigInt { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -237,7 +248,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 +266,24 @@ 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#" + @EntryPoint() + 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(()) } 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..88db764e6d 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,16 @@ bit[2] c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Result[] { - mutable c = [Zero, Zero]; - c -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Result[] { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = [Zero, Zero]; + c + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -32,15 +33,16 @@ bit c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Result { - mutable c = Zero; - c -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Result { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = Zero; + c + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -52,15 +54,16 @@ bool c; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Bool { - mutable c = false; - c -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Bool { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable c = false; + c + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -72,15 +75,16 @@ 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#" + @EntryPoint() + 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 +96,16 @@ float f; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Double { - mutable f = 0.; - f -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable f = 0.; + f + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -112,15 +117,16 @@ float[42] f; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Double { - mutable f = 0.; - f -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Double { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable f = 0.; + f + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -132,15 +138,16 @@ int[42] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -152,15 +159,16 @@ int i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -172,15 +180,16 @@ uint i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -192,15 +201,16 @@ uint[42] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : Int { - mutable i = 0; - i -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : Int { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -212,15 +222,16 @@ int[65] i; "#; let qsharp = compile_qasm_to_qsharp_operation(source)?; - expect![ - r#" -@EntryPoint() -operation Test() : BigInt { - mutable i = 0L; - i -} -"# - ] + expect![[r#" + @EntryPoint() + operation Test() : BigInt { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + mutable i = 0; + i + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -240,23 +251,24 @@ 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#" + @EntryPoint() + 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(()) } diff --git a/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs b/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs index 0d4f1ba594..58a93ab28e 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs @@ -243,8 +243,8 @@ fn assigning_uint_to_negative_lit_results_in_semantic_error() { let Err(errors) = compile_qasm_stmt_to_qsharp(source) else { panic!("Expected error"); }; - expect![ + 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/binary.rs b/compiler/qsc_qasm3/src/tests/expression/binary.rs index 533e9323c5..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; 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..e07e28d457 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary/comparison.rs +++ b/compiler/qsc_qasm3/src/tests/expression/binary/comparison.rs @@ -23,11 +23,13 @@ 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 { @EntryPoint() operation Test() : (Int, Int, Bool, Bool, Bool, Bool, Bool, Bool) { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 5; mutable y = 3; mutable f = (x > y); @@ -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,11 +59,13 @@ 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 { @EntryPoint() operation Test() : (Int, Int, Bool, Bool, Bool, Bool, Bool, Bool) { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 5; mutable y = 3; mutable f = (x > y); @@ -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,11 +95,13 @@ 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 { @EntryPoint() operation Test() : (Result, Result, Bool, Bool, Bool, Bool, Bool, Bool) { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = One; mutable y = Zero; mutable f = (x > y); @@ -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 { 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)) - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; 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,11 +213,13 @@ 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 { @EntryPoint() operation Test() : (Double, Double, Bool, Bool, Bool, Bool, Bool, Bool) { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = 5.; mutable y = 3.; mutable f = (x > y); @@ -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,11 +251,13 @@ 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 { @EntryPoint() operation Test() : (Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool, Bool) { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; mutable x = true; mutable y = false; mutable a = (x and y); @@ -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/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..21e138626b 100644 --- a/compiler/qsc_qasm3/src/tests/expression/bits.rs +++ b/compiler/qsc_qasm3/src/tests/expression/bits.rs @@ -30,21 +30,9 @@ fn bit_array_bits_and_register_ops() -> miette::Result<(), Vec> { namespace qasm3_import { @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)) - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; 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]; @@ -74,28 +62,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 index 8c5628378c..8347947792 100644 --- a/compiler/qsc_qasm3/src/tests/expression/function_call.rs +++ b/compiler/qsc_qasm3/src/tests/expression/function_call.rs @@ -14,6 +14,9 @@ fn funcall_with_no_arguments_generates_correct_qsharp() -> miette::Result<(), Ve let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let empty : () -> Unit = () -> {}; empty(); "#]] @@ -30,6 +33,9 @@ fn void_function_with_one_argument_generates_correct_qsharp() -> miette::Result< let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let f : (Int) -> Unit = (x) -> {}; f(2); "#]] @@ -49,6 +55,9 @@ fn funcall_with_one_argument_generates_correct_qsharp() -> miette::Result<(), Ve let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let square : (Int) -> Int = (x) -> { return x * x; }; @@ -70,6 +79,9 @@ fn funcall_with_two_arguments_generates_correct_qsharp() -> miette::Result<(), V let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let sum : (Int, Int) -> Int = (x, y) -> { return x + y; }; @@ -94,13 +106,9 @@ fn funcall_with_qubit_argument() -> 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 - } - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let parity : (Qubit[]) => Result = (qs) => { mutable a = QIR.Intrinsic.__quantum__qis__m__body(qs[0]); mutable b = QIR.Intrinsic.__quantum__qis__m__body(qs[1]); @@ -187,8 +195,11 @@ fn funcall_accepts_qubit_argument() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let h_wrapper : (Qubit) => Unit = (q) => { - H(q); + h(q); }; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); h_wrapper(q); @@ -209,6 +220,9 @@ fn classical_decl_initialized_with_funcall() -> miette::Result<(), Vec> let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let square : (Int) -> Int = (x) -> { return x * x; }; @@ -258,18 +272,9 @@ fn funcall_implicit_arg_cast_uint_to_bitarray() -> miette::Result<(), Vec>>= 1; - } - Microsoft.Quantum.Arrays.Reversed(result) - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let parity : (Result[]) -> Result = (arr) -> { return 1; }; diff --git a/compiler/qsc_qasm3/src/tests/expression/ident.rs b/compiler/qsc_qasm3/src/tests/expression/ident.rs index f17a9d86f2..b7a5b1ce6b 100644 --- a/compiler/qsc_qasm3/src/tests/expression/ident.rs +++ b/compiler/qsc_qasm3/src/tests/expression/ident.rs @@ -67,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 b08eb71371..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!["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 3d879ca671..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 721778b5f7..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!["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 c76ee6b0ff..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(()) } @@ -104,12 +97,10 @@ fn bool_indexed_ty_is_same_as_element_ty() -> miette::Result<(), Vec> { "; 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(()) } @@ -125,7 +116,7 @@ fn bitstring_slicing() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![r#""#].assert_eq(&qsharp); + expect![[r#""#]].assert_eq(&qsharp); Ok(()) } @@ -140,7 +131,7 @@ fn bitstring_slicing_with_step() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![r#""#].assert_eq(&qsharp); + expect![[r#""#]].assert_eq(&qsharp); Ok(()) } @@ -155,6 +146,6 @@ fn bitstring_index_set() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![r#""#].assert_eq(&qsharp); + 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..cb5f05270a 100644 --- a/compiler/qsc_qasm3/src/tests/expression/unary.rs +++ b/compiler/qsc_qasm3/src/tests/expression/unary.rs @@ -15,12 +15,10 @@ fn bitwise_not_int() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" + expect![[r#" mutable x = 5; mutable y = ~~~x; - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } @@ -33,12 +31,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 +50,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 +69,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 +93,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 +109,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 4f6fdbd3fd..0e9bcac15b 100644 --- a/compiler/qsc_qasm3/src/tests/output.rs +++ b/compiler/qsc_qasm3/src/tests/output.rs @@ -41,13 +41,16 @@ fn using_re_semantics_removes_output() -> miette::Result<(), Vec> { expect![[r#" namespace qasm3_import { operation Test(theta : Double, beta : Int) : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; 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]); } @@ -89,13 +92,16 @@ fn using_qasm_semantics_captures_all_classical_decls_as_output() -> miette::Resu expect![[r#" namespace qasm3_import { operation Test(theta : Double, beta : Int) : (Result[], Double, Double) { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; 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) @@ -137,13 +143,16 @@ fn using_qiskit_semantics_only_bit_array_is_captured_and_reversed( expect![[r#" namespace qasm3_import { operation Test(theta : Double, beta : Int) : Result[] { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; 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) @@ -193,17 +202,20 @@ c2[2] = measure q[4]; expect![[r#" namespace qasm3_import { operation Test(theta : Double, beta : Int) : (Result[], Result[]) { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; 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]); @@ -246,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/scopes.rs b/compiler/qsc_qasm3/src/tests/scopes.rs index 5480fca9f1..7651ee223d 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); + h(q); }; }; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); my_h(q); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } diff --git a/compiler/qsc_qasm3/src/tests/statement/annotation.rs b/compiler/qsc_qasm3/src/tests/statement/annotation.rs index 64155978ba..1ad3d5ecb7 100644 --- a/compiler/qsc_qasm3/src/tests/statement/annotation.rs +++ b/compiler/qsc_qasm3/src/tests/statement/annotation.rs @@ -18,6 +18,9 @@ fn simulatable_intrinsic_can_be_applied_to_gate() -> miette::Result<(), Vec miette::Result<(), Vec miette::Result< 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.; @@ -103,6 +109,9 @@ fn can_assign_const_expr_to_non_const_decl() -> miette::Result<(), Vec> 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; @@ -120,6 +129,9 @@ fn ident_const() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let a = 1; mutable r = [Zero]; "#]] @@ -157,6 +169,9 @@ fn unary_op_neg_float() -> miette::Result<(), Vec> { 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]; @@ -175,6 +190,9 @@ fn unary_op_neg_int() -> miette::Result<(), Vec> { 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]; @@ -184,16 +202,23 @@ fn unary_op_neg_int() -> miette::Result<(), Vec> { } #[test] -#[ignore = "casting float to angle is not yet supported"] fn unary_op_neg_angle() -> miette::Result<(), Vec> { let source = r#" - const angle a = -1.0; - const bool b = a; + const angle[32] a = -1.0; + const bit b = a; bit[b] r; "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![[r#""#]].assert_eq(&qsharp); + 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(()) } @@ -207,6 +232,9 @@ fn unary_op_negb_uint() -> miette::Result<(), Vec> { 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]; @@ -216,20 +244,28 @@ fn unary_op_negb_uint() -> miette::Result<(), Vec> { } #[test] -#[ignore = "angles are not yet supported"] -fn unary_op_negb_angle() { + +fn unary_op_negb_angle() -> miette::Result<(), Vec> { let source = r#" - const angle a = 1.0; - const bool b = ~a; + const angle[32] a = 1.0; + const bit b = ~a; 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#""#]].assert_eq(&errs_string); + 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] @@ -242,6 +278,9 @@ fn unary_op_negb_bit() -> miette::Result<(), Vec> { 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]; @@ -260,9 +299,9 @@ fn unary_op_negb_bitarray() -> 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)) - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let a = [One, Zero, One]; let b = __ResultArrayAsIntBE__(~~~a); mutable r = [Zero, Zero]; @@ -284,6 +323,9 @@ fn lhs_ty_equals_rhs_ty_assumption_holds() -> miette::Result<(), Vec> { 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); @@ -307,6 +349,9 @@ fn binary_op_shl_uint() -> miette::Result<(), Vec> { 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]; @@ -316,17 +361,29 @@ fn binary_op_shl_uint() -> miette::Result<(), Vec> { } #[test] -#[ignore = "angles are not yet supported"] + fn binary_op_shl_angle() -> miette::Result<(), Vec> { let source = r#" - const angle a = 1; - const angle b = a << 2; - const bool c = b; + 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#""#]].assert_eq(&qsharp); + 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(()) } @@ -340,13 +397,9 @@ fn binary_op_shl_bit() -> 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 - } - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let a = One; let b = if __ResultAsInt__(a) <<< 2 == 0 { One @@ -369,21 +422,9 @@ fn binary_op_shl_bitarray() -> 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)) - } + 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]; @@ -471,6 +512,9 @@ fn binary_op_shr_uint() -> miette::Result<(), Vec> { 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]; @@ -480,17 +524,29 @@ fn binary_op_shr_uint() -> miette::Result<(), Vec> { } #[test] -#[ignore = "angles are not yet supported"] + fn binary_op_shr_angle() -> miette::Result<(), Vec> { let source = r#" - const angle a = 1; - const angle b = a >> 2; - const bool c = b; + 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#""#]].assert_eq(&qsharp); + 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(()) } @@ -504,13 +560,9 @@ fn binary_op_shr_bit() -> 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 - } - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let a = One; let b = if __ResultAsInt__(a) >>> 2 == 0 { One @@ -533,21 +585,9 @@ fn binary_op_shr_bitarray() -> 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)) - } + 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]; @@ -637,6 +677,9 @@ fn binary_op_andb_uint() -> miette::Result<(), Vec> { 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]; @@ -646,17 +689,34 @@ fn binary_op_andb_uint() -> miette::Result<(), Vec> { } #[test] -#[ignore = "angles are not yet supported"] + fn binary_op_andb_angle() -> miette::Result<(), Vec> { let source = r#" - const angle a = 1; - const angle b = a & 2; - const bool c = b; - bit[c] 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#""#]].assert_eq(&qsharp); + 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(()) } @@ -670,13 +730,9 @@ fn binary_op_andb_bit() -> 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 - } - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let a = One; let b = if __ResultAsInt__(a) &&& 0 == 0 { One @@ -699,21 +755,9 @@ fn binary_op_andb_bitarray() -> 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)) - } + 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]; @@ -734,6 +778,9 @@ fn binary_op_orb_uint() -> miette::Result<(), Vec> { 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]; @@ -743,17 +790,34 @@ fn binary_op_orb_uint() -> miette::Result<(), Vec> { } #[test] -#[ignore = "angles are not yet supported"] + fn binary_op_orb_angle() -> miette::Result<(), Vec> { let source = r#" - const angle a = 1.0; - const angle b = a | 2.0; - const bool c = b; - bit[c] 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#""#]].assert_eq(&qsharp); + 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(()) } @@ -767,13 +831,9 @@ fn binary_op_orb_bit() -> 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 - } - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let a = One; let b = if __ResultAsInt__(a) ||| 0 == 0 { One @@ -796,21 +856,9 @@ fn binary_op_orb_bitarray() -> 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)) - } + 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]; @@ -831,6 +879,9 @@ fn binary_op_xorb_uint() -> miette::Result<(), Vec> { 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]; @@ -840,17 +891,34 @@ fn binary_op_xorb_uint() -> miette::Result<(), Vec> { } #[test] -#[ignore = "angles are not yet supported"] + fn binary_op_xorb_angle() -> miette::Result<(), Vec> { let source = r#" - const angle a = 1; - const angle b = a ^ 2; - const bool c = b; - bit[c] 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#""#]].assert_eq(&qsharp); + 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(()) } @@ -864,13 +932,9 @@ fn binary_op_xorb_bit() -> 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 - } - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let a = One; let b = if __ResultAsInt__(a) ^^^ 1 == 0 { One @@ -893,21 +957,9 @@ fn binary_op_xorb_bitarray() -> 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)) - } + 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]; @@ -931,6 +983,9 @@ fn binary_op_andl_bool() -> miette::Result<(), Vec> { 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 = []; @@ -955,6 +1010,9 @@ fn binary_op_orl_bool() -> miette::Result<(), Vec> { 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 = []; @@ -984,6 +1042,9 @@ fn binary_op_comparison_int() -> miette::Result<(), Vec> { 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 = []; @@ -1010,6 +1071,9 @@ fn binary_op_comparison_uint() -> miette::Result<(), Vec> { 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 = []; @@ -1023,10 +1087,10 @@ fn binary_op_comparison_uint() -> miette::Result<(), Vec> { } #[test] -#[ignore = "angles are not yet supported"] + fn binary_op_comparison_angle() -> miette::Result<(), Vec> { let source = r#" - const angle a = 2.0; + const angle[32] a = 2.0; bit[a == a] r1; bit[a != a] r2; bit[a > a] r3; @@ -1036,7 +1100,22 @@ fn binary_op_comparison_angle() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![[r#""#]].assert_eq(&qsharp); + 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(()) } @@ -1054,6 +1133,9 @@ fn binary_op_comparison_bit() -> miette::Result<(), Vec> { 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 = []; @@ -1080,6 +1162,9 @@ fn binary_op_comparison_bitarray() -> miette::Result<(), Vec> { 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 = []; @@ -1106,6 +1191,9 @@ fn binary_op_add_int() -> miette::Result<(), Vec> { 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]; @@ -1124,6 +1212,9 @@ fn binary_op_add_uint() -> miette::Result<(), Vec> { 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]; @@ -1142,6 +1233,9 @@ fn binary_op_add_float() -> miette::Result<(), Vec> { 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]; @@ -1151,16 +1245,32 @@ fn binary_op_add_float() -> miette::Result<(), Vec> { } #[test] -#[ignore = "angles are not yet supported"] + fn binary_op_add_angle() -> miette::Result<(), Vec> { let source = r#" - const angle a = 1.0; - const angle b = 2.0; - bit[a + b] 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#""#]].assert_eq(&qsharp); + 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(()) } @@ -1176,6 +1286,9 @@ fn binary_op_sub_int() -> miette::Result<(), Vec> { 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]; @@ -1194,6 +1307,9 @@ fn binary_op_sub_uint() -> miette::Result<(), Vec> { 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]; @@ -1212,6 +1328,9 @@ fn binary_op_sub_float() -> miette::Result<(), Vec> { 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]; @@ -1221,16 +1340,32 @@ fn binary_op_sub_float() -> miette::Result<(), Vec> { } #[test] -#[ignore = "angles are not yet supported"] + fn binary_op_sub_angle() -> miette::Result<(), Vec> { let source = r#" - const float a = 3.0; - const float b = 2.0; - bit[a - b] 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#""#]].assert_eq(&qsharp); + 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(()) } @@ -1246,6 +1381,9 @@ fn binary_op_mul_int() -> miette::Result<(), Vec> { 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]; @@ -1264,6 +1402,9 @@ fn binary_op_mul_uint() -> miette::Result<(), Vec> { 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]; @@ -1282,6 +1423,9 @@ fn binary_op_mul_float() -> miette::Result<(), Vec> { 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]; @@ -1291,17 +1435,33 @@ fn binary_op_mul_float() -> miette::Result<(), Vec> { } #[test] -#[ignore = "angles are not yet supported"] + fn binary_op_mul_angle() -> miette::Result<(), Vec> { let source = r#" - const float a = 3.0; + const angle[32] a = 1.0; const uint b = 2; - bit[a * b] r1; - bit[b * a] r2; + 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#""#]].assert_eq(&qsharp); + 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(()) } @@ -1317,6 +1477,9 @@ fn binary_op_div_int() -> miette::Result<(), Vec> { 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]; @@ -1335,6 +1498,9 @@ fn binary_op_div_uint() -> miette::Result<(), Vec> { 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]; @@ -1353,6 +1519,9 @@ fn binary_op_div_float() -> miette::Result<(), Vec> { 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]; @@ -1362,16 +1531,42 @@ fn binary_op_div_float() -> miette::Result<(), Vec> { } #[test] -#[ignore = "angles are not yet supported"] + fn binary_op_div_angle() -> miette::Result<(), Vec> { let source = r#" - const angle a = 6.0; - const uint b = 2; - bit[a / b] 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#""#]].assert_eq(&qsharp); + 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(()) } @@ -1386,6 +1581,9 @@ fn binary_op_mod_int() -> miette::Result<(), Vec> { 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]; "#]] @@ -1402,6 +1600,9 @@ fn binary_op_mod_uint() -> miette::Result<(), Vec> { 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]; "#]] @@ -1421,6 +1622,9 @@ fn binary_op_pow_int() -> miette::Result<(), Vec> { 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]; @@ -1439,6 +1643,9 @@ fn binary_op_pow_uint() -> miette::Result<(), Vec> { 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]; @@ -1457,6 +1664,9 @@ fn binary_op_pow_float() -> miette::Result<(), Vec> { 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]; @@ -1473,30 +1683,34 @@ fn cast_to_bool() -> miette::Result<(), Vec> { const int a = 0; const uint b = 1; const float c = 2.0; - // const angle d = 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 s4 = d; const bool s5 = e; bit[s1] r1; bit[s2] r2; bit[s3] r3; - // bit[s4] r4; + bit[s4] r4; bit[s5] r5; "#; 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 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 @@ -1513,10 +1727,12 @@ fn cast_to_bool() -> miette::Result<(), Vec> { } 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); @@ -1544,20 +1760,9 @@ fn cast_to_int() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" - function __BoolAsInt__(value : Bool) : Int { - if value { - 1 - } else { - 0 - } - } - function __ResultAsInt__(input : Result) : Int { - if Microsoft.Quantum.Convert.ResultAsBool(input) { - 1 - } else { - 0 - } - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let a = true; let b = 2; let c = 3.; @@ -1596,20 +1801,9 @@ fn cast_to_uint() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" - function __BoolAsInt__(value : Bool) : Int { - if value { - 1 - } else { - 0 - } - } - function __ResultAsInt__(input : Result) : Int { - if Microsoft.Quantum.Convert.ResultAsBool(input) { - 1 - } else { - 0 - } - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let a = true; let b = 2; let c = 3.; @@ -1645,13 +1839,9 @@ fn cast_to_float() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" - function __BoolAsDouble__(value : Bool) : Double { - if value { - 1. - } else { - 0. - } - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let a = true; let b = 2; let c = 3; @@ -1667,21 +1857,37 @@ fn cast_to_float() -> miette::Result<(), Vec> { } #[test] -#[ignore = "angles are not yet supported"] + fn cast_to_angle() -> miette::Result<(), Vec> { let source = r#" - const float a = 2.0; - const bit b = 1; + const float a1 = 2.0; + const bit a2 = 1; - const angle s1 = a; - const angle s2 = b; + 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#""#]].assert_eq(&qsharp); + 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(()) } @@ -1691,27 +1897,31 @@ fn cast_to_bit() -> miette::Result<(), Vec> { const bool a = false; const int b = 1; const uint c = 2; - // const angle d = 3.0; + const angle[32] d = 3.0; const bit s1 = a; const bit s2 = b; const bit s3 = c; - // const bit s4 = d; + const bit s4 = d; bit[s1] r1; bit[s2] r2; bit[s3] r3; - // bit[s4] r4; + bit[s4] r4; "#; let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" - function __BoolAsResult__(input : Bool) : Result { - Microsoft.Quantum.Convert.BoolAsResult(input) - } + 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 @@ -1723,9 +1933,11 @@ fn cast_to_bit() -> miette::Result<(), Vec> { } 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/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 b2941fdf86..3e7fecced1 100644 --- a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs +++ b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs @@ -14,23 +14,13 @@ fn u_gate_can_be_called() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp(source)?; - expect![ - r#" - operation U(theta : Double, phi : Double, lambda : Double, qubit : Qubit) : Unit is Adj + Ctl { - body ... { - Rz(lambda, qubit); - Ry(theta, qubit); - Rz(phi, qubit); - R(PauliI, -lambda - phi - theta, qubit); - } - adjoint auto; - controlled auto; - controlled adjoint auto; - } + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); - U(1., 2., 3., q); - "# - ] + U(__DoubleAsAngle__(1., 53), __DoubleAsAngle__(2., 53), __DoubleAsAngle__(3., 53), q); + "#]] .assert_eq(&qsharp); Ok(()) } @@ -43,15 +33,10 @@ fn gphase_gate_can_be_called() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp(source)?; expect![[r#" - operation gphase(theta : Double) : Unit is Adj + Ctl { - body ... { - Exp([], theta, []) - } - adjoint auto; - controlled auto; - controlled adjoint auto; - } - gphase(2.); + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + gphase(__DoubleAsAngle__(2., 53)); "#]] .assert_eq(&qsharp); Ok(()) @@ -66,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(()) } @@ -85,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(()) } @@ -106,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(()) } @@ -132,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 @@ -170,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(()) } @@ -184,14 +168,13 @@ 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(()) } @@ -284,8 +267,11 @@ fn rx_gate_with_one_angle_can_be_called() -> 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(); - Rx(2., q); + rx(__DoubleAsAngle__(2., 53), q); "#]] .assert_eq(&qsharp); Ok(()) @@ -328,9 +314,12 @@ fn implicit_cast_to_angle_works() -> 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(); - let a = 2.0; - Rx(2., q); + mutable a = 2.; + rx(__DoubleAsAngle__(a, 53), q); "#]] .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 9723bf3de8..3f551c4b7b 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> { + 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 fc21457b00..3c95d89622 100644 --- a/compiler/qsc_qasm3/src/tests/statement/include.rs +++ b/compiler/qsc_qasm3/src/tests/statement/include.rs @@ -42,10 +42,12 @@ fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { namespace qasm3_import { @EntryPoint() operation Test() : Result[] { - @SimulatableIntrinsic() - operation my_gate(q : Qubit) : Unit { - X(q); - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let my_gate : (Qubit) => Unit = (q) => { + x(q); + }; mutable c = [Zero]; let q = QIR.Runtime.AllocateQubitArray(1); my_gate(q[0]); diff --git a/compiler/qsc_qasm3/src/tests/statement/measure.rs b/compiler/qsc_qasm3/src/tests/statement/measure.rs index 4072d847e7..f3695d51e6 100644 --- a/compiler/qsc_qasm3/src/tests/statement/measure.rs +++ b/compiler/qsc_qasm3/src/tests/statement/measure.rs @@ -16,6 +16,9 @@ fn single_qubit_can_be_measured_into_single_bit() -> miette::Result<(), Vec 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]); @@ -121,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 97698f9e2d..fa4689dd25 100644 --- a/compiler/qsc_qasm3/src/tests/statement/reset.rs +++ b/compiler/qsc_qasm3/src/tests/statement/reset.rs @@ -33,20 +33,21 @@ fn reset_calls_are_generated_from_qasm() -> miette::Result<(), Vec> { 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 { @EntryPoint() operation Test() : Result[] { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; 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 81083c13f8..874d753bdb 100644 --- a/compiler/qsc_qasm3/src/tests/statement/switch.rs +++ b/compiler/qsc_qasm3/src/tests/statement/switch.rs @@ -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(()) } @@ -47,7 +48,7 @@ fn default_as_only_case_causes_parse_error() { panic!("Expected an error, got {res:?}"); }; assert_eq!(errors.len(), 1); - expect!["missing switch statement cases"].assert_eq(&errors[0].to_string()); + expect![["missing switch statement cases"]].assert_eq(&errors[0].to_string()); } #[test] @@ -64,7 +65,7 @@ fn no_cases_causes_parse_error() { panic!("Expected an error, got {res:?}"); }; assert_eq!(errors.len(), 1); - expect!["missing switch statement cases"].assert_eq(&errors[0].to_string()); + expect![["missing switch statement cases"]].assert_eq(&errors[0].to_string()); } #[test] @@ -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(()) } @@ -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(()) } @@ -186,35 +189,32 @@ fn spec_case_3() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qsharp_file(source)?; - expect![ - r#" + expect![[r#" namespace qasm3_import { @EntryPoint() operation Test() : Result[] { - function __ResultArrayAsIntBE__(results : Result[]) : Int { - Microsoft.Quantum.Convert.ResultArrayAsInt(Microsoft.Quantum.Arrays.Reversed(results)) - } + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; 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.1; @@ -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]; + let foo : (Int, Qubit[]) => Result = (i, d) => { + 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(()) } @@ -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 76e67f585a..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; diff --git a/compiler/qsc_qasm3/src/types.rs b/compiler/qsc_qasm3/src/types.rs index 046ba921d0..d8f607001b 100644 --- a/compiler/qsc_qasm3/src/types.rs +++ b/compiler/qsc_qasm3/src/types.rs @@ -99,6 +99,7 @@ impl Complex { #[derive(Debug, Clone, Default, PartialEq, Eq)] pub enum Type { + Angle(bool), Bool(bool), BigInt(bool), Complex(bool), @@ -177,6 +178,7 @@ impl From<&crate::semantic::types::ArrayDimensions> for ArrayDimensions { 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"), diff --git a/pip/src/interop.rs b/pip/src/interop.rs index c6296cb807..4a6cd01f62 100644 --- a/pip/src/interop.rs +++ b/pip/src/interop.rs @@ -8,6 +8,7 @@ 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; @@ -17,8 +18,7 @@ use qsc::qasm3::{ }; 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}; @@ -539,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, From cbf1e12d658a64daa890e28c0916012c1e8bffd0 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Tue, 1 Apr 2025 06:11:15 -0700 Subject: [PATCH 71/98] Lower and compile complex binary ops (#2268) --- compiler/qsc_qasm3/src/compiler.rs | 38 +++++- compiler/qsc_qasm3/src/semantic/lowerer.rs | 16 ++- .../src/tests/expression/binary/complex.rs | 120 ++++++++++++++---- 3 files changed, 141 insertions(+), 33 deletions(-) diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index d3f36066cc..c997016539 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -400,8 +400,8 @@ impl QasmCompiler { 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 angles correctly. - if matches!(&stmt.lhs.ty, Type::Angle(..)) { + // 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, @@ -1095,6 +1095,12 @@ impl QasmCompiler { 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()) } @@ -1162,6 +1168,34 @@ impl QasmCompiler { 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), diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index f9d2755877..14071415a1 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -2745,11 +2745,17 @@ impl Lowerer { let expr = if matches!(ty, Type::Complex(..)) { if is_complex_binop_supported(op) { - // TODO: How do we handle complex binary expressions? - // this is going to be a call to a built-in function - // that doesn't map to qasm def semantics - self.push_unimplemented_error_message("complex binary exprs", span); - err_expr!(ty.clone(), span) + 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:?}"), diff --git a/compiler/qsc_qasm3/src/tests/expression/binary/complex.rs b/compiler/qsc_qasm3/src/tests/expression/binary/complex.rs index 869e58d936..fb70121884 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary/complex.rs +++ b/compiler/qsc_qasm3/src/tests/expression/binary/complex.rs @@ -2,10 +2,41 @@ // Licensed under the MIT License. use crate::tests::compile_qasm_stmt_to_qsharp; - use expect_test::expect; use miette::Report; +#[test] +fn addition() -> miette::Result<(), Vec> { + let source = " + input complex[float] a; + input complex[float] b; + complex x = (a + b); + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + mutable x = (Microsoft.Quantum.Math.PlusC(a, b)); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn addition_assign_op() -> miette::Result<(), Vec> { + let source = " + input complex[float] a; + complex x = 0.0; + x += a; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + set x = Microsoft.Quantum.Math.PlusC(x, a); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + #[test] fn subtraction() -> miette::Result<(), Vec> { let source = " @@ -15,29 +46,25 @@ fn subtraction() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" + expect![[r#" mutable x = (Microsoft.Quantum.Math.MinusC(a, b)); - "# - ] + "#]] .assert_eq(&qsharp); Ok(()) } #[test] -fn addition() -> miette::Result<(), Vec> { +fn subtraction_assign_op() -> miette::Result<(), Vec> { let source = " input complex[float] a; - input complex[float] b; - complex x = (a + b); + 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(()) } @@ -51,11 +78,25 @@ fn multiplication() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" + 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(()) } @@ -69,17 +110,30 @@ fn division() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" + 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; @@ -88,11 +142,25 @@ fn power() -> miette::Result<(), Vec> { "; let qsharp = compile_qasm_stmt_to_qsharp(source)?; - expect![ - r#" + expect![[r#" mutable x = (Microsoft.Quantum.Math.PowC(a, b)); - "# - ] + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn power_assign_op() -> miette::Result<(), Vec> { + let source = " + input complex[float] a; + complex x = 0.0; + x **= a; + "; + + let qsharp = compile_qasm_stmt_to_qsharp(source)?; + expect![[r#" + set x = Microsoft.Quantum.Math.PowC(x, a); + "#]] .assert_eq(&qsharp); Ok(()) } From 30f13341a6451d18c624c2a15db80eedb4b4a0e8 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Tue, 1 Apr 2025 06:16:30 -0700 Subject: [PATCH 72/98] Fixing angle input/output (#2269) --- compiler/qsc_qasm3/src/ast_builder.rs | 59 ++++--- compiler/qsc_qasm3/src/compile.rs | 2 +- compiler/qsc_qasm3/src/compiler.rs | 149 +++++++++++++----- .../tests/declaration/io/explicit_input.rs | 16 ++ .../tests/declaration/io/explicit_output.rs | 35 ++-- .../tests/declaration/io/implicit_output.rs | 35 ++-- .../src/tests/expression/binary/comparison.rs | 42 ++--- .../qsc_qasm3/src/tests/expression/bits.rs | 12 +- compiler/qsc_qasm3/src/tests/output.rs | 24 +-- .../qsc_qasm3/src/tests/statement/include.rs | 6 +- .../qsc_qasm3/src/tests/statement/reset.rs | 6 +- .../qsc_qasm3/src/tests/statement/switch.rs | 6 +- 12 files changed, 255 insertions(+), 137 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm3/src/ast_builder.rs index f69686fe17..ccdf6e92c2 100644 --- a/compiler/qsc_qasm3/src/ast_builder.rs +++ b/compiler/qsc_qasm3/src/ast_builder.rs @@ -988,6 +988,17 @@ 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(), @@ -995,7 +1006,7 @@ pub(crate) fn build_qasm_import_decl() -> Vec { ] } -pub(crate) fn build_qasm_import_decl_angle() -> Stmt { +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")), @@ -1009,21 +1020,16 @@ pub(crate) fn build_qasm_import_decl_angle() -> Stmt { is_glob: true, }]; let decl = ImportOrExportDecl::new(Span::default(), items.into_boxed_slice(), false); - let item = Item { + Item { id: NodeId::default(), span: Span::default(), kind: Box::new(ItemKind::ImportOrExport(decl)), doc: "".into(), attrs: Box::new([]), - }; - Stmt { - kind: Box::new(StmtKind::Item(Box::new(item))), - span: Span::default(), - id: NodeId::default(), } } -pub(crate) fn build_qasm_import_decl_convert() -> Stmt { +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")), @@ -1037,21 +1043,16 @@ pub(crate) fn build_qasm_import_decl_convert() -> Stmt { is_glob: true, }]; let decl = ImportOrExportDecl::new(Span::default(), items.into_boxed_slice(), false); - let item = Item { + Item { id: NodeId::default(), span: Span::default(), kind: Box::new(ItemKind::ImportOrExport(decl)), doc: "".into(), attrs: Box::new([]), - }; - Stmt { - kind: Box::new(StmtKind::Item(Box::new(item))), - span: Span::default(), - id: NodeId::default(), } } -pub(crate) fn build_qasm_import_decl_intrinsic() -> Stmt { +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")), @@ -1065,17 +1066,12 @@ pub(crate) fn build_qasm_import_decl_intrinsic() -> Stmt { is_glob: true, }]; let decl = ImportOrExportDecl::new(Span::default(), items.into_boxed_slice(), false); - let item = Item { + Item { id: NodeId::default(), span: Span::default(), kind: Box::new(ItemKind::ImportOrExport(decl)), doc: "".into(), attrs: Box::new([]), - }; - Stmt { - kind: Box::new(StmtKind::Item(Box::new(item))), - span: Span::default(), - id: NodeId::default(), } } @@ -1190,10 +1186,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(), @@ -1204,23 +1200,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(), diff --git a/compiler/qsc_qasm3/src/compile.rs b/compiler/qsc_qasm3/src/compile.rs index d7d0c36824..f71a4f5637 100644 --- a/compiler/qsc_qasm3/src/compile.rs +++ b/compiler/qsc_qasm3/src/compile.rs @@ -3697,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, ) } diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index c997016539..90b10ac1a4 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -22,9 +22,9 @@ use crate::{ 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_qasm_import_decl, 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_item, build_tuple_expr, + build_path_ident_expr, 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, @@ -94,9 +94,18 @@ impl QasmCompiler { /// source file and build the appropriate package based on the /// configuration. pub fn compile(mut self, program: &crate::semantic::ast::Program) -> QasmCompileUnit { - self.append_runtime_import_decls(); - self.compile_stmts(&program.statements); + // 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(), @@ -114,7 +123,9 @@ impl QasmCompiler { let (operation, mut signature) = self.create_entry_operation(operation_name, whole_span); let ns = self.config.namespace(); signature.ns = Some(ns.to_string()); - let top = build_top_level_ns_with_item(whole_span, ns, operation); + 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]), @@ -164,6 +175,22 @@ impl QasmCompiler { ) -> (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, @@ -175,6 +202,7 @@ impl QasmCompiler { ) } + #[allow(clippy::too_many_lines)] fn create_entry_item>( &mut self, name: S, @@ -192,6 +220,60 @@ impl QasmCompiler { 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![]) @@ -214,7 +296,21 @@ impl QasmCompiler { output .iter() .map(|symbol| { - build_path_ident_expr(symbol.name.as_str(), symbol.span, symbol.span) + 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::>() }; @@ -233,7 +329,13 @@ impl QasmCompiler { } else { output .iter() - .map(|symbol| symbol.qsharp_ty.clone()) + .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::>() }; @@ -255,36 +357,7 @@ impl QasmCompiler { } crate::types::Type::Tuple(vec![]) }; - - let ast_ty = map_qsharp_type_to_ast_ty(&output_ty); - signature.output = format!("{output_ty}"); - // TODO: This can create a collision on multiple compiles when interactive - // We also have issues with the new entry point inference logic - let input_desc = input - .iter() - .flat_map(|s| { - s.iter() - .map(|s| (s.name.to_string(), format!("{}", s.qsharp_ty))) - }) - .collect::>(); - signature.input = input_desc; - let input_pats = input - .into_iter() - .flat_map(|s| { - s.into_iter().map(|s| { - build_arg_pat( - s.name.clone(), - s.span, - map_qsharp_type_to_ast_ty(&s.qsharp_ty), - ) - }) - }) - .collect::>(); - - ( - build_operation_with_stmts(name, input_pats, ast_ty, stmts, whole_span), - signature, - ) + output_ty } /// Appends the runtime imports to the compiled statements. 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 39c4fe404d..7224dc346f 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/io/explicit_input.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/io/explicit_input.rs @@ -220,6 +220,22 @@ input qubit q; .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] fn order_is_preserved_with_multiple_inputs() -> miette::Result<(), Vec> { let source = r#" 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 ce241820d9..ea8ac75890 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/io/explicit_output.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/io/explicit_output.rs @@ -13,7 +13,6 @@ output bit[2] c; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Result[] { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -34,7 +33,6 @@ output bit c; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Result { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -55,7 +53,6 @@ output bool c; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Bool { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -76,7 +73,6 @@ output complex[float] c; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Microsoft.Quantum.Math.Complex { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -97,7 +93,6 @@ output float f; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Double { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -118,7 +113,6 @@ output float[42] f; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Double { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -139,7 +133,6 @@ output int[42] i; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Int { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -160,7 +153,6 @@ output int i; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Int { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -181,7 +173,6 @@ output uint i; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Int { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -202,7 +193,6 @@ output uint[42] i; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Int { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -223,7 +213,6 @@ output int[65] i; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : BigInt { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -267,7 +256,6 @@ 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[]) { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -287,3 +275,26 @@ output bit[2] 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 88db764e6d..79b7291ec8 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/io/implicit_output.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/io/implicit_output.rs @@ -13,7 +13,6 @@ bit[2] c; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Result[] { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -34,7 +33,6 @@ bit c; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Result { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -55,7 +53,6 @@ bool c; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Bool { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -76,7 +73,6 @@ complex[float] c; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Microsoft.Quantum.Math.Complex { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -97,7 +93,6 @@ float f; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Double { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -118,7 +113,6 @@ float[42] f; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Double { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -139,7 +133,6 @@ int[42] i; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Int { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -160,7 +153,6 @@ int i; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Int { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -181,7 +173,6 @@ uint i; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Int { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -202,7 +193,6 @@ uint[42] i; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : Int { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -223,7 +213,6 @@ int[65] i; let qsharp = compile_qasm_to_qsharp_operation(source)?; expect![[r#" - @EntryPoint() operation Test() : BigInt { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -252,7 +241,6 @@ 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[]) { import QasmStd.Angle.*; import QasmStd.Convert.*; @@ -272,3 +260,26 @@ bit[2] 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/expression/binary/comparison.rs b/compiler/qsc_qasm3/src/tests/expression/binary/comparison.rs index e07e28d457..a14625c864 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary/comparison.rs +++ b/compiler/qsc_qasm3/src/tests/expression/binary/comparison.rs @@ -25,11 +25,11 @@ fn int_var_comparisons_can_be_translated() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp_file(source)?; 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) { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; mutable x = 5; mutable y = 3; mutable f = (x > y); @@ -61,11 +61,11 @@ fn uint_var_comparisons_can_be_translated() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp_file(source)?; 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) { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; mutable x = 5; mutable y = 3; mutable f = (x > y); @@ -97,11 +97,11 @@ fn bit_var_comparisons_can_be_translated() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp_file(source)?; 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) { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; mutable x = One; mutable y = Zero; mutable f = (x > y); @@ -133,11 +133,11 @@ fn bitarray_var_comparisons_can_be_translated() -> miette::Result<(), Vec __ResultArrayAsIntBE__(y)); @@ -175,10 +175,10 @@ fn bitarray_var_comparison_to_int_can_be_translated() -> miette::Result<(), Vec< let qsharp = compile_qasm_to_qsharp_file(source)?; 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) { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; mutable x = [One]; mutable a = (__ResultArrayAsIntBE__(x) > y); mutable b = (__ResultArrayAsIntBE__(x) >= y); @@ -215,11 +215,11 @@ fn float_var_comparisons_can_be_translated() -> miette::Result<(), Vec> let qsharp = compile_qasm_to_qsharp_file(source)?; 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) { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; mutable x = 5.; mutable y = 3.; mutable f = (x > y); @@ -253,11 +253,11 @@ fn bool_var_comparisons_can_be_translated() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp_file(source)?; 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) { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; mutable x = true; mutable y = false; mutable a = (x and y); diff --git a/compiler/qsc_qasm3/src/tests/expression/bits.rs b/compiler/qsc_qasm3/src/tests/expression/bits.rs index 21e138626b..6751290904 100644 --- a/compiler/qsc_qasm3/src/tests/expression/bits.rs +++ b/compiler/qsc_qasm3/src/tests/expression/bits.rs @@ -25,14 +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[]) { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; 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]; @@ -47,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(()) } diff --git a/compiler/qsc_qasm3/src/tests/output.rs b/compiler/qsc_qasm3/src/tests/output.rs index 0e9bcac15b..f3cd433dbc 100644 --- a/compiler/qsc_qasm3/src/tests/output.rs +++ b/compiler/qsc_qasm3/src/tests/output.rs @@ -40,10 +40,10 @@ fn using_re_semantics_removes_output() -> miette::Result<(), Vec> { 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 { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; mutable c = [Zero, Zero]; let q = QIR.Runtime.AllocateQubitArray(2); mutable gamma = 0.; @@ -91,10 +91,10 @@ fn using_qasm_semantics_captures_all_classical_decls_as_output() -> miette::Resu 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) { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; mutable c = [Zero, Zero]; let q = QIR.Runtime.AllocateQubitArray(2); mutable gamma = 0.; @@ -142,10 +142,10 @@ fn using_qiskit_semantics_only_bit_array_is_captured_and_reversed( 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[] { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; mutable c = [Zero, Zero]; let q = QIR.Runtime.AllocateQubitArray(2); mutable gamma = 0.; @@ -201,10 +201,10 @@ c2[2] = measure q[4]; 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[]) { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; mutable c = [Zero, Zero]; mutable c2 = [Zero, Zero, Zero]; let q = QIR.Runtime.AllocateQubitArray(5); diff --git a/compiler/qsc_qasm3/src/tests/statement/include.rs b/compiler/qsc_qasm3/src/tests/statement/include.rs index 3c95d89622..20f1abb5db 100644 --- a/compiler/qsc_qasm3/src/tests/statement/include.rs +++ b/compiler/qsc_qasm3/src/tests/statement/include.rs @@ -40,11 +40,11 @@ fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { 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[] { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; let my_gate : (Qubit) => Unit = (q) => { x(q); }; diff --git a/compiler/qsc_qasm3/src/tests/statement/reset.rs b/compiler/qsc_qasm3/src/tests/statement/reset.rs index fa4689dd25..d0ef520368 100644 --- a/compiler/qsc_qasm3/src/tests/statement/reset.rs +++ b/compiler/qsc_qasm3/src/tests/statement/reset.rs @@ -35,11 +35,11 @@ fn reset_calls_are_generated_from_qasm() -> miette::Result<(), Vec> { let qsharp = gen_qsharp(&unit.package.expect("no package found")); expect![[r#" namespace qasm3_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; @EntryPoint() operation Test() : Result[] { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; mutable meas = [Zero]; let q = QIR.Runtime.AllocateQubitArray(1); Reset(q[0]); diff --git a/compiler/qsc_qasm3/src/tests/statement/switch.rs b/compiler/qsc_qasm3/src/tests/statement/switch.rs index 874d753bdb..1c8674c2d8 100644 --- a/compiler/qsc_qasm3/src/tests/statement/switch.rs +++ b/compiler/qsc_qasm3/src/tests/statement/switch.rs @@ -191,11 +191,11 @@ fn spec_case_3() -> miette::Result<(), Vec> { let qsharp = compile_qasm_to_qsharp_file(source)?; expect![[r#" namespace qasm3_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; @EntryPoint() operation Test() : Result[] { - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); mutable b = [Zero, Zero]; if __ResultArrayAsIntBE__(b) == 0 { From 67b0161a69910fa02fd6e20bbe490eb6df0aa689 Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Tue, 1 Apr 2025 07:35:34 -0700 Subject: [PATCH 73/98] Fix scoping resolution for defs --- compiler/qsc_qasm3/src/semantic/symbols.rs | 6 +- compiler/qsc_qasm3/src/tests/scopes.rs | 170 +++++++++++++++++++++ 2 files changed, 174 insertions(+), 2 deletions(-) diff --git a/compiler/qsc_qasm3/src/semantic/symbols.rs b/compiler/qsc_qasm3/src/semantic/symbols.rs index 11a0a01dd6..b954a1bdd8 100644 --- a/compiler/qsc_qasm3/src/semantic/symbols.rs +++ b/compiler/qsc_qasm3/src/semantic/symbols.rs @@ -447,7 +447,7 @@ impl SymbolTable { 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) + || matches!(symbol.ty, Type::Gate(..) | Type::Void | Type::Function(..)) || self.is_scope_rooted_in_global() { return Some((id, symbol)); @@ -457,7 +457,9 @@ impl SymbolTable { // 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) { + if symbol.ty.is_const() + || matches!(symbol.ty, Type::Gate(..) | Type::Void | Type::Function(..)) + { return Some((id, symbol)); } } diff --git a/compiler/qsc_qasm3/src/tests/scopes.rs b/compiler/qsc_qasm3/src/tests/scopes.rs index 7651ee223d..4f5bbd49ff 100644 --- a/compiler/qsc_qasm3/src/tests/scopes.rs +++ b/compiler/qsc_qasm3/src/tests/scopes.rs @@ -54,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.*; + let my_h : (Qubit) => Unit = (q) => { + h(q); + }; + let my_hx : (Qubit) => Unit = (q) => { + 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.*; + let apply_h : (Qubit) => Unit = (q) => { + h(q); + }; + let apply_hx : (Qubit) => Unit = (q) => { + 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.*; + let apply_h : (Qubit) => Unit = (q) => { + h(q); + }; + let my_hx : (Qubit) => Unit = (q) => { + 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.*; + let my_h : (Qubit) => Unit = (q) => { + h(q); + }; + let apply_hx : (Qubit) => Unit = (q) => { + 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.*; + let apply_hx : (Int, Qubit) => Unit = (limit, q) => { + 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(()) +} From 828d570c2b91f07080a3e95c821a2d72108b13b5 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Tue, 1 Apr 2025 10:44:41 -0700 Subject: [PATCH 74/98] functions and operations with const eval (#2270) Compile functions and operations const evaluating any references to symbol in an external scope. --- compiler/qsc_qasm3/src/ast_builder.rs | 108 ++++++++++++- compiler/qsc_qasm3/src/compiler.rs | 36 +++-- compiler/qsc_qasm3/src/semantic/lowerer.rs | 42 ++++- compiler/qsc_qasm3/src/semantic/symbols.rs | 33 +++- .../qsc_qasm3/src/tests/declaration/def.rs | 14 +- .../qsc_qasm3/src/tests/declaration/gate.rs | 28 ++-- .../src/tests/expression/function_call.rs | 28 ++-- compiler/qsc_qasm3/src/tests/scopes.rs | 42 ++--- .../src/tests/statement/annotation.rs | 4 +- .../src/tests/statement/gate_call.rs | 149 ++++++++++++++++++ .../qsc_qasm3/src/tests/statement/include.rs | 4 +- .../qsc_qasm3/src/tests/statement/switch.rs | 4 +- 12 files changed, 405 insertions(+), 87 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm3/src/ast_builder.rs index ccdf6e92c2..ce7eed4c2d 100644 --- a/compiler/qsc_qasm3/src/ast_builder.rs +++ b/compiler/qsc_qasm3/src/ast_builder.rs @@ -7,9 +7,9 @@ use num_bigint::BigInt; use qsc_ast::ast::{ self, Attr, Block, CallableBody, CallableDecl, CallableKind, Expr, ExprKind, FieldAssign, - Ident, ImportOrExportDecl, ImportOrExportItem, Item, ItemKind, Lit, Mutability, NodeId, Pat, - PatKind, Path, PathKind, QubitInit, QubitInitKind, QubitSource, Stmt, StmtKind, TopLevelNode, - Ty, TyKind, + 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; @@ -1691,6 +1691,108 @@ pub(crate) fn build_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: Option, + kind: CallableKind, + functors: Option, +) -> 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 return_type = if let Some(ty) = return_type { + ty + } else { + build_path_ident_ty("Unit") + }; + + 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))), + ..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() diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index 90b10ac1a4..7b99e2b040 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -10,20 +10,22 @@ use qsc_frontend::{compile::SourceMap, error::WithSource}; use crate::{ ast_builder::{ - build_arg_pat, build_array_reverse_expr, build_assignment_statement, 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_expr_array_expr, build_for_stmt, 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_lambda, - 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_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_adj_plus_ctl_functor, build_arg_pat, build_array_reverse_expr, + build_assignment_statement, 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_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_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, @@ -627,7 +629,7 @@ impl QasmCompiler { // 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_lambda( + Some(build_function_or_operation( name, cargs, vec![], @@ -637,6 +639,7 @@ impl QasmCompiler { stmt.span, return_type, kind, + None, )) } @@ -899,7 +902,7 @@ impl QasmCompiler { let body = Some(self.compile_block(&stmt.body)); - Some(build_lambda( + Some(build_function_or_operation( name, cargs, qargs, @@ -909,6 +912,7 @@ impl QasmCompiler { stmt.span, None, qsast::CallableKind::Operation, + Some(build_adj_plus_ctl_functor()), )) } diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index 14071415a1..c34b73dcb5 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -257,7 +257,7 @@ impl Lowerer { gate_symbol("cx", 0, 2), gate_symbol("cy", 0, 2), gate_symbol("cz", 0, 2), - gate_symbol("cp", 0, 2), + gate_symbol("cp", 1, 2), gate_symbol("swap", 0, 2), gate_symbol("ccx", 0, 3), gate_symbol("cu", 4, 2), @@ -520,7 +520,45 @@ impl Lowerer { let (symbol_id, symbol) = self.try_get_existing_or_insert_err_symbol(&name, ident.span); - let kind = semantic::ExprKind::Ident(symbol_id); + // 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.symbols) { + semantic::ExprKind::Lit(val) + } else { + self.push_semantic_error(SemanticErrorKind::ExprMustBeConst( + ident.name.to_string(), + ident.span, + )); + semantic::ExprKind::Err + } + } else { + semantic::ExprKind::Ident(symbol_id) + }; + semantic::Expr { span: ident.span, kind: Box::new(kind), diff --git a/compiler/qsc_qasm3/src/semantic/symbols.rs b/compiler/qsc_qasm3/src/semantic/symbols.rs index b954a1bdd8..0da5f644e7 100644 --- a/compiler/qsc_qasm3/src/semantic/symbols.rs +++ b/compiler/qsc_qasm3/src/semantic/symbols.rs @@ -2,10 +2,9 @@ // Licensed under the MIT License. use core::f64; -use std::rc::Rc; - use qsc_data_structures::{index_map::IndexMap, span::Span}; use rustc_hash::FxHashMap; +use std::rc::Rc; use super::{ ast::{Expr, ExprKind, LiteralKind}, @@ -137,6 +136,12 @@ impl Symbol { } } + /// 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 { @@ -468,6 +473,22 @@ impl SymbolTable { 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) @@ -481,6 +502,14 @@ impl SymbolTable { .any(|scope| scope.kind == ScopeKind::Function) } + #[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_global(&self) -> bool { for scope in self.scopes.iter().rev() { diff --git a/compiler/qsc_qasm3/src/tests/declaration/def.rs b/compiler/qsc_qasm3/src/tests/declaration/def.rs index 6fa2418435..6f30a22b97 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/def.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/def.rs @@ -11,7 +11,7 @@ fn no_parameters_no_return() -> miette::Result<(), Vec> { let qsharp = compile_qasm_stmt_to_qsharp(source)?; expect![[r#" - let empty : () -> Unit = () -> {}; + function empty() : Unit {} "#]] .assert_eq(&qsharp); Ok(()) @@ -27,9 +27,9 @@ fn single_parameter() -> miette::Result<(), Vec> { let qsharp = compile_qasm_stmt_to_qsharp(source)?; expect![[r#" - let square : (Int) -> Int = (x) -> { + function square(x : Int) : Int { return x * x; - }; + } "#]] .assert_eq(&qsharp); Ok(()) @@ -45,9 +45,9 @@ fn qubit_parameter() -> miette::Result<(), Vec> { let qsharp = compile_qasm_stmt_to_qsharp(source)?; expect![[r#" - let square : (Qubit) => Int = (q) => { + operation square(q : Qubit) : Int { return 1; - }; + } "#]] .assert_eq(&qsharp); Ok(()) @@ -63,9 +63,9 @@ fn qubit_array_parameter() -> miette::Result<(), Vec> { let qsharp = compile_qasm_stmt_to_qsharp(source)?; expect![[r#" - let square : (Qubit[]) => Int = (qs) => { + operation square(qs : Qubit[]) : Int { return 1; - }; + } "#]] .assert_eq(&qsharp); Ok(()) diff --git a/compiler/qsc_qasm3/src/tests/declaration/gate.rs b/compiler/qsc_qasm3/src/tests/declaration/gate.rs index 7795720a1b..5f3e98d5bd 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) => { + 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) => { + expect![[r#" + operation my_h(q : Qubit, q2 : Qubit) : Unit is Adj + Ctl { h(q2); h(q); - }; - "# - ] + } + "#]] .assert_eq(&qsharp); Ok(()) } @@ -60,9 +56,9 @@ fn single_angle_single_qubit() -> miette::Result<(), Vec> { let qsharp = compile_qasm_stmt_to_qsharp(source)?; expect![[r#" - let my_h : (__Angle__, Qubit) => Unit = (θ, q) => { + operation my_h(θ : __Angle__, q : Qubit) : Unit is Adj + Ctl { rx(θ, q); - }; + } "#]] .assert_eq(&qsharp); Ok(()) @@ -80,10 +76,10 @@ fn two_angles_two_qubits() -> miette::Result<(), Vec> { let qsharp = compile_qasm_stmt_to_qsharp(source)?; expect![[r#" - let my_h : (__Angle__, __Angle__, Qubit, Qubit) => Unit = (θ, φ, q, q2) => { + operation my_h(θ : __Angle__, φ : __Angle__, q : Qubit, q2 : Qubit) : Unit is Adj + Ctl { rx(θ, q2); ry(φ, q); - }; + } "#]] .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 index 8347947792..a679d76f82 100644 --- a/compiler/qsc_qasm3/src/tests/expression/function_call.rs +++ b/compiler/qsc_qasm3/src/tests/expression/function_call.rs @@ -17,7 +17,7 @@ fn funcall_with_no_arguments_generates_correct_qsharp() -> miette::Result<(), Ve import QasmStd.Angle.*; import QasmStd.Convert.*; import QasmStd.Intrinsic.*; - let empty : () -> Unit = () -> {}; + function empty() : Unit {} empty(); "#]] .assert_eq(&qsharp); @@ -36,7 +36,7 @@ fn void_function_with_one_argument_generates_correct_qsharp() -> miette::Result< import QasmStd.Angle.*; import QasmStd.Convert.*; import QasmStd.Intrinsic.*; - let f : (Int) -> Unit = (x) -> {}; + function f(x : Int) : Unit {} f(2); "#]] .assert_eq(&qsharp); @@ -58,9 +58,9 @@ fn funcall_with_one_argument_generates_correct_qsharp() -> miette::Result<(), Ve import QasmStd.Angle.*; import QasmStd.Convert.*; import QasmStd.Intrinsic.*; - let square : (Int) -> Int = (x) -> { + function square(x : Int) : Int { return x * x; - }; + } square(2); "#]] .assert_eq(&qsharp); @@ -82,9 +82,9 @@ fn funcall_with_two_arguments_generates_correct_qsharp() -> miette::Result<(), V import QasmStd.Angle.*; import QasmStd.Convert.*; import QasmStd.Intrinsic.*; - let sum : (Int, Int) -> Int = (x, y) -> { + function sum(x : Int, y : Int) : Int { return x + y; - }; + } sum(2, 3); "#]] .assert_eq(&qsharp); @@ -109,7 +109,7 @@ fn funcall_with_qubit_argument() -> miette::Result<(), Vec> { import QasmStd.Angle.*; import QasmStd.Convert.*; import QasmStd.Intrinsic.*; - let parity : (Qubit[]) => Result = (qs) => { + 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 { @@ -117,7 +117,7 @@ fn funcall_with_qubit_argument() -> miette::Result<(), Vec> { } else { Zero }; - }; + } let qs = QIR.Runtime.AllocateQubitArray(2); mutable p = parity(qs); "#]] @@ -198,9 +198,9 @@ fn funcall_accepts_qubit_argument() -> miette::Result<(), Vec> { import QasmStd.Angle.*; import QasmStd.Convert.*; import QasmStd.Intrinsic.*; - let h_wrapper : (Qubit) => Unit = (q) => { + operation h_wrapper(q : Qubit) : Unit { h(q); - }; + } let q = QIR.Runtime.__quantum__rt__qubit_allocate(); h_wrapper(q); "#]] @@ -223,9 +223,9 @@ fn classical_decl_initialized_with_funcall() -> miette::Result<(), Vec> import QasmStd.Angle.*; import QasmStd.Convert.*; import QasmStd.Intrinsic.*; - let square : (Int) -> Int = (x) -> { + function square(x : Int) : Int { return x * x; - }; + } mutable a = square(2); "#]] .assert_eq(&qsharp); @@ -275,9 +275,9 @@ fn funcall_implicit_arg_cast_uint_to_bitarray() -> miette::Result<(), Vec Result = (arr) -> { + function parity(arr : Result[]) : Result { return 1; - }; + } mutable p = parity(__IntAsResultArrayBE__(2, 2)); "#]] .assert_eq(&qsharp); diff --git a/compiler/qsc_qasm3/src/tests/scopes.rs b/compiler/qsc_qasm3/src/tests/scopes.rs index 4f5bbd49ff..c200ad0ffd 100644 --- a/compiler/qsc_qasm3/src/tests/scopes.rs +++ b/compiler/qsc_qasm3/src/tests/scopes.rs @@ -25,11 +25,11 @@ fn can_access_const_decls_from_global_scope() -> miette::Result<(), Vec> import QasmStd.Convert.*; import QasmStd.Intrinsic.*; let i = 7; - let my_h : (Qubit) => Unit = (q) => { - if i == 0 { + 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); "#]] @@ -75,13 +75,13 @@ fn gates_can_call_previously_declared_gates() -> miette::Result<(), Vec> import QasmStd.Angle.*; import QasmStd.Convert.*; import QasmStd.Intrinsic.*; - let my_h : (Qubit) => Unit = (q) => { + operation my_h(q : Qubit) : Unit is Adj + Ctl { h(q); - }; - let my_hx : (Qubit) => Unit = (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); "#]] @@ -109,13 +109,13 @@ fn def_can_call_previously_declared_def() -> miette::Result<(), Vec> { import QasmStd.Angle.*; import QasmStd.Convert.*; import QasmStd.Intrinsic.*; - let apply_h : (Qubit) => Unit = (q) => { + operation apply_h(q : Qubit) : Unit { h(q); - }; - let apply_hx : (Qubit) => Unit = (q) => { + } + operation apply_hx(q : Qubit) : Unit { apply_h(q); x(q); - }; + } let q = QIR.Runtime.__quantum__rt__qubit_allocate(); apply_hx(q); "#]] @@ -143,13 +143,13 @@ fn gate_can_call_previously_declared_def() -> miette::Result<(), Vec> { import QasmStd.Angle.*; import QasmStd.Convert.*; import QasmStd.Intrinsic.*; - let apply_h : (Qubit) => Unit = (q) => { + operation apply_h(q : Qubit) : Unit { h(q); - }; - let my_hx : (Qubit) => Unit = (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); "#]] @@ -177,13 +177,13 @@ fn def_can_call_previously_declared_gate() -> miette::Result<(), Vec> { import QasmStd.Angle.*; import QasmStd.Convert.*; import QasmStd.Intrinsic.*; - let my_h : (Qubit) => Unit = (q) => { + operation my_h(q : Qubit) : Unit is Adj + Ctl { h(q); - }; - let apply_hx : (Qubit) => Unit = (q) => { + } + operation apply_hx(q : Qubit) : Unit { my_h(q); x(q); - }; + } let q = QIR.Runtime.__quantum__rt__qubit_allocate(); apply_hx(q); "#]] @@ -211,13 +211,13 @@ fn def_can_call_itself_recursively() -> miette::Result<(), Vec> { import QasmStd.Angle.*; import QasmStd.Convert.*; import QasmStd.Intrinsic.*; - let apply_hx : (Int, Qubit) => Unit = (limit, q) => { + 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); "#]] diff --git a/compiler/qsc_qasm3/src/tests/statement/annotation.rs b/compiler/qsc_qasm3/src/tests/statement/annotation.rs index 1ad3d5ecb7..373002d9f7 100644 --- a/compiler/qsc_qasm3/src/tests/statement/annotation.rs +++ b/compiler/qsc_qasm3/src/tests/statement/annotation.rs @@ -22,8 +22,8 @@ fn simulatable_intrinsic_can_be_applied_to_gate() -> miette::Result<(), Vec miette::Result<(), Vec> { .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(()) +} diff --git a/compiler/qsc_qasm3/src/tests/statement/include.rs b/compiler/qsc_qasm3/src/tests/statement/include.rs index 20f1abb5db..383082d39b 100644 --- a/compiler/qsc_qasm3/src/tests/statement/include.rs +++ b/compiler/qsc_qasm3/src/tests/statement/include.rs @@ -45,9 +45,9 @@ fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { import QasmStd.Intrinsic.*; @EntryPoint() operation Test() : Result[] { - let my_gate : (Qubit) => Unit = (q) => { + operation my_gate(q : Qubit) : Unit is Adj + Ctl { x(q); - }; + } mutable c = [Zero]; let q = QIR.Runtime.AllocateQubitArray(1); my_gate(q[0]); diff --git a/compiler/qsc_qasm3/src/tests/statement/switch.rs b/compiler/qsc_qasm3/src/tests/statement/switch.rs index 1c8674c2d8..cafd8d2627 100644 --- a/compiler/qsc_qasm3/src/tests/statement/switch.rs +++ b/compiler/qsc_qasm3/src/tests/statement/switch.rs @@ -255,9 +255,9 @@ fn spec_case_4() -> miette::Result<(), Vec> { import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); mutable b = [Zero, Zero]; - let foo : (Int, Qubit[]) => Result = (i, d) => { + 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; From 48d6784df3678c863f09d8fb9543443341a7d239 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Tue, 1 Apr 2025 12:13:14 -0700 Subject: [PATCH 75/98] Compile annotations into attributes (#2271) --- compiler/qsc_qasm3/src/ast_builder.rs | 75 ++++++++++++----- compiler/qsc_qasm3/src/compile.rs | 2 +- compiler/qsc_qasm3/src/compiler.rs | 82 +++++++++++++++---- compiler/qsc_qasm3/src/parser/error.rs | 2 +- compiler/qsc_qasm3/src/parser/stmt.rs | 15 ++-- compiler/qsc_qasm3/src/semantic/error.rs | 2 +- compiler/qsc_qasm3/src/semantic/lowerer.rs | 43 ++-------- .../src/tests/statement/annotation.rs | 78 +++++++++++++++++- compiler/qsc_qasm3/src/tests/statement/end.rs | 19 +++-- .../qsc_qasm3/src/tests/statement/include.rs | 1 + 10 files changed, 226 insertions(+), 93 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm3/src/ast_builder.rs index ce7eed4c2d..e0cb35e55f 100644 --- a/compiler/qsc_qasm3/src/ast_builder.rs +++ b/compiler/qsc_qasm3/src/ast_builder.rs @@ -14,7 +14,7 @@ use qsc_ast::ast::{ use qsc_data_structures::span::Span; use crate::{ - parser::ast::list_from_iter, + parser::ast::{list_from_iter, List}, runtime::RuntimeFunctions, stdlib::angle::Angle, types::{ArrayDimensions, Complex}, @@ -1459,15 +1459,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 { @@ -1484,19 +1483,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)>, @@ -1703,6 +1689,7 @@ pub(crate) fn build_function_or_operation( return_type: Option, kind: CallableKind, functors: Option, + attrs: List, ) -> Stmt { let args = cargs .into_iter() @@ -1763,6 +1750,7 @@ pub(crate) fn build_function_or_operation( let item = Item { span: gate_span, kind: Box::new(ast::ItemKind::Callable(Box::new(decl))), + attrs, ..Default::default() }; @@ -1807,3 +1795,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 f71a4f5637..0d499060af 100644 --- a/compiler/qsc_qasm3/src/compile.rs +++ b/compiler/qsc_qasm3/src/compile.rs @@ -416,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); diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index 7b99e2b040..eb709e4361 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -11,17 +11,17 @@ 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_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_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_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_qasm_import_decl, build_qasm_import_items, build_range_expr, build_reset_call, @@ -379,6 +379,19 @@ impl QasmCompiler { } 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), @@ -391,10 +404,10 @@ impl QasmCompiler { self.compile_calibration_grammar_stmt(stmt) } semast::StmtKind::ClassicalDecl(stmt) => self.compile_classical_decl(stmt), - semast::StmtKind::Def(stmt) => self.compile_def_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::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), @@ -405,7 +418,9 @@ impl QasmCompiler { 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(stmt) => self.compile_gate_decl_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), @@ -600,7 +615,11 @@ impl QasmCompiler { Some(stmt) } - fn compile_def_stmt(&mut self, stmt: &semast::DefStmt) -> Option { + 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(); @@ -627,6 +646,10 @@ impl QasmCompiler { 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( @@ -640,6 +663,7 @@ impl QasmCompiler { return_type, kind, None, + list_from_iter(attrs), )) } @@ -653,9 +677,8 @@ impl QasmCompiler { None } - fn compile_end_stmt(&mut self, stmt: &semast::EndStmt) -> Option { - self.push_unimplemented_error_message("end 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 { @@ -866,6 +889,7 @@ impl QasmCompiler { 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(); @@ -902,6 +926,10 @@ impl QasmCompiler { let body = Some(self.compile_block(&stmt.body)); + let attrs = annotations + .iter() + .filter_map(|annotation| self.compile_annotation(annotation)); + Some(build_function_or_operation( name, cargs, @@ -913,9 +941,27 @@ impl QasmCompiler { None, qsast::CallableKind::Operation, Some(build_adj_plus_ctl_functor()), + 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; diff --git a/compiler/qsc_qasm3/src/parser/error.rs b/compiler/qsc_qasm3/src/parser/error.rs index 30c4f81d55..99042af191 100644 --- a/compiler/qsc_qasm3/src/parser/error.rs +++ b/compiler/qsc_qasm3/src/parser/error.rs @@ -98,7 +98,7 @@ pub enum ErrorKind { #[error("Empty statements are not supported")] #[diagnostic(code("Qasm3.Parse.EmptyStatement"))] EmptyStatement(#[label] Span), - #[error("expected statement after annotation")] + #[error("Annotation missing target statement.")] #[diagnostic(code("Qasm3.Parse.FloatingAnnotation"))] FloatingAnnotation(#[label] Span), #[error("expected {0}, found {1}")] diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index a172af1be2..b909ef427a 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -156,11 +156,16 @@ pub(super) fn parse(s: &mut ParserContext) -> Result { } else if let Some(stmt) = opt(s, parse_measure_stmt)? { StmtKind::Measure(stmt) } else { - return Err(Error::new(ErrorKind::Rule( - "statement", - s.peek().kind, - s.peek().span, - ))); + 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 { diff --git a/compiler/qsc_qasm3/src/semantic/error.rs b/compiler/qsc_qasm3/src/semantic/error.rs index 404ae890b3..b9ed712721 100644 --- a/compiler/qsc_qasm3/src/semantic/error.rs +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -120,7 +120,7 @@ pub enum SemanticErrorKind { #[error("Indexed must be a single expression.")] #[diagnostic(code("Qsc.Qasm3.Compile.IndexMustBeSingleExpr"))] IndexMustBeSingleExpr(#[label] Span), - #[error("Annotations only valid on gate definitions.")] + #[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}.")] diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index c34b73dcb5..d476177763 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -198,7 +198,7 @@ impl Lowerer { 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::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), @@ -217,7 +217,7 @@ impl Lowerer { syntax::StmtKind::WhileLoop(stmt) => self.lower_while_stmt(stmt), syntax::StmtKind::Err => semantic::StmtKind::Err, }; - let annotations = self.lower_annotations(&stmt.annotations, &stmt.kind); + let annotations = Self::lower_annotations(&stmt.annotations); semantic::Stmt { span: stmt.span, annotations: syntax::list_from_iter(annotations), @@ -720,42 +720,14 @@ impl Lowerer { } } - fn lower_annotations( - &mut self, - annotations: &[Box], - kind: &syntax::StmtKind, - ) -> Vec { + fn lower_annotations(annotations: &[Box]) -> Vec { annotations .iter() - .map(|annotation| self.lower_annotation(annotation, kind)) + .map(|annotation| Self::lower_annotation(annotation)) .collect::>() } - fn lower_annotation( - &mut self, - annotation: &syntax::Annotation, - kind: &syntax::StmtKind, - ) -> semantic::Annotation { - if !matches!( - annotation.identifier.to_string().as_str(), - "SimulatableIntrinsic" | "Config" - ) { - self.push_unsupported_error_message( - format!("Annotation {}.", annotation.identifier), - annotation.span, - ); - } - - if let syntax::StmtKind::GateCall(_) = &kind { - self.push_unsupported_error_message( - format!( - "Annotation {} is only allowed on gate definitions.", - annotation.identifier - ), - annotation.span, - ); - } - + fn lower_annotation(annotation: &syntax::Annotation) -> semantic::Annotation { semantic::Annotation { span: annotation.span, identifier: annotation.identifier.clone(), @@ -1159,9 +1131,8 @@ impl Lowerer { semantic::StmtKind::Err } - fn lower_end_stmt(&mut self, stmt: &syntax::EndStmt) -> semantic::StmtKind { - self.push_unimplemented_error_message("end 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 { diff --git a/compiler/qsc_qasm3/src/tests/statement/annotation.rs b/compiler/qsc_qasm3/src/tests/statement/annotation.rs index 373002d9f7..bbedda3b53 100644 --- a/compiler/qsc_qasm3/src/tests/statement/annotation.rs +++ b/compiler/qsc_qasm3/src/tests/statement/annotation.rs @@ -16,8 +16,7 @@ fn simulatable_intrinsic_can_be_applied_to_gate() -> miette::Result<(), Vec 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/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/include.rs b/compiler/qsc_qasm3/src/tests/statement/include.rs index 383082d39b..5a7da1a6e8 100644 --- a/compiler/qsc_qasm3/src/tests/statement/include.rs +++ b/compiler/qsc_qasm3/src/tests/statement/include.rs @@ -45,6 +45,7 @@ fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { import QasmStd.Intrinsic.*; @EntryPoint() operation Test() : Result[] { + @SimulatableIntrinsic() operation my_gate(q : Qubit) : Unit is Adj + Ctl { x(q); } From f348aecba5595facaab3a457038db8a26577ab39 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Tue, 1 Apr 2025 13:07:28 -0700 Subject: [PATCH 76/98] unignore tests --- compiler/qsc_qasm3/src/ast_builder.rs | 3 + .../src/semantic/tests/decls/bool.rs | 20 +- .../src/semantic/tests/decls/complex.rs | 171 +++++---- .../tests/expression/binary/comparison.rs | 358 +++++++++++++++++- .../tests/expression/binary/complex.rs | 115 +++++- .../expression/implicit_cast_from_bitarray.rs | 293 +++++++++++++- .../expression/implicit_cast_from_float.rs | 122 +++--- .../qsc_qasm3/src/tests/declaration/float.rs | 3 - .../src/tests/declaration/integer.rs | 6 +- .../src/tests/declaration/unsigned_integer.rs | 6 +- .../qsc_qasm3/src/tests/expression/unary.rs | 24 +- .../src/tests/statement/const_eval.rs | 2 +- .../qsc_qasm3/src/tests/statement/if_stmt.rs | 28 +- 13 files changed, 949 insertions(+), 202 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm3/src/ast_builder.rs index e0cb35e55f..a4b23ac8a6 100644 --- a/compiler/qsc_qasm3/src/ast_builder.rs +++ b/compiler/qsc_qasm3/src/ast_builder.rs @@ -738,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, @@ -764,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() } } @@ -801,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() } } diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/bool.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/bool.rs index 895c0a6942..f7792018c6 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/bool.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/bool.rs @@ -28,19 +28,27 @@ fn with_no_init_expr_has_generated_lit_expr() { #[ignore = "Unimplemented"] fn array_with_no_init_expr_has_generated_lit_expr() { check_classical_decl( - "bool[4] a;", + "array[bool, 4] a;", &expect![[r#" Program: version: - statements: + 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: bool array - | default value + x this statement is not yet handled during OpenQASM 3 import: semantic type + | from array type ,-[test:1:1] - 1 | bool[4] a; - : ^^^^^^^^^ + 1 | array[bool, 4] a; + : ^^^^^^^^^^^^^^ `---- ]"#]], ); diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs index 6435cfa507..6cf6676fdb 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/complex.rs @@ -234,106 +234,147 @@ fn implicit_bitness_int_real_only() { } #[test] -#[ignore = "Requires support for binary operators"] fn implicit_bitness_simple_double_pos_im() { check_classical_decl( "complex[float] x = 1.1 + 2.2im;", &expect![[r#" - Program: - version: - statements: - - [Qsc.Qasm3.Compile.Unimplemented - - x this statement is not yet handled during OpenQASM 3 import: binary op expr - ,-[test:1:20] - 1 | complex[float] x = 1.1 + 2.2im; - : ^^^^^^^^^^^ - `---- - ]"#]], + 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] -#[ignore = "Requires support for binary operators"] fn implicit_bitness_simple_double_neg_im() { check_classical_decl( "complex[float] x = 1.1 - 2.2im;", &expect![[r#" - Program: - version: - statements: - - [Qsc.Qasm3.Compile.Unimplemented - - x this statement is not yet handled during OpenQASM 3 import: binary op expr - ,-[test:1:20] - 1 | complex[float] x = 1.1 - 2.2im; - : ^^^^^^^^^^^ - `---- - ]"#]], + 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] -#[ignore = "Requires support for binary operators"] fn const_implicit_bitness_simple_double_neg_im() { check_classical_decl( "const complex[float] x = 1.1 - 2.2im;", &expect![[r#" - Program: - version: - statements: - - [Qsc.Qasm3.Compile.Unimplemented - - x this statement is not yet handled during OpenQASM 3 import: binary op expr - ,-[test:1:26] - 1 | const complex[float] x = 1.1 - 2.2im; - : ^^^^^^^^^^^ - `---- - ]"#]], + 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] -#[ignore = "Requires support for binary operators"] fn implicit_bitness_simple_double_neg_real() { check_classical_decl( "complex[float] x = -1.1 + 2.2im;", &expect![[r#" - Program: - version: - statements: - - [Qsc.Qasm3.Compile.Unimplemented - - x this statement is not yet handled during OpenQASM 3 import: binary op expr - ,-[test:1:20] - 1 | complex[float] x = -1.1 + 2.2im; - : ^^^^^^^^^^^^ - `---- - ]"#]], + 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] -#[ignore = "Requires support for binary operators"] fn const_implicit_bitness_simple_double_neg_real() { check_classical_decl( "const complex[float] x = -1.1 + 2.2im;", &expect![[r#" - Program: - version: - statements: - - [Qsc.Qasm3.Compile.Unimplemented - - x this statement is not yet handled during OpenQASM 3 import: binary op expr - ,-[test:1:26] - 1 | const complex[float] x = -1.1 + 2.2im; - : ^^^^^^^^^^^^ - `---- - ]"#]], + 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/expression/binary/comparison.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison.rs index 1743c4c3ec..36c2847de0 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison.rs @@ -13,7 +13,6 @@ use expect_test::expect; use crate::semantic::tests::check_stmt_kinds; #[test] -#[ignore = "not yet implemented"] fn bitarray_var_comparisons_can_be_translated() { let input = r#" bit[1] x = "1"; @@ -26,11 +25,149 @@ fn bitarray_var_comparisons_can_be_translated() { bool d = x != y; "#; - check_stmt_kinds(input, &expect![[r#""#]]); + 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) + "#]]); } #[test] -#[ignore = "not yet implemented"] fn bitarray_var_comparison_to_int_can_be_translated() { let input = r#" bit[1] x = "1"; @@ -49,5 +186,218 @@ fn bitarray_var_comparison_to_int_can_be_translated() { bool l = y != x; "#; - check_stmt_kinds(input, &expect![[r#""#]]); + 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/complex.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs index 8e3f78604f..66fc8ce939 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs @@ -6,7 +6,6 @@ use expect_test::expect; use crate::semantic::tests::check_stmt_kinds; #[test] -#[ignore = "not yet implemented"] fn subtraction() { let input = " input complex[float] a; @@ -14,11 +13,30 @@ fn subtraction() { complex x = (a - b); "; - check_stmt_kinds(input, &expect![[r#""#]]); + 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: Sub + lhs: Expr [86-87]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [90-91]: + ty: Complex(None, false) + kind: SymbolId(9) + "#]]); } #[test] -#[ignore = "not yet implemented"] fn addition() { let input = " input complex[float] a; @@ -26,11 +44,30 @@ fn addition() { complex x = (a + b); "; - check_stmt_kinds(input, &expect![[r#""#]]); + 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] -#[ignore = "not yet implemented"] fn multiplication() { let input = " input complex[float] a; @@ -38,11 +75,30 @@ fn multiplication() { complex x = (a * b); "; - check_stmt_kinds(input, &expect![[r#""#]]); + 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: Mul + lhs: Expr [86-87]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [90-91]: + ty: Complex(None, false) + kind: SymbolId(9) + "#]]); } #[test] -#[ignore = "not yet implemented"] fn division() { let input = " input complex[float] a; @@ -50,11 +106,30 @@ fn division() { complex x = (a / b); "; - check_stmt_kinds(input, &expect![[r#""#]]); + 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: Div + lhs: Expr [86-87]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [90-91]: + ty: Complex(None, false) + kind: SymbolId(9) + "#]]); } #[test] -#[ignore = "not yet implemented"] fn power() { let input = " input complex[float] a; @@ -62,5 +137,25 @@ fn power() { complex x = (a ** b); "; - check_stmt_kinds(input, &expect![[r#""#]]); + check_stmt_kinds(input, &expect![[r#" + InputDeclaration [9-32]: + symbol_id: 8 + InputDeclaration [41-64]: + symbol_id: 9 + ClassicalDeclarationStmt [73-94]: + symbol_id: 10 + ty_span: [73-80] + init_expr: Expr [85-93]: + ty: Complex(None, false) + kind: Paren Expr [86-92]: + ty: Complex(None, false) + kind: BinaryOpExpr: + op: Exp + lhs: Expr [86-87]: + ty: Complex(None, false) + kind: SymbolId(8) + rhs: Expr [91-92]: + ty: Complex(None, false) + kind: SymbolId(9) + "#]]); } 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 index 019a219e04..b20511e089 100644 --- 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 @@ -6,18 +6,46 @@ use expect_test::expect; use crate::semantic::tests::check_classical_decls; #[test] -#[ignore = "not yet implemented"] fn to_int_decl_implicitly() { let input = r#" bit[5] reg; int b = reg; "#; - check_classical_decls(input, &expect![[r#""#]]); + 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] -#[ignore = "not yet implemented"] fn to_int_assignment_implicitly() { let input = r#" bit[5] reg; @@ -25,11 +53,48 @@ fn to_int_assignment_implicitly() { a = reg; "#; - check_classical_decls(input, &expect![[r#""#]]); + 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] -#[ignore = "not yet implemented"] fn to_int_with_equal_width_in_assignment_implicitly() { let input = r#" bit[5] reg; @@ -37,22 +102,85 @@ fn to_int_with_equal_width_in_assignment_implicitly() { a = reg; "#; - check_classical_decls(input, &expect![[r#""#]]); + 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] -#[ignore = "not yet implemented"] 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#""#]]); + 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] -#[ignore = "not yet implemented"] fn to_int_with_higher_width_implicitly_fails() { let input = " int[6] a; @@ -60,21 +188,89 @@ fn to_int_with_higher_width_implicitly_fails() { a = reg; "; - check_classical_decls(input, &expect![[r#""#]]); + 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] -#[ignore = "not yet implemented"] fn to_int_with_higher_width_decl_implicitly_fails() { let input = " bit[5] reg; int[6] a = reg; "; - check_classical_decls(input, &expect![[r#""#]]); + 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] -#[ignore = "not yet implemented"] fn to_int_with_lower_width_implicitly_fails() { let input = " input int[4] a; @@ -82,16 +278,81 @@ fn to_int_with_lower_width_implicitly_fails() { a = reg; "; - check_classical_decls(input, &expect![[r#""#]]); + 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] -#[ignore = "not yet implemented"] fn to_int_with_lower_width_decl_implicitly_fails() { let input = " bit[5] reg; int[4] a = reg; "; - check_classical_decls(input, &expect![[r#""#]]); + 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_float.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/implicit_cast_from_float.rs index 706646a53b..bce8631b3f 100644 --- 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 @@ -531,7 +531,6 @@ fn to_explicit_complex_implicitly() { } #[test] -#[ignore = "not yet implemented"] fn to_angle_implicitly() { let input = " float x = 42.; @@ -541,44 +540,37 @@ fn to_angle_implicitly() { check_classical_decls( input, &expect![[r#" - Program: - version: - statements: - Stmt [9-23]: - annotations: - kind: ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Float(None, true) - kind: Lit: Float(42.0) - - [Qsc.Qasm3.Compile.Unimplemented - - x this statement is not yet handled during OpenQASM 3 import: Cast float - | to angle - ,-[test:3:9] - 2 | float x = 42.; - 3 | angle y = x; - : ^^^^^^^^^^^^ - 4 | - `---- - , Qsc.Qasm3.Compile.CannotCast - - x Cannot cast expression of type Float(None, false) to type Angle(None, - | false) - ,-[test:3:9] - 2 | float x = 42.; - 3 | angle y = x; - : ^^^^^^^^^^^^ - 4 | - `---- - ]"#]], + 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] -#[ignore = "not yet implemented"] fn to_explicit_angle_implicitly() { let input = " float x = 42.; @@ -588,38 +580,32 @@ fn to_explicit_angle_implicitly() { check_classical_decls( input, &expect![[r#" - Program: - version: - statements: - Stmt [9-23]: - annotations: - kind: ClassicalDeclarationStmt [9-23]: - symbol_id: 6 - ty_span: [9-14] - init_expr: Expr [19-22]: - ty: Float(None, true) - kind: Lit: Float(42.0) - - [Qsc.Qasm3.Compile.Unimplemented - - x this statement is not yet handled during OpenQASM 3 import: Cast float - | to angle - ,-[test:3:9] - 2 | float x = 42.; - 3 | angle[4] y = x; - : ^^^^^^^^^^^^^^^ - 4 | - `---- - , Qsc.Qasm3.Compile.CannotCast - - x Cannot cast expression of type Float(None, false) to type Angle(Some(4), - | false) - ,-[test:3:9] - 2 | float x = 42.; - 3 | angle[4] y = x; - : ^^^^^^^^^^^^^^^ - 4 | - `---- - ]"#]], + 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/tests/declaration/float.rs b/compiler/qsc_qasm3/src/tests/declaration/float.rs index e8eb351e99..2f7ca8a970 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/float.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/float.rs @@ -87,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; @@ -104,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; @@ -121,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; diff --git a/compiler/qsc_qasm3/src/tests/declaration/integer.rs b/compiler/qsc_qasm3/src/tests/declaration/integer.rs index 187431b62d..09ba4498b6 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/integer.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/integer.rs @@ -71,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; @@ -104,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)?; @@ -169,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; @@ -202,7 +199,6 @@ fn const_implicit_bitness_int_binary_low_decl() -> miette::Result<(), Vec miette::Result<(), Vec> { let source = " const int x = 0B1010; diff --git a/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs b/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs index 58a93ab28e..87b40a8158 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs @@ -39,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; @@ -72,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)?; @@ -137,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; @@ -170,7 +167,6 @@ fn const_implicit_bitness_int_binary_low_decl() -> miette::Result<(), Vec miette::Result<(), Vec> { let source = " const uint x = 0B1010; diff --git a/compiler/qsc_qasm3/src/tests/expression/unary.rs b/compiler/qsc_qasm3/src/tests/expression/unary.rs index cb5f05270a..fd6b709470 100644 --- a/compiler/qsc_qasm3/src/tests/expression/unary.rs +++ b/compiler/qsc_qasm3/src/tests/expression/unary.rs @@ -7,20 +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)?; + let Err(errors) = compile_qasm_to_qsharp(source) else { + panic!("Expected error"); + }; + expect![[r#" - mutable x = 5; - mutable y = ~~~x; - "#]] - .assert_eq(&qsharp); - Ok(()) + [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] diff --git a/compiler/qsc_qasm3/src/tests/statement/const_eval.rs b/compiler/qsc_qasm3/src/tests/statement/const_eval.rs index 03de904b1c..14dd57add3 100644 --- a/compiler/qsc_qasm3/src/tests/statement/const_eval.rs +++ b/compiler/qsc_qasm3/src/tests/statement/const_eval.rs @@ -143,7 +143,7 @@ fn ident_const() -> miette::Result<(), Vec> { #[ignore = "indexed ident is not yet supported"] fn indexed_ident() -> miette::Result<(), Vec> { let source = r#" - const uint[2] a = {1, 2}; + const array[uint, 2] a = {1, 2}; bit[a[1]] r; "#; diff --git a/compiler/qsc_qasm3/src/tests/statement/if_stmt.rs b/compiler/qsc_qasm3/src/tests/statement/if_stmt.rs index 3f551c4b7b..a5714850fd 100644 --- a/compiler/qsc_qasm3/src/tests/statement/if_stmt.rs +++ b/compiler/qsc_qasm3/src/tests/statement/if_stmt.rs @@ -69,7 +69,6 @@ fn can_use_negated_cond_with_implicit_cast_to_bool() -> 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(()) } From 3dbb16e5f42d245b0d372e7fce5e31a763b6c3c6 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Tue, 1 Apr 2025 13:32:18 -0700 Subject: [PATCH 77/98] add simulatable intrinsic QIR unit tests --- .../src/tests/expression/function_call.rs | 54 ++++++++++++++++++- .../src/tests/statement/gate_call.rs | 21 ++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/compiler/qsc_qasm3/src/tests/expression/function_call.rs b/compiler/qsc_qasm3/src/tests/expression/function_call.rs index a679d76f82..183110a613 100644 --- a/compiler/qsc_qasm3/src/tests/expression/function_call.rs +++ b/compiler/qsc_qasm3/src/tests/expression/function_call.rs @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::tests::compile_qasm_to_qsharp; +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> { @@ -311,3 +312,54 @@ fn funcall_implicit_arg_cast_uint_to_qubit_errors() { ]"#]] .assert_eq(&format!("{errors:?}")); } + +#[test] +fn simulatable_intrinsic_on_gates_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(()) +} \ No newline at end of file diff --git a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs index 1da5555965..9b0d8929d2 100644 --- a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs +++ b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs @@ -473,3 +473,24 @@ fn custom_gate_can_be_called_with_pow_modifier() -> miette::Result<(), Vec miette::Result<(), Vec> { + let source = r#" + include "stdgates.inc"; + + @SimulatableIntrinsic + gate my_gate(a) q { + rx(a) q; + } + + qubit q; + my_gate(2.0) q; + bit result = measure q; + "#; + + let qsharp = compile_qasm_to_qir(source, Profile::AdaptiveRI)?; + expect![[r#""#]] + .assert_eq(&qsharp); + Ok(()) +} \ No newline at end of file From 257ccd92023ddb9f7ffa045894f3fe5d3bf564be Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Wed, 2 Apr 2025 06:13:47 -0700 Subject: [PATCH 78/98] clippy lints --- .../tests/expression/binary/comparison.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison.rs index 36c2847de0..2ec6e5c4e8 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/comparison.rs @@ -12,6 +12,7 @@ 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#" @@ -25,7 +26,9 @@ fn bitarray_var_comparisons_can_be_translated() { bool d = x != y; "#; - check_stmt_kinds(input, &expect![[r#" + check_stmt_kinds( + input, + &expect![[r#" ClassicalDeclarationStmt [9-24]: symbol_id: 8 ty_span: [9-15] @@ -164,9 +167,11 @@ fn bitarray_var_comparisons_can_be_translated() { 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#" @@ -186,7 +191,9 @@ fn bitarray_var_comparison_to_int_can_be_translated() { bool l = y != x; "#; - check_stmt_kinds(input, &expect![[r#" + check_stmt_kinds( + input, + &expect![[r#" ClassicalDeclarationStmt [9-24]: symbol_id: 8 ty_span: [9-15] @@ -399,5 +406,6 @@ fn bitarray_var_comparison_to_int_can_be_translated() { expr: Expr [339-340]: ty: BitArray(One(1), false) kind: SymbolId(8) - "#]]); + "#]], + ); } From 79d42f50bd6125fcba9f4c9b4b8f5fd613e40771 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Wed, 2 Apr 2025 06:16:45 -0700 Subject: [PATCH 79/98] apply cargo fmt --- .../tests/expression/binary/complex.rs | 35 +++++++++---- .../expression/implicit_cast_from_bitarray.rs | 49 +++++++++++++------ .../src/tests/expression/function_call.rs | 2 +- .../src/tests/statement/gate_call.rs | 5 +- 4 files changed, 63 insertions(+), 28 deletions(-) diff --git a/compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs index 66fc8ce939..49c7d68b3c 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs @@ -13,7 +13,9 @@ fn subtraction() { complex x = (a - b); "; - check_stmt_kinds(input, &expect![[r#" + check_stmt_kinds( + input, + &expect![[r#" InputDeclaration [9-32]: symbol_id: 8 InputDeclaration [41-64]: @@ -33,7 +35,8 @@ fn subtraction() { rhs: Expr [90-91]: ty: Complex(None, false) kind: SymbolId(9) - "#]]); + "#]], + ); } #[test] @@ -44,7 +47,9 @@ fn addition() { complex x = (a + b); "; - check_stmt_kinds(input, &expect![[r#" + check_stmt_kinds( + input, + &expect![[r#" InputDeclaration [9-32]: symbol_id: 8 InputDeclaration [41-64]: @@ -64,7 +69,8 @@ fn addition() { rhs: Expr [90-91]: ty: Complex(None, false) kind: SymbolId(9) - "#]]); + "#]], + ); } #[test] @@ -75,7 +81,9 @@ fn multiplication() { complex x = (a * b); "; - check_stmt_kinds(input, &expect![[r#" + check_stmt_kinds( + input, + &expect![[r#" InputDeclaration [9-32]: symbol_id: 8 InputDeclaration [41-64]: @@ -95,7 +103,8 @@ fn multiplication() { rhs: Expr [90-91]: ty: Complex(None, false) kind: SymbolId(9) - "#]]); + "#]], + ); } #[test] @@ -106,7 +115,9 @@ fn division() { complex x = (a / b); "; - check_stmt_kinds(input, &expect![[r#" + check_stmt_kinds( + input, + &expect![[r#" InputDeclaration [9-32]: symbol_id: 8 InputDeclaration [41-64]: @@ -126,7 +137,8 @@ fn division() { rhs: Expr [90-91]: ty: Complex(None, false) kind: SymbolId(9) - "#]]); + "#]], + ); } #[test] @@ -137,7 +149,9 @@ fn power() { complex x = (a ** b); "; - check_stmt_kinds(input, &expect![[r#" + check_stmt_kinds( + input, + &expect![[r#" InputDeclaration [9-32]: symbol_id: 8 InputDeclaration [41-64]: @@ -157,5 +171,6 @@ fn power() { rhs: Expr [91-92]: ty: Complex(None, false) kind: SymbolId(9) - "#]]); + "#]], + ); } 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 index b20511e089..d48f090faa 100644 --- 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 @@ -53,7 +53,9 @@ fn to_int_assignment_implicitly() { a = reg; "#; - check_classical_decls(input, &expect![[r#" + check_classical_decls( + input, + &expect![[r#" ClassicalDeclarationStmt [9-20]: symbol_id: 8 ty_span: [9-15] @@ -91,7 +93,8 @@ fn to_int_assignment_implicitly() { type: Int(None, false) qsharp_type: Int io_kind: Default - "#]]); + "#]], + ); } #[test] @@ -102,7 +105,9 @@ fn to_int_with_equal_width_in_assignment_implicitly() { a = reg; "#; - check_classical_decls(input, &expect![[r#" + check_classical_decls( + input, + &expect![[r#" ClassicalDeclarationStmt [9-20]: symbol_id: 8 ty_span: [9-15] @@ -140,7 +145,8 @@ fn to_int_with_equal_width_in_assignment_implicitly() { type: Int(Some(5), false) qsharp_type: Int io_kind: Default - "#]]); + "#]], + ); } #[test] @@ -150,7 +156,9 @@ fn to_int_with_equal_width_in_decl_implicitly() { int[5] a = reg; "#; - check_classical_decls(input, &expect![[r#" + check_classical_decls( + input, + &expect![[r#" ClassicalDeclarationStmt [9-20]: symbol_id: 8 ty_span: [9-15] @@ -177,7 +185,8 @@ fn to_int_with_equal_width_in_decl_implicitly() { type: Int(Some(5), false) qsharp_type: Int io_kind: Default - "#]]); + "#]], + ); } #[test] @@ -188,7 +197,9 @@ fn to_int_with_higher_width_implicitly_fails() { a = reg; "; - check_classical_decls(input, &expect![[r#" + check_classical_decls( + input, + &expect![[r#" Program: version: statements: @@ -227,7 +238,8 @@ fn to_int_with_higher_width_implicitly_fails() { : ^^^ 5 | `---- - ]"#]]); + ]"#]], + ); } #[test] @@ -236,7 +248,9 @@ fn to_int_with_higher_width_decl_implicitly_fails() { bit[5] reg; int[6] a = reg; "; - check_classical_decls(input, &expect![[r#" + check_classical_decls( + input, + &expect![[r#" Program: version: statements: @@ -267,7 +281,8 @@ fn to_int_with_higher_width_decl_implicitly_fails() { : ^^^ 4 | `---- - ]"#]]); + ]"#]], + ); } #[test] @@ -278,7 +293,9 @@ fn to_int_with_lower_width_implicitly_fails() { a = reg; "; - check_classical_decls(input, &expect![[r#" + check_classical_decls( + input, + &expect![[r#" Program: version: statements: @@ -313,7 +330,8 @@ fn to_int_with_lower_width_implicitly_fails() { : ^^^ 5 | `---- - ]"#]]); + ]"#]], + ); } #[test] @@ -323,7 +341,9 @@ fn to_int_with_lower_width_decl_implicitly_fails() { int[4] a = reg; "; - check_classical_decls(input, &expect![[r#" + check_classical_decls( + input, + &expect![[r#" Program: version: statements: @@ -354,5 +374,6 @@ fn to_int_with_lower_width_decl_implicitly_fails() { : ^^^ 4 | `---- - ]"#]]); + ]"#]], + ); } diff --git a/compiler/qsc_qasm3/src/tests/expression/function_call.rs b/compiler/qsc_qasm3/src/tests/expression/function_call.rs index 183110a613..fb8088c735 100644 --- a/compiler/qsc_qasm3/src/tests/expression/function_call.rs +++ b/compiler/qsc_qasm3/src/tests/expression/function_call.rs @@ -362,4 +362,4 @@ fn simulatable_intrinsic_on_gates_generates_correct_qir() -> miette::Result<(), "#]] .assert_eq(&qsharp); Ok(()) -} \ No newline at end of file +} diff --git a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs index 9b0d8929d2..407c610753 100644 --- a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs +++ b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs @@ -490,7 +490,6 @@ fn simulatable_intrinsic_on_gates_generates_correct_qir() -> miette::Result<(), "#; let qsharp = compile_qasm_to_qir(source, Profile::AdaptiveRI)?; - expect![[r#""#]] - .assert_eq(&qsharp); + expect![[r#""#]].assert_eq(&qsharp); Ok(()) -} \ No newline at end of file +} From a6456dd7f3315c3db07feba224e7524181da8103 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Wed, 2 Apr 2025 06:40:33 -0700 Subject: [PATCH 80/98] make const measurement a parser error --- compiler/qsc_qasm3/src/parser/expr.rs | 14 ++++++++++++ compiler/qsc_qasm3/src/parser/stmt.rs | 2 +- .../src/parser/stmt/tests/classical_decl.rs | 22 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/compiler/qsc_qasm3/src/parser/expr.rs b/compiler/qsc_qasm3/src/parser/expr.rs index f657496b3a..ae69817719 100644 --- a/compiler/qsc_qasm3/src/parser/expr.rs +++ b/compiler/qsc_qasm3/src/parser/expr.rs @@ -724,6 +724,20 @@ pub(super) fn declaration_expr(s: &mut ParserContext) -> Result { 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 { diff --git a/compiler/qsc_qasm3/src/parser/stmt.rs b/compiler/qsc_qasm3/src/parser/stmt.rs index b909ef427a..17eeac9c9c 100644 --- a/compiler/qsc_qasm3/src/parser/stmt.rs +++ b/compiler/qsc_qasm3/src/parser/stmt.rs @@ -743,7 +743,7 @@ fn parse_constant_classical_decl(s: &mut ParserContext) -> Result { let ty = scalar_or_array_type(s)?; let identifier = Box::new(prim::ident(s)?); token(s, TokenKind::Eq)?; - let init_expr = expr::declaration_expr(s)?; + let init_expr = expr::const_declaration_expr(s)?; recovering_semi(s); let decl = ConstantDeclStmt { span: s.span(lo), diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs index 2ae53268ce..f6c15e9267 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs @@ -1103,3 +1103,25 @@ fn measure_register_decl() { 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, + }, + ), + ) + "#]], + ); +} \ No newline at end of file From f8d9c7cc0a82b37299860a13d42377d909786be6 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Wed, 2 Apr 2025 11:29:54 -0700 Subject: [PATCH 81/98] do not generate functors when `@SimulatableIntrinsic` is present --- compiler/qsc_qasm3/src/compiler.rs | 12 +++++- .../src/tests/expression/function_call.rs | 2 +- .../src/tests/statement/annotation.rs | 4 +- .../src/tests/statement/gate_call.rs | 40 ++++++++++++++++--- .../qsc_qasm3/src/tests/statement/include.rs | 2 +- 5 files changed, 50 insertions(+), 10 deletions(-) diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index eb709e4361..a2ae9b609d 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -930,6 +930,16 @@ impl QasmCompiler { .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, @@ -940,7 +950,7 @@ impl QasmCompiler { stmt.span, None, qsast::CallableKind::Operation, - Some(build_adj_plus_ctl_functor()), + functors, list_from_iter(attrs), )) } diff --git a/compiler/qsc_qasm3/src/tests/expression/function_call.rs b/compiler/qsc_qasm3/src/tests/expression/function_call.rs index fb8088c735..f31582c480 100644 --- a/compiler/qsc_qasm3/src/tests/expression/function_call.rs +++ b/compiler/qsc_qasm3/src/tests/expression/function_call.rs @@ -314,7 +314,7 @@ fn funcall_implicit_arg_cast_uint_to_qubit_errors() { } #[test] -fn simulatable_intrinsic_on_gates_generates_correct_qir() -> miette::Result<(), Vec> { +fn simulatable_intrinsic_on_def_stmt_generates_correct_qir() -> miette::Result<(), Vec> { let source = r#" include "stdgates.inc"; diff --git a/compiler/qsc_qasm3/src/tests/statement/annotation.rs b/compiler/qsc_qasm3/src/tests/statement/annotation.rs index bbedda3b53..672058209d 100644 --- a/compiler/qsc_qasm3/src/tests/statement/annotation.rs +++ b/compiler/qsc_qasm3/src/tests/statement/annotation.rs @@ -21,10 +21,10 @@ fn simulatable_intrinsic_can_be_applied_to_gate() -> miette::Result<(), Vec miette::Result<(), Vec miette::Result<(), Vec> { +fn simulatable_intrinsic_on_gate_stmt_generates_correct_qir() -> miette::Result<(), Vec> { let source = r#" include "stdgates.inc"; @SimulatableIntrinsic - gate my_gate(a) q { - rx(a) q; + gate my_gate q { + x q; } qubit q; - my_gate(2.0) q; + my_gate q; bit result = measure q; "#; let qsharp = compile_qasm_to_qir(source, Profile::AdaptiveRI)?; - expect![[r#""#]].assert_eq(&qsharp); + 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/include.rs b/compiler/qsc_qasm3/src/tests/statement/include.rs index 5a7da1a6e8..19e4f96472 100644 --- a/compiler/qsc_qasm3/src/tests/statement/include.rs +++ b/compiler/qsc_qasm3/src/tests/statement/include.rs @@ -46,7 +46,7 @@ fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { @EntryPoint() operation Test() : Result[] { @SimulatableIntrinsic() - operation my_gate(q : Qubit) : Unit is Adj + Ctl { + operation my_gate(q : Qubit) : Unit { x(q); } mutable c = [Zero]; From bf3a82ff4509d65cfe1269aa75ad0e9582b9b611 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Wed, 2 Apr 2025 11:31:18 -0700 Subject: [PATCH 82/98] cargo fmt --- compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs index f6c15e9267..0b7f4ddb01 100644 --- a/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs +++ b/compiler/qsc_qasm3/src/parser/stmt/tests/classical_decl.rs @@ -1124,4 +1124,4 @@ fn const_decl_with_measurement_init_fails() { ) "#]], ); -} \ No newline at end of file +} From cbe4fd260297ef505641f6b3f031a72bab8fc7c3 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Wed, 2 Apr 2025 13:16:19 -0700 Subject: [PATCH 83/98] break and continue stmts --- compiler/qsc_qasm3/src/compiler.rs | 12 ++ compiler/qsc_qasm3/src/semantic/ast.rs | 26 ++++ compiler/qsc_qasm3/src/semantic/error.rs | 4 + compiler/qsc_qasm3/src/semantic/lowerer.rs | 33 +++- compiler/qsc_qasm3/src/semantic/symbols.rs | 30 +++- .../src/semantic/tests/statements.rs | 2 + .../semantic/tests/statements/break_stmt.rs | 146 ++++++++++++++++++ .../tests/statements/continue_stmt.rs | 146 ++++++++++++++++++ .../semantic/tests/statements/while_stmt.rs | 57 +++---- 9 files changed, 413 insertions(+), 43 deletions(-) create mode 100644 compiler/qsc_qasm3/src/semantic/tests/statements/break_stmt.rs create mode 100644 compiler/qsc_qasm3/src/semantic/tests/statements/continue_stmt.rs diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index a2ae9b609d..f73e9eb8b0 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -400,10 +400,12 @@ impl QasmCompiler { 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), @@ -586,6 +588,11 @@ impl QasmCompiler { 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, @@ -615,6 +622,11 @@ impl QasmCompiler { 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, diff --git a/compiler/qsc_qasm3/src/semantic/ast.rs b/compiler/qsc_qasm3/src/semantic/ast.rs index 625a7e91e5..95218b80d3 100644 --- a/compiler/qsc_qasm3/src/semantic/ast.rs +++ b/compiler/qsc_qasm3/src/semantic/ast.rs @@ -347,8 +347,10 @@ pub enum StmtKind { Barrier(BarrierStmt), Box(BoxStmt), Block(Box), + Break(BreakStmt), CalibrationGrammar(CalibrationGrammarStmt), ClassicalDecl(ClassicalDeclarationStmt), + Continue(ContinueStmt), Def(DefStmt), DefCal(DefCalStmt), Delay(DelayStmt), @@ -384,8 +386,10 @@ impl Display for StmtKind { 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}"), @@ -498,6 +502,28 @@ impl Display for Block { } } +#[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), diff --git a/compiler/qsc_qasm3/src/semantic/error.rs b/compiler/qsc_qasm3/src/semantic/error.rs index b9ed712721..e5198ea09f 100644 --- a/compiler/qsc_qasm3/src/semantic/error.rs +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -141,6 +141,9 @@ pub enum SemanticErrorKind { #[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), @@ -299,6 +302,7 @@ impl SemanticErrorKind { 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) } diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index d476177763..d8df3ccdc1 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -850,8 +850,16 @@ impl Lowerer { } fn lower_break(&mut self, stmt: &syntax::BreakStmt) -> semantic::StmtKind { - self.push_unimplemented_error_message("break stmt", stmt.span); - semantic::StmtKind::Err + 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 { @@ -968,8 +976,16 @@ impl Lowerer { } fn lower_continue_stmt(&mut self, stmt: &syntax::ContinueStmt) -> semantic::StmtKind { - self.push_unimplemented_error_message("continue stmt", stmt.span); - semantic::StmtKind::Err + 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 { @@ -1152,7 +1168,7 @@ impl Lowerer { let set_declaration = self.lower_enumerable_set(&stmt.set_declaration); // Push scope where the loop variable lives. - self.symbols.push_scope(ScopeKind::Block); + 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); @@ -1748,6 +1764,10 @@ impl Lowerer { } 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); @@ -1756,6 +1776,9 @@ impl Lowerer { 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, diff --git a/compiler/qsc_qasm3/src/semantic/symbols.rs b/compiler/qsc_qasm3/src/semantic/symbols.rs index 0da5f644e7..667e96d5af 100644 --- a/compiler/qsc_qasm3/src/semantic/symbols.rs +++ b/compiler/qsc_qasm3/src/semantic/symbols.rs @@ -277,6 +277,7 @@ pub enum ScopeKind { Function, Gate, Block, + Loop, } const BUILTIN_SYMBOLS: [(&str, f64); 6] = [ @@ -424,7 +425,10 @@ impl SymbolTable { { let scopes = self.scopes.iter().rev(); let predicate = |x: &Scope| { - x.kind == ScopeKind::Block || x.kind == ScopeKind::Function || x.kind == ScopeKind::Gate + matches!( + x.kind, + ScopeKind::Block | ScopeKind::Loop | ScopeKind::Function | ScopeKind::Gate + ) }; // Use scan to track the last item that returned false @@ -510,6 +514,30 @@ impl SymbolTable { .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() { diff --git a/compiler/qsc_qasm3/src/semantic/tests/statements.rs b/compiler/qsc_qasm3/src/semantic/tests/statements.rs index eda5cb3db6..c31fcb3141 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements.rs @@ -2,6 +2,8 @@ // Licensed under the MIT License. mod box_stmt; +mod break_stmt; +mod continue_stmt; mod for_stmt; mod if_stmt; mod switch_stmt; 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..d1fbac0a82 --- /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..eaeefd1518 --- /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/while_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs index 8d926b8974..bf8d92833f 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/while_stmt.rs @@ -5,49 +5,32 @@ use crate::semantic::tests::check_stmt_kinds; use expect_test::expect; #[test] -fn single_stmt_body_doesnt_creates_its_own_scope() { +fn single_stmt_body_creates_its_own_scope() { check_stmt_kinds( " int a = 3; while(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(3) - Stmt [20-42]: - annotations: - kind: 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: 8 - ty_span: [32-35] - init_expr: Expr [40-41]: - ty: Int(None, false) - kind: Lit: Int(1) - - [Qsc.Qasm3.Compile.RedefinedSymbol - - x Redefined symbol: a. - ,-[test:3:21] - 2 | int a = 3; - 3 | while(true) int a = 1; - : ^ - 4 | - `---- - ]"#]], + 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) + "#]], ); } From 6a0386d2fd163a84511964113515e2e592c37f81 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Wed, 2 Apr 2025 21:28:45 -0700 Subject: [PATCH 84/98] lower duration literals --- compiler/qsc_qasm3/src/compiler.rs | 2 +- compiler/qsc_qasm3/src/parser/ast.rs | 2 +- compiler/qsc_qasm3/src/semantic/ast.rs | 12 ++++++++++ compiler/qsc_qasm3/src/semantic/lowerer.rs | 24 ++++++++++--------- .../src/semantic/tests/decls/duration.rs | 9 +------ compiler/qsc_qasm3/src/tests/declaration.rs | 24 +++++++++---------- 6 files changed, 40 insertions(+), 33 deletions(-) diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index f73e9eb8b0..5dc517bdcf 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -1512,7 +1512,7 @@ impl QasmCompiler { _unit: TimeUnit, span: Span, ) -> qsast::Expr { - self.push_unsupported_error_message("timing literals", span); + self.push_unsupported_error_message("Timing literals", span); err_expr(span) } diff --git a/compiler/qsc_qasm3/src/parser/ast.rs b/compiler/qsc_qasm3/src/parser/ast.rs index 442a13e7aa..0dc85a26c1 100644 --- a/compiler/qsc_qasm3/src/parser/ast.rs +++ b/compiler/qsc_qasm3/src/parser/ast.rs @@ -1744,7 +1744,7 @@ impl From for crate::semantic::symbols::IOKind { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Copy)] pub enum TimeUnit { Dt, /// Nanoseconds. diff --git a/compiler/qsc_qasm3/src/semantic/ast.rs b/compiler/qsc_qasm3/src/semantic/ast.rs index 95218b80d3..23782f7d92 100644 --- a/compiler/qsc_qasm3/src/semantic/ast.rs +++ b/compiler/qsc_qasm3/src/semantic/ast.rs @@ -1755,6 +1755,18 @@ impl Display for TimeUnit { } } +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, diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index d8df3ccdc1..2e4e2d343f 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -602,10 +602,13 @@ impl Lowerer { self.push_unsupported_error_message("String literals", expr.span); (semantic::ExprKind::Err, Type::Err) } - syntax::LiteralKind::Duration(_, _) => { - self.push_unsupported_error_message("Duration 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 @@ -2105,6 +2108,10 @@ impl Lowerer { 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 @@ -2133,13 +2140,8 @@ impl Lowerer { self.push_unimplemented_error_message("uint array default value", span); None } - Type::Duration(_) - | Type::Gate(_, _) - | Type::Function(..) - | Type::Range - | Type::Set - | Type::Void => { - let message = format!("Default values for {ty:?} are unsupported."); + 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 } diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls/duration.rs b/compiler/qsc_qasm3/src/semantic/tests/decls/duration.rs index 9f8a20da98..56d173ae52 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls/duration.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls/duration.rs @@ -20,7 +20,7 @@ fn with_no_init_expr_has_generated_lit_expr() { ty_span: [0-8] init_expr: Expr [0-0]: ty: Duration(true) - kind: Err + kind: Lit: Duration(0.0, Ns) [Qsc.Qasm3.Compile.NotSupported @@ -29,13 +29,6 @@ fn with_no_init_expr_has_generated_lit_expr() { 1 | duration a; : ^^^^^^^^ `---- - , Qsc.Qasm3.Compile.NotSupported - - x Default values for Duration(false) are unsupported. are not supported. - ,-[test:1:1] - 1 | duration a; - : ^^^^^^^^^^^ - `---- ]"#]], ); } diff --git a/compiler/qsc_qasm3/src/tests/declaration.rs b/compiler/qsc_qasm3/src/tests/declaration.rs index c23f0af07d..debae1f218 100644 --- a/compiler/qsc_qasm3/src/tests/declaration.rs +++ b/compiler/qsc_qasm3/src/tests/declaration.rs @@ -17,7 +17,6 @@ use crate::{ tests::{compile_fragments, compile_with_config, fail_on_compilation_errors}, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics, }; - use miette::Report; #[test] @@ -65,17 +64,16 @@ fn duration_literal() -> miette::Result<(), Vec> { None, ); let unit = compile_with_config(source, config).expect("parse failed"); - println!("{:?}", unit.errors); - assert_eq!(unit.errors.len(), 5); for error in &unit.errors { - assert!( - error - .to_string() - .contains("Duration type values are not supported.") - || error - .to_string() - .contains("Timing literal expressions are not supported.") - ); + println!("{error}"); + } + assert_eq!(unit.errors.len(), 10); + for error in &unit.errors { + assert!([ + "Duration type values are not supported.", + "Timing literals are not supported.", + ] + .contains(&error.to_string().as_str())); } Ok(()) @@ -96,7 +94,9 @@ fn stretch() { ); let unit = compile_with_config(source, config).expect("parse failed"); assert!(unit.has_errors()); - println!("{:?}", unit.errors); + for error in &unit.errors { + println!("{error}"); + } assert!(unit.errors.len() == 2); assert!(unit.errors[0] .to_string() From 8874f881dd64e5339a573babfb7f296e0309db1d Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Wed, 2 Apr 2025 21:55:39 -0700 Subject: [PATCH 85/98] use `Type::Void` instead of `Option::None` to represent no return --- compiler/qsc_qasm3/src/ast_builder.rs | 8 +------- compiler/qsc_qasm3/src/compiler.rs | 6 +++--- compiler/qsc_qasm3/src/semantic/ast.rs | 4 ++-- compiler/qsc_qasm3/src/semantic/lowerer.rs | 15 +++++++-------- .../src/semantic/tests/statements/break_stmt.rs | 2 +- .../semantic/tests/statements/continue_stmt.rs | 2 +- compiler/qsc_qasm3/src/semantic/types.rs | 2 +- 7 files changed, 16 insertions(+), 23 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm3/src/ast_builder.rs index a4b23ac8a6..e74154743a 100644 --- a/compiler/qsc_qasm3/src/ast_builder.rs +++ b/compiler/qsc_qasm3/src/ast_builder.rs @@ -1689,7 +1689,7 @@ pub(crate) fn build_function_or_operation( name_span: Span, body_span: Span, gate_span: Span, - return_type: Option, + return_type: Ty, kind: CallableKind, functors: Option, attrs: List, @@ -1724,12 +1724,6 @@ pub(crate) fn build_function_or_operation( ..Default::default() }; - let return_type = if let Some(ty) = return_type { - ty - } else { - build_path_ident_ty("Unit") - }; - let body = CallableBody::Block(Box::new(body.unwrap_or_else(|| Block { id: NodeId::default(), span: body_span, diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index 5dc517bdcf..0ef44dba90 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -23,7 +23,7 @@ use crate::{ 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_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, @@ -651,7 +651,7 @@ impl QasmCompiler { .collect(); let body = Some(self.compile_block(&stmt.body)); - let return_type = stmt.return_type.as_ref().map(map_qsharp_type_to_ast_ty); + let return_type = map_qsharp_type_to_ast_ty(&stmt.return_type); let kind = if stmt.has_qubit_params { qsast::CallableKind::Operation } else { @@ -960,7 +960,7 @@ impl QasmCompiler { symbol.span, stmt.body.span, stmt.span, - None, + build_path_ident_ty("Unit"), qsast::CallableKind::Operation, functors, list_from_iter(attrs), diff --git a/compiler/qsc_qasm3/src/semantic/ast.rs b/compiler/qsc_qasm3/src/semantic/ast.rs index 23782f7d92..c77fff55df 100644 --- a/compiler/qsc_qasm3/src/semantic/ast.rs +++ b/compiler/qsc_qasm3/src/semantic/ast.rs @@ -1291,7 +1291,7 @@ pub struct DefStmt { pub has_qubit_params: bool, pub params: Box<[SymbolId]>, pub body: Block, - pub return_type: Option, + pub return_type: crate::types::Type, } impl Display for DefStmt { @@ -1300,7 +1300,7 @@ impl Display for DefStmt { 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_opt_field(f, "return_type", self.return_type.as_ref())?; + writeln_field(f, "return_type", &self.return_type)?; write_field(f, "body", &self.body) } } diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index 2e4e2d343f..e6ddcd18e8 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -1015,9 +1015,12 @@ impl Lowerer { 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); - (Some(ty), Some(qsharp_ty)) + (ty, qsharp_ty) } else { - (None, None) + ( + crate::semantic::types::Type::Void, + crate::types::Type::Tuple(Default::default()), + ) }; // 2. Push the function symbol to the symbol table. @@ -1025,7 +1028,7 @@ impl Lowerer { 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.map(Rc::new)); + let ty = crate::semantic::types::Type::Function(param_types.into(), Rc::new(return_ty)); let has_qubit_params = stmt .params @@ -1244,11 +1247,7 @@ impl Lowerer { )); } - if let Some(return_ty) = return_ty { - (params_ty.clone(), (**return_ty).clone()) - } else { - (Rc::default(), crate::semantic::types::Type::Err) - } + (params_ty.clone(), (**return_ty).clone()) } else { self.push_semantic_error(SemanticErrorKind::CannotCallNonFunction(symbol.span)); (Rc::default(), crate::semantic::types::Type::Err) diff --git a/compiler/qsc_qasm3/src/semantic/tests/statements/break_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/break_stmt.rs index d1fbac0a82..5e3c9fcc3b 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements/break_stmt.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/break_stmt.rs @@ -117,7 +117,7 @@ fn intermediate_def_scope_fails() { symbol_id: 8 has_qubit_params: false parameters: - return_type: + return_type: () body: Block [36-54]: Stmt [46-52]: annotations: diff --git a/compiler/qsc_qasm3/src/semantic/tests/statements/continue_stmt.rs b/compiler/qsc_qasm3/src/semantic/tests/statements/continue_stmt.rs index eaeefd1518..7a73aaae27 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/statements/continue_stmt.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/statements/continue_stmt.rs @@ -117,7 +117,7 @@ fn intermediate_def_scope_fails() { symbol_id: 8 has_qubit_params: false parameters: - return_type: + return_type: () body: Block [36-57]: Stmt [46-55]: annotations: diff --git a/compiler/qsc_qasm3/src/semantic/types.rs b/compiler/qsc_qasm3/src/semantic/types.rs index e05dadc203..e4f24d60a7 100644 --- a/compiler/qsc_qasm3/src/semantic/types.rs +++ b/compiler/qsc_qasm3/src/semantic/types.rs @@ -43,7 +43,7 @@ pub enum Type { // realistically the sizes could be u3 Gate(u32, u32), - Function(Rc<[Type]>, Option>), + Function(Rc<[Type]>, Rc), Range, Set, Void, From cabf3276c74fc5c5d943c683a2c39e550e1c2855 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Wed, 2 Apr 2025 23:03:22 -0700 Subject: [PATCH 86/98] return stmt casts to the return type of the function --- compiler/qsc_qasm3/src/semantic/error.rs | 12 +++ compiler/qsc_qasm3/src/semantic/lowerer.rs | 41 +++++++- compiler/qsc_qasm3/src/semantic/symbols.rs | 31 +++++-- .../qsc_qasm3/src/tests/declaration/def.rs | 93 +++++++++++++++++++ .../src/tests/expression/function_call.rs | 6 +- 5 files changed, 167 insertions(+), 16 deletions(-) diff --git a/compiler/qsc_qasm3/src/semantic/error.rs b/compiler/qsc_qasm3/src/semantic/error.rs index e5198ea09f..330ca9cb41 100644 --- a/compiler/qsc_qasm3/src/semantic/error.rs +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -150,6 +150,9 @@ pub enum SemanticErrorKind { #[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), @@ -189,6 +192,9 @@ pub enum SemanticErrorKind { #[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), @@ -333,6 +339,9 @@ impl SemanticErrorKind { 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) => { @@ -366,6 +375,9 @@ impl SemanticErrorKind { 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) diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index e6ddcd18e8..7c56eedf31 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -1015,10 +1015,10 @@ impl Lowerer { 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); - (ty, qsharp_ty) + (Rc::new(ty), qsharp_ty) } else { ( - crate::semantic::types::Type::Void, + Rc::new(crate::semantic::types::Type::Void), crate::types::Type::Tuple(Default::default()), ) }; @@ -1028,7 +1028,7 @@ impl Lowerer { 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(), Rc::new(return_ty)); + let ty = crate::semantic::types::Type::Function(param_types.into(), return_ty.clone()); let has_qubit_params = stmt .params @@ -1047,7 +1047,7 @@ impl Lowerer { 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); + self.symbols.push_scope(ScopeKind::Function(return_ty)); let params = param_symbols .into_iter() @@ -1678,7 +1678,7 @@ impl Lowerer { } fn lower_return(&mut self, stmt: &syntax::ReturnStmt) -> semantic::StmtKind { - let expr = stmt + let mut expr = stmt .expr .as_ref() .map(|expr| match &**expr { @@ -1687,6 +1687,37 @@ impl Lowerer { }) .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, diff --git a/compiler/qsc_qasm3/src/semantic/symbols.rs b/compiler/qsc_qasm3/src/semantic/symbols.rs index 667e96d5af..0fbb4081e3 100644 --- a/compiler/qsc_qasm3/src/semantic/symbols.rs +++ b/compiler/qsc_qasm3/src/semantic/symbols.rs @@ -274,7 +274,9 @@ 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, + /// 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, @@ -427,7 +429,7 @@ impl SymbolTable { let predicate = |x: &Scope| { matches!( x.kind, - ScopeKind::Block | ScopeKind::Loop | ScopeKind::Function | ScopeKind::Gate + ScopeKind::Block | ScopeKind::Loop | ScopeKind::Function(..) | ScopeKind::Gate ) }; @@ -485,7 +487,7 @@ impl SymbolTable { } if matches!( scope.kind, - ScopeKind::Gate | ScopeKind::Function | ScopeKind::Global + ScopeKind::Gate | ScopeKind::Function(..) | ScopeKind::Global ) { return true; } @@ -503,7 +505,19 @@ impl SymbolTable { self.scopes .iter() .rev() - .any(|scope| scope.kind == ScopeKind::Function) + .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] @@ -511,7 +525,7 @@ impl SymbolTable { self.scopes .iter() .rev() - .any(|scope| matches!(scope.kind, ScopeKind::Gate | ScopeKind::Function)) + .any(|scope| matches!(scope.kind, ScopeKind::Gate | ScopeKind::Function(..))) } #[must_use] @@ -531,7 +545,7 @@ impl SymbolTable { // // 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) { + if matches!(scope.kind, ScopeKind::Function(..) | ScopeKind::Gate) { return false; } } @@ -541,10 +555,7 @@ impl SymbolTable { #[must_use] pub fn is_scope_rooted_in_global(&self) -> bool { for scope in self.scopes.iter().rev() { - if scope.kind == ScopeKind::Function { - return false; - } - if scope.kind == ScopeKind::Gate { + if matches!(scope.kind, ScopeKind::Function(..) | ScopeKind::Gate) { return false; } } diff --git a/compiler/qsc_qasm3/src/tests/declaration/def.rs b/compiler/qsc_qasm3/src/tests/declaration/def.rs index 6f30a22b97..d4947bb9cd 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/def.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/def.rs @@ -70,3 +70,96 @@ fn qubit_array_parameter() -> miette::Result<(), Vec> { .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:?}")); +} diff --git a/compiler/qsc_qasm3/src/tests/expression/function_call.rs b/compiler/qsc_qasm3/src/tests/expression/function_call.rs index f31582c480..1599bf4d13 100644 --- a/compiler/qsc_qasm3/src/tests/expression/function_call.rs +++ b/compiler/qsc_qasm3/src/tests/expression/function_call.rs @@ -277,7 +277,11 @@ fn funcall_implicit_arg_cast_uint_to_bitarray() -> miette::Result<(), Vec Date: Wed, 2 Apr 2025 23:23:31 -0700 Subject: [PATCH 87/98] fix clippy warning --- compiler/qsc_qasm3/src/semantic/lowerer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index 7c56eedf31..3cc4e1a6e8 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -1714,7 +1714,7 @@ impl Lowerer { ); return semantic::StmtKind::Err; } - *expr = Box::new(self.cast_expr_to_type(&ty, expr)) + *expr = Box::new(self.cast_expr_to_type(&ty, expr)); } } From ae28d80562483f1ce65f47ca8a93f85ff0e90618 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Thu, 3 Apr 2025 08:04:10 -0700 Subject: [PATCH 88/98] add assign_op complex tests --- .../tests/expression/binary/complex.rs | 280 ++++++++++++++---- .../src/tests/expression/binary/complex.rs | 20 +- 2 files changed, 227 insertions(+), 73 deletions(-) diff --git a/compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs index 49c7d68b3c..d070cbb067 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/complex.rs @@ -1,16 +1,46 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use expect_test::expect; - use crate::semantic::tests::check_stmt_kinds; +use expect_test::expect; #[test] -fn subtraction() { +fn addition() { let input = " input complex[float] a; input complex[float] b; - complex x = (a - 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( @@ -28,7 +58,7 @@ fn subtraction() { kind: Paren Expr [86-91]: ty: Complex(None, false) kind: BinaryOpExpr: - op: Sub + op: Add lhs: Expr [86-87]: ty: Complex(None, false) kind: SymbolId(8) @@ -40,36 +70,67 @@ fn subtraction() { } #[test] -fn addition() { +fn subtraction() { let input = " input complex[float] a; input complex[float] b; - complex x = (a + 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-93]: - symbol_id: 10 - ty_span: [73-80] - init_expr: Expr [85-92]: - ty: Complex(None, false) - kind: Paren Expr [86-91]: + 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 [86-87]: + op: Sub + lhs: Expr [85-86]: ty: Complex(None, false) kind: SymbolId(8) - rhs: Expr [90-91]: + 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) + "#]], ); } @@ -78,32 +139,63 @@ fn multiplication() { let input = " input complex[float] a; input complex[float] b; - complex x = (a * 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-93]: - symbol_id: 10 - ty_span: [73-80] - init_expr: Expr [85-92]: - ty: Complex(None, false) - kind: Paren Expr [86-91]: + 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 [86-87]: + lhs: Expr [85-86]: ty: Complex(None, false) kind: SymbolId(8) - rhs: Expr [90-91]: + 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) + "#]], ); } @@ -112,32 +204,63 @@ fn division() { let input = " input complex[float] a; input complex[float] b; - complex x = (a / 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-93]: - symbol_id: 10 - ty_span: [73-80] - init_expr: Expr [85-92]: - ty: Complex(None, false) - kind: Paren Expr [86-91]: + 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 [86-87]: + lhs: Expr [85-86]: ty: Complex(None, false) kind: SymbolId(8) - rhs: Expr [90-91]: + 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) + "#]], ); } @@ -146,31 +269,62 @@ fn power() { let input = " input complex[float] a; input complex[float] b; - complex x = (a ** 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-94]: - symbol_id: 10 - ty_span: [73-80] - init_expr: Expr [85-93]: - ty: Complex(None, false) - kind: Paren Expr [86-92]: + 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 [86-87]: + lhs: Expr [85-86]: ty: Complex(None, false) kind: SymbolId(8) - rhs: Expr [91-92]: + 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/tests/expression/binary/complex.rs b/compiler/qsc_qasm3/src/tests/expression/binary/complex.rs index fb70121884..00366c153a 100644 --- a/compiler/qsc_qasm3/src/tests/expression/binary/complex.rs +++ b/compiler/qsc_qasm3/src/tests/expression/binary/complex.rs @@ -10,12 +10,12 @@ 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.PlusC(a, b)); + mutable x = Microsoft.Quantum.Math.PlusC(a, b); "#]] .assert_eq(&qsharp); Ok(()) @@ -42,12 +42,12 @@ 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)); + mutable x = Microsoft.Quantum.Math.MinusC(a, b); "#]] .assert_eq(&qsharp); Ok(()) @@ -74,12 +74,12 @@ 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)); + mutable x = Microsoft.Quantum.Math.TimesC(a, b); "#]] .assert_eq(&qsharp); Ok(()) @@ -106,12 +106,12 @@ 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)); + mutable x = Microsoft.Quantum.Math.DividedByC(a, b); "#]] .assert_eq(&qsharp); Ok(()) @@ -138,12 +138,12 @@ 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)); + mutable x = Microsoft.Quantum.Math.PowC(a, b); "#]] .assert_eq(&qsharp); Ok(()) From 914c30f944f066985c7f840d7c86b535280d1785 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Thu, 3 Apr 2025 09:04:46 -0700 Subject: [PATCH 89/98] mark assigning_uint_to_negative_lit_results_in_semantic_error as ignore --- compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs b/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs index 87b40a8158..e35fe8a539 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/unsigned_integer.rs @@ -231,6 +231,7 @@ fn explicit_bitness_int_decl() -> miette::Result<(), Vec> { } #[test] +#[ignore = "not implemented"] fn assigning_uint_to_negative_lit_results_in_semantic_error() { let source = " const uint[10] x = -42; From a551d8388ffce41a905d455fc3240e3e036e2be1 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Thu, 3 Apr 2025 14:42:45 -0700 Subject: [PATCH 90/98] lower extern --- compiler/qsc_qasm3/src/parser/ast.rs | 14 +++-- compiler/qsc_qasm3/src/parser/mut_visit.rs | 4 -- compiler/qsc_qasm3/src/semantic/ast.rs | 10 ++-- compiler/qsc_qasm3/src/semantic/lowerer.rs | 63 +++++++++++++++++++++- 4 files changed, 75 insertions(+), 16 deletions(-) diff --git a/compiler/qsc_qasm3/src/parser/ast.rs b/compiler/qsc_qasm3/src/parser/ast.rs index 0dc85a26c1..6f974e5180 100644 --- a/compiler/qsc_qasm3/src/parser/ast.rs +++ b/compiler/qsc_qasm3/src/parser/ast.rs @@ -711,19 +711,24 @@ impl Display for ClassicalArgument { #[derive(Clone, Debug)] pub enum ExternParameter { ArrayReference(ArrayReferenceType, Span), - Quantum(Option, 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::Quantum(expr, span) => { - write!(f, "{span}: {expr:?}") - } ExternParameter::ArrayReference(ty, span) => { write!(f, "{span}: {ty}") } @@ -741,7 +746,6 @@ 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), } } diff --git a/compiler/qsc_qasm3/src/parser/mut_visit.rs b/compiler/qsc_qasm3/src/parser/mut_visit.rs index f14543d1ad..e848076e1a 100644 --- a/compiler/qsc_qasm3/src/parser/mut_visit.rs +++ b/compiler/qsc_qasm3/src/parser/mut_visit.rs @@ -801,10 +801,6 @@ pub fn walk_extern_parameter(vis: &mut impl MutVisitor, param: &mut ExternParame vis.visit_span(span); vis.visit_array_ref_type(ty); } - ExternParameter::Quantum(expr, span) => { - vis.visit_span(span); - expr.iter_mut().for_each(|expr| vis.visit_expr(expr)); - } ExternParameter::Scalar(ty, span) => { vis.visit_span(span); vis.visit_scalar_type(ty); diff --git a/compiler/qsc_qasm3/src/semantic/ast.rs b/compiler/qsc_qasm3/src/semantic/ast.rs index c77fff55df..e044269351 100644 --- a/compiler/qsc_qasm3/src/semantic/ast.rs +++ b/compiler/qsc_qasm3/src/semantic/ast.rs @@ -1087,17 +1087,17 @@ impl Display for QuantumGateDefinition { #[derive(Clone, Debug)] pub struct ExternDecl { pub span: Span, - pub ident: Box, - pub params: List, - pub return_type: Option, + 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, "ident", &self.ident)?; + writeln_field(f, "symbol_id", &self.symbol_id)?; writeln_list_field(f, "parameters", &self.params)?; - write_opt_field(f, "return_type", self.return_type.as_ref()) + write_field(f, "return_type", &self.return_type) } } diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index 3cc4e1a6e8..3ea32e5076 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -1166,8 +1166,67 @@ impl Lowerer { } fn lower_extern(&mut self, stmt: &syntax::ExternDecl) -> semantic::StmtKind { - self.push_unimplemented_error_message("extern stmt", stmt.span); - semantic::StmtKind::Err + // 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::DefDeclarationInNonGlobalScope(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 { From d1fb2d7dd52842b87a2fbc05460be51f467a8aaf Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Thu, 3 Apr 2025 15:08:38 -0700 Subject: [PATCH 91/98] extern unit tests --- compiler/qsc_qasm3/src/semantic/error.rs | 6 ++ compiler/qsc_qasm3/src/semantic/lowerer.rs | 2 +- .../qsc_qasm3/src/semantic/tests/decls.rs | 1 + .../src/semantic/tests/decls/extern_decl.rs | 88 +++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 compiler/qsc_qasm3/src/semantic/tests/decls/extern_decl.rs diff --git a/compiler/qsc_qasm3/src/semantic/error.rs b/compiler/qsc_qasm3/src/semantic/error.rs index 330ca9cb41..ccbe28a2a6 100644 --- a/compiler/qsc_qasm3/src/semantic/error.rs +++ b/compiler/qsc_qasm3/src/semantic/error.rs @@ -93,6 +93,9 @@ pub enum SemanticErrorKind { #[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), @@ -296,6 +299,9 @@ impl SemanticErrorKind { 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) } diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index 3ea32e5076..c73076fbaa 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -1169,7 +1169,7 @@ impl Lowerer { // 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::DefDeclarationInNonGlobalScope(stmt.span); + let kind = SemanticErrorKind::ExternDeclarationInNonGlobalScope(stmt.span); self.push_semantic_error(kind); } diff --git a/compiler/qsc_qasm3/src/semantic/tests/decls.rs b/compiler/qsc_qasm3/src/semantic/tests/decls.rs index 94629a0e1a..1240c0c4d8 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/decls.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/decls.rs @@ -7,6 +7,7 @@ mod bool; mod complex; mod creg; mod duration; +mod extern_decl; mod float; mod int; mod qreg; 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(); } + : ^^^^^^^^^^^ + `---- + ]"#]], + ); +} From 34ce0a7cd88c0011ab1b358352cbc3a70bb369e6 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Fri, 4 Apr 2025 15:44:10 -0700 Subject: [PATCH 92/98] Better error messages for const evaluator (#2277) --- compiler/qsc_qasm3/src/lib.rs | 4 + .../qsc_qasm3/src/semantic/ast/const_eval.rs | 278 ++++++++++-------- compiler/qsc_qasm3/src/semantic/lowerer.rs | 32 +- .../binary/arithmetic_conversions.rs | 44 +++ compiler/qsc_qasm3/src/semantic/types.rs | 2 +- .../qsc_qasm3/src/tests/declaration/def.rs | 94 ++++++ .../qsc_qasm3/src/tests/declaration/gate.rs | 94 ++++++ .../src/tests/statement/const_eval.rs | 40 +++ 8 files changed, 461 insertions(+), 127 deletions(-) diff --git a/compiler/qsc_qasm3/src/lib.rs b/compiler/qsc_qasm3/src/lib.rs index 72890f4e92..c450949440 100644 --- a/compiler/qsc_qasm3/src/lib.rs +++ b/compiler/qsc_qasm3/src/lib.rs @@ -77,6 +77,9 @@ pub enum ErrorKind { #[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}")] @@ -90,6 +93,7 @@ impl ErrorKind { ErrorKind::Parse(error, span) => Self::Parse(error, span + 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::OldIO(error) => Self::OldIO(error), } diff --git a/compiler/qsc_qasm3/src/semantic/ast/const_eval.rs b/compiler/qsc_qasm3/src/semantic/ast/const_eval.rs index a899186e55..cc7fc68f39 100644 --- a/compiler/qsc_qasm3/src/semantic/ast/const_eval.rs +++ b/compiler/qsc_qasm3/src/semantic/ast/const_eval.rs @@ -10,33 +10,53 @@ 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::{ - symbols::SymbolTable, - types::{ArrayDimensions, Type}, - }, + 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 { - pub fn const_eval(&self, symbols: &SymbolTable) -> Option { + /// 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(symbols), - ExprKind::IndexedIdentifier(indexed_ident) => indexed_ident.const_eval(symbols), - ExprKind::UnaryOp(unary_op_expr) => unary_op_expr.const_eval(symbols), - ExprKind::BinaryOp(binary_op_expr) => binary_op_expr.const_eval(symbols), + 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(symbols, ty), - ExprKind::Cast(cast) => cast.const_eval(symbols, ty), - ExprKind::IndexExpr(index_expr) => index_expr.const_eval(symbols, ty), - ExprKind::Paren(expr) => expr.const_eval(symbols), + 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, } @@ -44,17 +64,17 @@ impl Expr { } impl SymbolId { - fn const_eval(self, symbols: &SymbolTable) -> Option { - let symbol = symbols[self].clone(); + 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(symbols) // const eval that Expr + .const_eval(ctx) // const eval that Expr } } impl IndexedIdent { #[allow(clippy::unused_self)] - fn const_eval(&self, _symbols: &SymbolTable) -> Option { + fn const_eval(&self, _ctx: &mut Lowerer) -> Option { None } } @@ -69,16 +89,16 @@ macro_rules! rewrap_lit { if let $pat = $lit { Some($out) } else { - None + unreachable!("if we hit this there is a bug in the type system") } }; } impl UnaryOpExpr { - fn const_eval(&self, symbols: &SymbolTable) -> Option { + 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(symbols)?; + let lit = self.expr.const_eval(ctx)?; match &self.op { UnaryOp::Neg => match operand_ty { @@ -138,86 +158,88 @@ fn assert_binary_op_ty_invariant(op: BinOp, lhs_ty: &Type, rhs_ty: &Type) { impl BinaryOpExpr { #[allow(clippy::too_many_lines)] - fn const_eval(&self, symbols: &SymbolTable) -> Option { + 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(symbols)?; - let rhs = self.rhs.const_eval(symbols)?; + 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 => match lhs_ty { - Type::UInt(Some(size), _) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), { - if rhs < 0 { - return None; + 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)) } - let mask = (1 << size) - 1; - Int((lhs << rhs) & mask) - }), - Type::UInt(..) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), { - if rhs < 0 { - return None; + 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) + }) } - Int(lhs << rhs) - }), - Type::Angle(..) => { - rewrap_lit!((lhs, rhs), (Angle(lhs), Int(rhs)), { - if rhs < 0 { - return None; - } - Angle(lhs << rhs) - }) + _ => None, } - Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Int(rhs)), { - if rhs < 0 { - return None; - } - // 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, rhs), (Bitstring(lhs, size), Int(rhs)), { - if rhs < 0 { - return 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)) } - let mask = BigInt::from((1 << size) - 1); - Bitstring((lhs << rhs) & mask, size) - }), - _ => None, - }, - BinOp::Shr => match lhs_ty { - Type::UInt(..) => rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), { - if rhs < 0 { - return None; + 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)) } - Int(lhs >> rhs) - }), - Type::Angle(..) => { - rewrap_lit!((lhs, rhs), (Angle(lhs), Int(rhs)), { - if rhs < 0 { - return None; - } - Angle(lhs >> rhs) - }) + _ => None, } - Type::Bit(..) => rewrap_lit!((lhs, rhs), (Bit(lhs), Int(rhs)), { - if rhs < 0 { - return None; - } - // 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, rhs), (Bitstring(lhs, size), Int(rhs)), { - if rhs < 0 { - return None; - } - Bitstring(lhs >> rhs, size) - }), - _ => None, - }, + } // Bitwise BinOp::AndB => match lhs_ty { @@ -423,6 +445,10 @@ impl BinaryOpExpr { } 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)] @@ -494,31 +520,28 @@ impl BinaryOpExpr { impl FunctionCall { #[allow(clippy::unused_self)] - fn const_eval(&self, _symbols: &SymbolTable, _ty: &Type) -> Option { + fn const_eval(&self, _ctx: &mut Lowerer, _ty: &Type) -> Option { None } } impl IndexExpr { #[allow(clippy::unused_self)] - fn const_eval(&self, _symbols: &SymbolTable, _ty: &Type) -> Option { + fn const_eval(&self, _ctx: &mut Lowerer, _ty: &Type) -> Option { None } } impl Cast { - fn const_eval(&self, symbols: &SymbolTable, ty: &Type) -> Option { - let lit = self.expr.const_eval(symbols)?; - let lit_ty = &self.expr.ty; - - match ty { - Type::Bool(_) => cast_to_bool(lit_ty, lit), - Type::Int(_, _) => cast_to_int(lit_ty, lit), - Type::UInt(_, _) => cast_to_uint(lit_ty, lit), - Type::Float(_, _) => cast_to_float(lit_ty, lit), - Type::Angle(_, _) => cast_to_angle(lit, lit_ty, ty), - Type::Bit(_) => cast_to_bit(lit_ty, lit), - Type::BitArray(dims, _) => cast_to_bitarray(lit_ty, lit, dims), + 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, } } @@ -531,10 +554,11 @@ impl Cast { /// +---------------+------+-----+------+-------+-------+-----+ /// | bool | - | Yes | Yes | Yes | Yes | Yes | /// +---------------+------+-----+------+-------+-------+-----+ -fn cast_to_bool(ty: &Type, lit: LiteralKind) -> Option { +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 ty { + 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)), @@ -552,10 +576,11 @@ fn cast_to_bool(ty: &Type, lit: LiteralKind) -> Option { /// +---------------+------+-----+------+-------+-------+-----+ /// | int | Yes | - | Yes | Yes | No | Yes | /// +---------------+------+-----+------+-------+-------+-----+ -fn cast_to_int(ty: &Type, lit: LiteralKind) -> Option { +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 ty { + 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(..) => { @@ -581,10 +606,11 @@ fn cast_to_int(ty: &Type, lit: LiteralKind) -> Option { /// +---------------+------+-----+------+-------+-------+-----+ /// | uint | Yes | Yes | - | Yes | No | Yes | /// +---------------+------+-----+------+-------+-------+-----+ -fn cast_to_uint(ty: &Type, lit: LiteralKind) -> Option { +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 ty { + 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(..) => { @@ -611,10 +637,11 @@ fn cast_to_uint(ty: &Type, lit: LiteralKind) -> Option { /// +---------------+------+-----+------+-------+-------+-----+ /// | float | Yes | Yes | Yes | - | No | No | /// +---------------+------+-----+------+-------+-------+-----+ -fn cast_to_float(ty: &Type, lit: LiteralKind) -> Option { +fn cast_to_float(cast: &Cast, ctx: &mut Lowerer) -> Option { use LiteralKind::{Bool, Float, Int}; + let lit = cast.expr.const_eval(ctx)?; - match ty { + 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#. @@ -633,9 +660,11 @@ fn cast_to_float(ty: &Type, lit: LiteralKind) -> Option { /// +---------------+------+-----+------+-------+-------+-----+ /// | angle | No | No | No | Yes | - | Yes | /// +---------------+------+-----+------+-------+-------+-----+ -fn cast_to_angle(lit: LiteralKind, lit_ty: &Type, target_ty: &Type) -> Option { +fn cast_to_angle(cast: &Cast, ctx: &mut Lowerer) -> Option { use LiteralKind::{Angle, Bit, Bitstring, Float}; - match lit_ty { + let lit = cast.expr.const_eval(ctx)?; + + match &cast.expr.ty { Type::Float(size, _) => rewrap_lit!( lit, Float(val), @@ -644,7 +673,7 @@ fn cast_to_angle(lit: LiteralKind, lit_ty: &Type, target_ty: &Type) -> Option

  • rewrap_lit!( lit, Angle(val), - Angle(val.cast_to_maybe_sized(target_ty.width())) + Angle(val.cast_to_maybe_sized(cast.ty.width())) ), Type::Bit(..) => rewrap_lit!( lit, @@ -673,10 +702,11 @@ fn cast_to_angle(lit: LiteralKind, lit_ty: &Type, target_ty: &Type) -> Option
  • Option { +fn cast_to_bit(cast: &Cast, ctx: &mut Lowerer) -> Option { use LiteralKind::{Angle, Bit, Bool, Int}; + let lit = cast.expr.const_eval(ctx)?; - match ty { + 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)), @@ -692,15 +722,21 @@ fn cast_to_bit(ty: &Type, lit: LiteralKind) -> Option { /// +---------------+------+-----+------+-------+-------+-----+ /// | bitarray | Yes | Yes | Yes | No | Yes | - | /// +---------------+------+-----+------+-------+-------+-----+ -fn cast_to_bitarray(ty: &Type, lit: LiteralKind, dims: &ArrayDimensions) -> Option { +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 ty { + 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)); @@ -709,6 +745,11 @@ fn cast_to_bitarray(ty: &Type, lit: LiteralKind, dims: &ArrayDimensions) -> Opti 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) @@ -716,6 +757,11 @@ fn cast_to_bitarray(ty: &Type, lit: LiteralKind, dims: &ArrayDimensions) -> Opti 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) diff --git a/compiler/qsc_qasm3/src/semantic/lowerer.rs b/compiler/qsc_qasm3/src/semantic/lowerer.rs index c73076fbaa..a9eb613d21 100644 --- a/compiler/qsc_qasm3/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm3/src/semantic/lowerer.rs @@ -4,10 +4,11 @@ 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_int_conversion; +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; @@ -61,7 +62,7 @@ macro_rules! err_expr { }; } -pub(super) struct Lowerer { +pub(crate) struct Lowerer { /// The root QASM source to compile. pub source: QasmSource, /// The source map of QASM sources for error reporting. @@ -546,11 +547,11 @@ impl Lowerer { && 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.symbols) { + if let Some(val) = symbol.get_const_expr().const_eval(self) { semantic::ExprKind::Lit(val) } else { self.push_semantic_error(SemanticErrorKind::ExprMustBeConst( - ident.name.to_string(), + "A captured variable".into(), ident.span, )); semantic::ExprKind::Err @@ -1506,7 +1507,7 @@ impl Lowerer { self.push_invalid_cast_error(target_ty, &expr.ty, expr.span); return None; }; - let Some(lit) = expr.const_eval(&self.symbols) else { + let Some(lit) = expr.const_eval(self) else { self.push_semantic_error(SemanticErrorKind::ExprMustBeConst( "ctrl modifier argument".into(), expr.span, @@ -1678,7 +1679,7 @@ impl Lowerer { 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.symbols)) + size_expr.map(|expr| expr.const_eval(self)) { if let Ok(size) = u32::try_from(val) { ( @@ -1906,7 +1907,7 @@ impl Lowerer { expr_span, ); - if let Some(lit) = expr.const_eval(&self.symbols) { + if let Some(lit) = expr.const_eval(self) { Some(lit) } else { self.push_semantic_error(SemanticErrorKind::ExprMustBeConst( @@ -2854,8 +2855,8 @@ impl Lowerer { } }; (new_left, new_right, promoted_type) - } else if binop_requires_symmetric_int_conversion(op) { - let ty = Type::Int(None, ty_constness); + } 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 { @@ -2943,7 +2944,11 @@ impl Lowerer { fn binop_requires_bitwise_symmetric_conversion(op: syntax::BinOp) -> bool { matches!( op, - syntax::BinOp::AndB | syntax::BinOp::OrB | syntax::BinOp::XorB + syntax::BinOp::AndB + | syntax::BinOp::OrB + | syntax::BinOp::XorB + | syntax::BinOp::Shl + | syntax::BinOp::Shr ) } @@ -3207,6 +3212,13 @@ impl Lowerer { 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); 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 index 6b8223047b..847c4abe36 100644 --- a/compiler/qsc_qasm3/src/semantic/tests/expression/binary/arithmetic_conversions.rs +++ b/compiler/qsc_qasm3/src/semantic/tests/expression/binary/arithmetic_conversions.rs @@ -262,3 +262,47 @@ fn multiplying_int_idents_with_width_greater_than_64_result_in_bigint_result() { "#]], ); } + +#[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/types.rs b/compiler/qsc_qasm3/src/semantic/types.rs index e4f24d60a7..7373338207 100644 --- a/compiler/qsc_qasm3/src/semantic/types.rs +++ b/compiler/qsc_qasm3/src/semantic/types.rs @@ -775,7 +775,7 @@ fn try_promote_bitarray_to_int(left_type: &Type, right_type: &Type) -> Option> -pub(crate) fn binop_requires_symmetric_int_conversion(op: syntax::BinOp) -> bool { +pub(crate) fn binop_requires_symmetric_uint_conversion(op: syntax::BinOp) -> bool { matches!(op, syntax::BinOp::Shl | syntax::BinOp::Shr) } diff --git a/compiler/qsc_qasm3/src/tests/declaration/def.rs b/compiler/qsc_qasm3/src/tests/declaration/def.rs index d4947bb9cd..3e1f200e5c 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/def.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/def.rs @@ -163,3 +163,97 @@ fn missing_return_expr_on_non_void_function_fails() { ]"#]] .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/gate.rs b/compiler/qsc_qasm3/src/tests/declaration/gate.rs index 5f3e98d5bd..879b0cb354 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/gate.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/gate.rs @@ -84,3 +84,97 @@ fn two_angles_two_qubits() -> miette::Result<(), Vec> { .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/statement/const_eval.rs b/compiler/qsc_qasm3/src/tests/statement/const_eval.rs index 14dd57add3..e4060157e8 100644 --- a/compiler/qsc_qasm3/src/tests/statement/const_eval.rs +++ b/compiler/qsc_qasm3/src/tests/statement/const_eval.rs @@ -76,6 +76,16 @@ fn non_const_exprs_fail_in_bitarray_size_position() { 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 @@ -86,6 +96,16 @@ fn non_const_exprs_fail_in_bitarray_size_position() { 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 @@ -487,6 +507,16 @@ fn binary_op_shl_creg_fails() { 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 @@ -650,6 +680,16 @@ fn binary_op_shr_creg_fails() { 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 From 6c86abd7d85bbd753a8b7d67f57c7172a1e79316 Mon Sep 17 00:00:00 2001 From: orpuente-MS <156957451+orpuente-MS@users.noreply.github.com> Date: Mon, 7 Apr 2025 15:35:02 -0700 Subject: [PATCH 93/98] Cyclic and multiple include errors (#2278) This PR adds cyclic and multiple include errors to the qasm parser. --- compiler/qsc_qasm3/src/io.rs | 156 ++++++++++++++- compiler/qsc_qasm3/src/io/error.rs | 6 + compiler/qsc_qasm3/src/parse.rs | 14 +- compiler/qsc_qasm3/src/parser.rs | 21 +- compiler/qsc_qasm3/src/parser/tests.rs | 8 +- compiler/qsc_qasm3/src/semantic.rs | 6 +- compiler/qsc_qasm3/src/semantic/tests.rs | 16 +- compiler/qsc_qasm3/src/tests.rs | 8 +- .../qsc_qasm3/src/tests/statement/include.rs | 180 ++++++++++++++++++ fuzz/fuzz_targets/qasm3.rs | 4 +- pip/src/interop.rs | 31 +-- pip/src/interpreter.rs | 4 +- 12 files changed, 404 insertions(+), 50 deletions(-) diff --git a/compiler/qsc_qasm3/src/io.rs b/compiler/qsc_qasm3/src/io.rs index bc705f037f..fa88bfe55b 100644 --- a/compiler/qsc_qasm3/src/io.rs +++ b/compiler/qsc_qasm3/src/io.rs @@ -17,8 +17,10 @@ use rustc_hash::FxHashMap; /// Implementations of this trait can be provided to the parser /// to customize how include files are resolved. pub trait SourceResolver { + fn ctx(&mut self) -> &mut SourceResolverContext; + #[cfg(feature = "fs")] - fn resolve

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

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

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

    (&mut self, path: P) -> miette::Result<(PathBuf, String), Error> where P: AsRef, { let path = path.as_ref(); + self.ctx().check_include_errors(&path.to_path_buf())?; match self.sources.get(path) { Some(source) => Ok((path.to_owned(), source.clone())), None => Err(Error(ErrorKind::NotFound(format!( diff --git a/compiler/qsc_qasm3/src/io/error.rs b/compiler/qsc_qasm3/src/io/error.rs index 1a3b40ecf3..64f58f036c 100644 --- a/compiler/qsc_qasm3/src/io/error.rs +++ b/compiler/qsc_qasm3/src/io/error.rs @@ -4,6 +4,8 @@ use miette::Diagnostic; use thiserror::Error; +use super::Cycle; + #[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] #[error(transparent)] #[diagnostic(transparent)] @@ -15,6 +17,10 @@ pub enum ErrorKind { NotFound(String), #[error("IO Error: {0}")] IO(String), + #[error("{0} was already included in: {1}")] + MultipleInclude(String, String), + #[error("Cyclic include:{0}")] + CyclicInclude(Cycle), } impl From for crate::Error { diff --git a/compiler/qsc_qasm3/src/parse.rs b/compiler/qsc_qasm3/src/parse.rs index 99c77efccc..c8987c1872 100644 --- a/compiler/qsc_qasm3/src/parse.rs +++ b/compiler/qsc_qasm3/src/parse.rs @@ -67,7 +67,11 @@ impl QasmParseResult { /// This function will resolve includes using the provided resolver. /// If an include file cannot be resolved, an error will be returned. /// If a file is included recursively, a stack overflow occurs. -pub fn parse_source(source: S, path: P, resolver: &R) -> miette::Result +pub fn parse_source( + source: S, + path: P, + resolver: &mut R, +) -> miette::Result where S: AsRef, P: AsRef, @@ -203,7 +207,7 @@ impl QasmSource { /// This function is the start of a recursive process that will resolve all /// includes in the QASM file. Any includes are parsed as if their contents /// were defined where the include statement is. -fn parse_qasm_file(path: P, resolver: &R) -> miette::Result +fn parse_qasm_file(path: P, resolver: &mut R) -> miette::Result where P: AsRef, R: SourceResolver, @@ -212,7 +216,7 @@ where parse_qasm_source(source, path, resolver) } -fn parse_qasm_source(source: S, path: P, resolver: &R) -> miette::Result +fn parse_qasm_source(source: S, path: P, resolver: &mut R) -> miette::Result where S: AsRef, P: AsRef, @@ -224,7 +228,7 @@ where fn parse_source_and_includes, R>( source: P, - resolver: &R, + resolver: &mut R, ) -> miette::Result<(ParseOrErrors, Vec)> where R: SourceResolver, @@ -240,7 +244,7 @@ where fn parse_includes( syntax_ast: &ParseOrErrors, - resolver: &R, + resolver: &mut R, ) -> miette::Result> where R: SourceResolver, diff --git a/compiler/qsc_qasm3/src/parser.rs b/compiler/qsc_qasm3/src/parser.rs index 69b9e9204b..3c9bd80a6f 100644 --- a/compiler/qsc_qasm3/src/parser.rs +++ b/compiler/qsc_qasm3/src/parser.rs @@ -110,7 +110,7 @@ fn update_offsets(source_map: &SourceMap, source: &mut QasmSource) { /// 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 +pub fn parse_source(source: S, path: P, resolver: &mut R) -> QasmParseResult where S: AsRef, P: AsRef, @@ -230,13 +230,22 @@ impl QasmSource { /// 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 +fn parse_qasm_file(path: P, resolver: &mut R) -> QasmSource where P: AsRef, R: SourceResolver, { match resolver.resolve(&path) { - Ok((path, source)) => parse_qasm_source(source, path, resolver), + Ok((path, source)) => { + let parse_result = parse_qasm_source(source, path, resolver); + + // Once we finish parsing the source, we pop the file from the + // resolver. This is needed to keep track of multiple includes + // and cyclic includes. + resolver.ctx().pop_current_file(); + + parse_result + } Err(e) => { let error = crate::parser::error::ErrorKind::IO(e); let error = crate::parser::Error(error, None); @@ -255,7 +264,7 @@ where } } -fn parse_qasm_source(source: S, path: P, resolver: &R) -> QasmSource +fn parse_qasm_source(source: S, path: P, resolver: &mut R) -> QasmSource where S: AsRef, P: AsRef, @@ -267,7 +276,7 @@ where fn parse_source_and_includes, R>( source: P, - resolver: &R, + resolver: &mut R, ) -> (Program, Vec, Vec) where R: SourceResolver, @@ -277,7 +286,7 @@ where (program, errors, included) } -fn parse_includes(program: &Program, resolver: &R) -> Vec +fn parse_includes(program: &Program, resolver: &mut R) -> Vec where R: SourceResolver, { diff --git a/compiler/qsc_qasm3/src/parser/tests.rs b/compiler/qsc_qasm3/src/parser/tests.rs index 30bd4e45d7..99ce264ffd 100644 --- a/compiler/qsc_qasm3/src/parser/tests.rs +++ b/compiler/qsc_qasm3/src/parser/tests.rs @@ -23,11 +23,11 @@ pub(crate) fn parse_all

    ( where P: AsRef, { - let resolver = InMemorySourceResolver::from_iter(sources); + let mut resolver = InMemorySourceResolver::from_iter(sources); let (path, source) = resolver .resolve(path.as_ref()) .map_err(|e| vec![Report::new(e)])?; - let res = crate::parser::parse_source(source, path, &resolver); + let res = crate::parser::parse_source(source, path, &mut resolver); if res.source.has_errors() { let errors = res .errors() @@ -44,8 +44,8 @@ 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); + let mut resolver = InMemorySourceResolver::from_iter([("test".into(), source.as_ref().into())]); + let res = parse_source(source, "test", &mut resolver); if res.source.has_errors() { let errors = res .errors() diff --git a/compiler/qsc_qasm3/src/semantic.rs b/compiler/qsc_qasm3/src/semantic.rs index e99dac9906..7780a6a159 100644 --- a/compiler/qsc_qasm3/src/semantic.rs +++ b/compiler/qsc_qasm3/src/semantic.rs @@ -96,18 +96,18 @@ where S: AsRef, P: AsRef, { - let resolver = InMemorySourceResolver::from_iter([( + let mut resolver = InMemorySourceResolver::from_iter([( path.as_ref().display().to_string().into(), source.as_ref().into(), )]); - parse_source(source, path, &resolver) + parse_source(source, path, &mut resolver) } /// Parse a QASM file and return the parse result. /// This function will resolve includes using the provided resolver. /// If an include file cannot be resolved, an error will be returned. /// If a file is included recursively, a stack overflow occurs. -pub fn parse_source(source: S, path: P, resolver: &R) -> QasmSemanticParseResult +pub fn parse_source(source: S, path: P, resolver: &mut R) -> QasmSemanticParseResult where S: AsRef, P: AsRef, diff --git a/compiler/qsc_qasm3/src/semantic/tests.rs b/compiler/qsc_qasm3/src/semantic/tests.rs index d2eb3f29a0..1b2aa04d14 100644 --- a/compiler/qsc_qasm3/src/semantic/tests.rs +++ b/compiler/qsc_qasm3/src/semantic/tests.rs @@ -29,11 +29,11 @@ pub(crate) fn parse_all

    ( where P: AsRef, { - let resolver = InMemorySourceResolver::from_iter(sources); + let mut 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); + let res = parse_source(source, path, &mut resolver); if res.source.has_errors() { let errors = res .errors() @@ -50,8 +50,8 @@ pub(crate) fn parse(source: S) -> miette::Result, { - let resolver = InMemorySourceResolver::from_iter([("test".into(), source.as_ref().into())]); - let res = parse_source(source, "test", &resolver); + let mut resolver = InMemorySourceResolver::from_iter([("test".into(), source.as_ref().into())]); + let res = parse_source(source, "test", &mut resolver); if res.source.has_errors() { let errors = res .errors() @@ -139,8 +139,8 @@ fn check_map( ) where S: AsRef, { - let resolver = InMemorySourceResolver::from_iter([("test".into(), input.as_ref().into())]); - let res = parse_source(input, "test", &resolver); + let mut resolver = InMemorySourceResolver::from_iter([("test".into(), input.as_ref().into())]); + let res = parse_source(input, "test", &mut resolver); let errors = res.all_errors(); @@ -182,13 +182,13 @@ fn check_map_all

    ( ) where P: AsRef, { - let resolver = InMemorySourceResolver::from_iter(sources); + let mut resolver = InMemorySourceResolver::from_iter(sources); let source = resolver .resolve(path.as_ref()) .map_err(|e| vec![e]) .expect("could not load source") .1; - let res = parse_source(source, path, &resolver); + let res = parse_source(source, path, &mut resolver); let errors = res.all_errors(); diff --git a/compiler/qsc_qasm3/src/tests.rs b/compiler/qsc_qasm3/src/tests.rs index e47f0d4a30..901ce15265 100644 --- a/compiler/qsc_qasm3/src/tests.rs +++ b/compiler/qsc_qasm3/src/tests.rs @@ -213,9 +213,9 @@ pub(crate) fn parse(source: S) -> miette::Result, { - let resolver = + let mut resolver = InMemorySourceResolver::from_iter([("Test.qasm".into(), source.as_ref().into())]); - let res = parse_source(source, "Test.qasm", &resolver); + let res = parse_source(source, "Test.qasm", &mut resolver); if res.source.has_errors() { let errors = res .errors() @@ -234,12 +234,12 @@ pub(crate) fn parse_all

    ( where P: AsRef, { - let resolver = InMemorySourceResolver::from_iter(sources); + let mut resolver = InMemorySourceResolver::from_iter(sources); let source = resolver .resolve(path.as_ref()) .map_err(|e| vec![Report::new(e)])? .1; - let res = parse_source(source, path, &resolver); + let res = parse_source(source, path, &mut resolver); if res.source.has_errors() { let errors = res .errors() diff --git a/compiler/qsc_qasm3/src/tests/statement/include.rs b/compiler/qsc_qasm3/src/tests/statement/include.rs index 19e4f96472..03e9e27717 100644 --- a/compiler/qsc_qasm3/src/tests/statement/include.rs +++ b/compiler/qsc_qasm3/src/tests/statement/include.rs @@ -59,3 +59,183 @@ fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { .assert_eq(&qsharp); Ok(()) } + +#[test] +fn multiple_include_in_same_file_errors() { + let main = r#" + include "source1.inc"; + include "source1.inc"; + "#; + let source1 = r#" + bit[1] c; + "#; + let all_sources = [ + ("main.qasm".into(), main.into()), + ("source1.inc".into(), source1.into()), + ]; + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + + let Err(errors) = compile_all_with_config("main.qasm", all_sources, config) else { + panic!("expected errors") + }; + + let errors: Vec<_> = errors.iter().map(|e| format!("{e}")).collect(); + let errors_string = errors.join("\n"); + expect!["source1.inc was already included in: main.qasm"].assert_eq(&errors_string); +} + +#[test] +fn multiple_include_in_different_files_errors() { + let main = r#" + include "source1.inc"; + include "source2.inc"; + "#; + let source1 = r#" + include "source3.inc"; + "#; + let source2 = r#" + include "source3.inc"; + "#; + let source3 = r#" + bit[1] c; + "#; + let all_sources = [ + ("main.qasm".into(), main.into()), + ("source1.inc".into(), source1.into()), + ("source2.inc".into(), source2.into()), + ("source3.inc".into(), source3.into()), + ]; + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + + let Err(errors) = compile_all_with_config("main.qasm", all_sources, config) else { + panic!("expected errors") + }; + + let errors: Vec<_> = errors.iter().map(|e| format!("{e}")).collect(); + let errors_string = errors.join("\n"); + expect!["source3.inc was already included in: source1.inc"].assert_eq(&errors_string); +} + +#[test] +fn self_include_errors() { + let main = r#" + include "source1.inc"; + "#; + let source1 = r#" + include "source1.inc"; + "#; + let all_sources = [ + ("main.qasm".into(), main.into()), + ("source1.inc".into(), source1.into()), + ]; + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + + let Err(errors) = compile_all_with_config("main.qasm", all_sources, config) else { + panic!("expected errors") + }; + + let errors: Vec<_> = errors.iter().map(|e| format!("{e}")).collect(); + let errors_string = errors.join("\n"); + expect![[r#" + Cyclic include: + source1.inc includes source1.inc"#]] + .assert_eq(&errors_string); +} + +#[test] +fn mutual_include_errors() { + let main = r#" + include "source1.inc"; + "#; + let source1 = r#" + include "source2.inc"; + "#; + let source2 = r#" + include "source1.inc"; + "#; + let all_sources = [ + ("main.qasm".into(), main.into()), + ("source1.inc".into(), source1.into()), + ("source2.inc".into(), source2.into()), + ]; + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + + let Err(errors) = compile_all_with_config("main.qasm", all_sources, config) else { + panic!("expected errors") + }; + + let errors: Vec<_> = errors.iter().map(|e| format!("{e}")).collect(); + let errors_string = errors.join("\n"); + expect![[r#" + Cyclic include: + source1.inc includes source2.inc + source2.inc includes source1.inc"#]] + .assert_eq(&errors_string); +} + +#[test] +fn cyclic_include_errors() { + let main = r#" + include "source1.inc"; + "#; + let source1 = r#" + include "source2.inc"; + "#; + let source2 = r#" + include "source3.inc"; + "#; + let source3 = r#" + include "source1.inc"; + "#; + let all_sources = [ + ("main.qasm".into(), main.into()), + ("source1.inc".into(), source1.into()), + ("source2.inc".into(), source2.into()), + ("source3.inc".into(), source3.into()), + ]; + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::Qiskit, + ProgramType::File, + Some("Test".into()), + None, + ); + + let Err(errors) = compile_all_with_config("main.qasm", all_sources, config) else { + panic!("expected errors") + }; + + let errors: Vec<_> = errors.iter().map(|e| format!("{e}")).collect(); + let errors_string = errors.join("\n"); + + expect![[r#" + Cyclic include: + source1.inc includes source2.inc + source2.inc includes source3.inc + source3.inc includes source1.inc"#]] + .assert_eq(&errors_string); +} diff --git a/fuzz/fuzz_targets/qasm3.rs b/fuzz/fuzz_targets/qasm3.rs index ce7b6f2ee1..3e785f27f9 100644 --- a/fuzz/fuzz_targets/qasm3.rs +++ b/fuzz/fuzz_targets/qasm3.rs @@ -10,8 +10,8 @@ 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); + let mut resolver = qsc::qasm3::io::InMemorySourceResolver::from_iter([]); + let _ = qsc::qasm3::parser::parse_source(fuzzed_code, "fuzz.qasm", &mut resolver); } } diff --git a/pip/src/interop.rs b/pip/src/interop.rs index 4a6cd01f62..8a8b6a4e61 100644 --- a/pip/src/interop.rs +++ b/pip/src/interop.rs @@ -11,8 +11,8 @@ 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::io::{SourceResolver, SourceResolverContext}; use qsc::qasm3::{ qasm_to_program, CompilerConfig, OperationSignature, QasmCompileUnit, QubitSemantics, }; @@ -38,6 +38,7 @@ where { fs: T, path: PathBuf, + ctx: SourceResolverContext, } impl ImportResolver @@ -48,6 +49,7 @@ where Self { fs, path: PathBuf::from(path.as_ref()), + ctx: Default::default(), } } } @@ -56,11 +58,16 @@ impl SourceResolver for ImportResolver where T: FileSystem, { - fn resolve

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

    (&mut self, path: P) -> miette::Result<(PathBuf, String), Error> where P: AsRef, { let path = self.path.join(path); + self.ctx().check_include_errors(&path)?; let (path, source) = self .fs .read_file(path.as_ref()) @@ -101,12 +108,12 @@ pub fn run_qasm3( let search_path = get_search_path(&kwargs)?; let fs = create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github); - let resolver = ImportResolver::new(fs, PathBuf::from(search_path)); + let mut resolver = ImportResolver::new(fs, PathBuf::from(search_path)); let (package, source_map, signature) = compile_qasm_enriching_errors( source, &operation_name, - &resolver, + &mut resolver, ProgramType::File, OutputSemantics::Qiskit, false, @@ -172,14 +179,14 @@ pub(crate) fn resource_estimate_qasm3( let search_path = get_search_path(&kwargs)?; let fs = create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github); - let resolver = ImportResolver::new(fs, PathBuf::from(search_path)); + let mut resolver = ImportResolver::new(fs, PathBuf::from(search_path)); let program_type = ProgramType::File; let output_semantics = OutputSemantics::ResourceEstimation; let (package, source_map, _) = compile_qasm_enriching_errors( source, &operation_name, - &resolver, + &mut resolver, program_type, output_semantics, false, @@ -235,14 +242,14 @@ pub(crate) fn compile_qasm3_to_qir( let search_path = get_search_path(&kwargs)?; let fs = create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github); - let resolver = ImportResolver::new(fs, PathBuf::from(search_path)); + let mut resolver = ImportResolver::new(fs, PathBuf::from(search_path)); let program_ty = get_program_type(&kwargs)?; let output_semantics = get_output_semantics(&kwargs)?; let (package, source_map, signature) = compile_qasm_enriching_errors( source, &operation_name, - &resolver, + &mut resolver, program_ty, output_semantics, false, @@ -261,7 +268,7 @@ pub(crate) fn compile_qasm3_to_qir( pub(crate) fn compile_qasm, R: SourceResolver>( source: S, operation_name: S, - resolver: &R, + resolver: &mut R, program_ty: ProgramType, output_semantics: OutputSemantics, ) -> PyResult { @@ -302,7 +309,7 @@ pub(crate) fn compile_qasm, R: SourceResolver>( pub(crate) fn compile_qasm_enriching_errors, R: SourceResolver>( source: S, operation_name: S, - resolver: &R, + resolver: &mut R, program_ty: ProgramType, output_semantics: OutputSemantics, allow_input_params: bool, @@ -373,14 +380,14 @@ pub(crate) fn compile_qasm3_to_qsharp( let search_path = get_search_path(&kwargs)?; let fs = create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github); - let resolver = ImportResolver::new(fs, PathBuf::from(search_path)); + let mut resolver = ImportResolver::new(fs, PathBuf::from(search_path)); let program_ty = get_program_type(&kwargs)?; let output_semantics = get_output_semantics(&kwargs)?; let (package, _, _) = compile_qasm_enriching_errors( source, &operation_name, - &resolver, + &mut resolver, program_ty, output_semantics, true, diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs index 38d5b6505f..69f62157f4 100644 --- a/pip/src/interpreter.rs +++ b/pip/src/interpreter.rs @@ -677,12 +677,12 @@ impl Interpreter { resolve_path, fetch_github, ); - let resolver = ImportResolver::new(fs, PathBuf::from(search_path)); + let mut resolver = ImportResolver::new(fs, PathBuf::from(search_path)); let (package, _source_map, signature) = compile_qasm_enriching_errors( source, &operation_name, - &resolver, + &mut resolver, program_type, output_semantics, false, From 8fd6a6c4b327b64722b849677486799ffdf45eb0 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Mon, 7 Apr 2025 16:44:05 -0700 Subject: [PATCH 94/98] qubit cleanup calls --- compiler/qsc_qasm3/src/ast_builder.rs | 8 ++ compiler/qsc_qasm3/src/compiler.rs | 20 +++- compiler/qsc_qasm3/src/semantic/symbols.rs | 19 ++++ .../qsc_qasm3/src/tests/declaration/qubit.rs | 103 +++++++++++++++++- .../src/tests/expression/function_call.rs | 3 + compiler/qsc_qasm3/src/tests/output.rs | 11 ++ .../src/tests/statement/gate_call.rs | 13 ++- .../qsc_qasm3/src/tests/statement/include.rs | 1 + .../qsc_qasm3/src/tests/statement/reset.rs | 19 ++-- .../qsc_qasm3/src/tests/statement/switch.rs | 1 + 10 files changed, 183 insertions(+), 15 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm3/src/ast_builder.rs index e74154743a..9946c67fb4 100644 --- a/compiler/qsc_qasm3/src/ast_builder.rs +++ b/compiler/qsc_qasm3/src/ast_builder.rs @@ -732,6 +732,14 @@ pub(crate) fn build_reset_call(expr: ast::Expr, name_span: Span, operand_span: S build_global_call_with_one_param("Reset", expr, name_span, operand_span) } +pub(crate) fn build_resetall_call( + expr: ast::Expr, + name_span: Span, + operand_span: Span, +) -> ast::Expr { + build_global_call_with_one_param("ResetAll", expr, name_span, operand_span) +} + pub(crate) fn build_global_call_with_one_param>( name: S, expr: ast::Expr, diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index 0ef44dba90..b9a46d2b54 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -25,7 +25,7 @@ use crate::{ 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_resetall_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, @@ -108,6 +108,11 @@ impl QasmCompiler { } self.compile_stmts(&program.statements); + + if matches!(program_ty, ProgramType::File | ProgramType::Operation) { + self.append_qubit_cleanup_calls(); + } + let (package, signature) = match program_ty { ProgramType::File => self.build_file(), ProgramType::Operation => self.build_operation(), @@ -437,6 +442,19 @@ impl QasmCompiler { } } + fn append_qubit_cleanup_calls(&mut self) { + for symbol in self.symbols.get_qubit_symbols() { + let expr = build_path_ident_expr(&symbol.name, Span::default(), Span::default()); + let cleanup_call = if matches!(symbol.ty, Type::Qubit) { + build_reset_call(expr, Span::default(), Span::default()) + } else { + build_resetall_call(expr, Span::default(), Span::default()) + }; + let stmt = build_stmt_semi_from_expr(cleanup_call); + self.stmts.push(stmt); + } + } + fn compile_alias_decl_stmt(&mut self, stmt: &semast::AliasDeclStmt) -> Option { self.push_unimplemented_error_message("alias statements", stmt.span); None diff --git a/compiler/qsc_qasm3/src/semantic/symbols.rs b/compiler/qsc_qasm3/src/semantic/symbols.rs index 0fbb4081e3..fc6afb4418 100644 --- a/compiler/qsc_qasm3/src/semantic/symbols.rs +++ b/compiler/qsc_qasm3/src/semantic/symbols.rs @@ -260,6 +260,14 @@ impl Scope { .map(|id| self.id_to_symbol.get(id).expect("ID should exist").clone()) .collect() } + + fn get_qubit_symbols(&self) -> Vec> { + self.id_to_symbol + .values() + .filter(|symbol| matches!(symbol.ty, Type::Qubit | Type::QubitArray(..))) + .map(Clone::clone) + .collect() + } } /// A symbol table is a collection of scopes and manages the symbol ids. @@ -479,6 +487,17 @@ impl SymbolTable { None } + /// Gets all symbols of a given type. + pub fn get_qubit_symbols(&self) -> Vec> { + let mut res = Vec::new(); + + for scope in self.scopes.iter().rev() { + res.extend(scope.get_qubit_symbols()); + } + + res + } + #[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() { diff --git a/compiler/qsc_qasm3/src/tests/declaration/qubit.rs b/compiler/qsc_qasm3/src/tests/declaration/qubit.rs index 15ec73d002..3029d72dae 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/qubit.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/qubit.rs @@ -4,11 +4,15 @@ use expect_test::expect; use miette::Report; -use crate::tests::{compile_fragments, fail_on_compilation_errors}; +use crate::tests::{ + compile_fragments, compile_with_config, fail_on_compilation_errors, + qsharp_from_qasm_compilation, +}; use crate::{ tests::{compile_qasm_stmt_to_qsharp, compile_qasm_stmt_to_qsharp_with_semantics}, QubitSemantics, }; +use crate::{CompilerConfig, OutputSemantics, ProgramType}; #[test] fn quantum() -> miette::Result<(), Vec> { @@ -53,3 +57,100 @@ fn single_qubit_decl_with_qsharp_semantics() -> miette::Result<(), Vec> .assert_eq(&qsharp); Ok(()) } + +#[test] +fn fragment_does_not_generate_qubit_cleanup_calls() -> miette::Result<(), Vec> { + let source = " + qubit q; + qubit[3] qs; + "; + + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::Fragments, + None, + None, + ); + + let unit = compile_with_config(source, config)?; + let qsharp = qsharp_from_qasm_compilation(unit)?; + + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + let qs = QIR.Runtime.AllocateQubitArray(3); + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn file_generates_qubit_cleanup_calls() -> miette::Result<(), Vec> { + let source = " + qubit q; + qubit[3] qs; + "; + + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::File, + None, + None, + ); + + let unit = compile_with_config(source, config)?; + let qsharp = qsharp_from_qasm_compilation(unit)?; + + expect![[r#" + namespace qasm3_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + @EntryPoint() + operation program() : Unit { + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + let qs = QIR.Runtime.AllocateQubitArray(3); + Reset(q); + ResetAll(qs); + } + }"#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[test] +fn operation_generates_qubit_cleanup_calls() -> miette::Result<(), Vec> { + let source = " + qubit q; + qubit[3] qs; + "; + + let config = CompilerConfig::new( + QubitSemantics::Qiskit, + OutputSemantics::OpenQasm, + ProgramType::Operation, + None, + None, + ); + + let unit = compile_with_config(source, config)?; + let qsharp = qsharp_from_qasm_compilation(unit)?; + + expect![[r#" + operation program() : Unit { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let q = QIR.Runtime.__quantum__rt__qubit_allocate(); + let qs = QIR.Runtime.AllocateQubitArray(3); + Reset(q); + ResetAll(qs); + } + "#]] + .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 index 1599bf4d13..8fbd5433ba 100644 --- a/compiler/qsc_qasm3/src/tests/expression/function_call.rs +++ b/compiler/qsc_qasm3/src/tests/expression/function_call.rs @@ -341,6 +341,7 @@ fn simulatable_intrinsic_on_def_stmt_generates_correct_qir() -> miette::Result<( 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__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__rt__tuple_record_output(i64 0, i8* null) ret void } @@ -349,6 +350,8 @@ fn simulatable_intrinsic_on_def_stmt_generates_correct_qir() -> miette::Result<( declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + declare void @__quantum__qis__reset__body(%Qubit*) #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" } diff --git a/compiler/qsc_qasm3/src/tests/output.rs b/compiler/qsc_qasm3/src/tests/output.rs index f3cd433dbc..f4c742e9ed 100644 --- a/compiler/qsc_qasm3/src/tests/output.rs +++ b/compiler/qsc_qasm3/src/tests/output.rs @@ -53,6 +53,7 @@ fn using_re_semantics_removes_output() -> miette::Result<(), Vec> { 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]); + ResetAll(q); } }"#]] .assert_eq(&qsharp); @@ -104,6 +105,7 @@ fn using_qasm_semantics_captures_all_classical_decls_as_output() -> miette::Resu 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]); + ResetAll(q); (c, gamma, delta) } }"#]] @@ -155,6 +157,7 @@ fn using_qiskit_semantics_only_bit_array_is_captured_and_reversed( 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]); + ResetAll(q); Microsoft.Quantum.Arrays.Reversed(c) } }"#]] @@ -221,6 +224,7 @@ c2[2] = measure q[4]; set c2 w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[2]); set c2 w/= 1 <- QIR.Intrinsic.__quantum__qis__m__body(q[3]); set c2 w/= 2 <- QIR.Intrinsic.__quantum__qis__m__body(q[4]); + ResetAll(q); (Microsoft.Quantum.Arrays.Reversed(c2), Microsoft.Quantum.Arrays.Reversed(c)) } }"#]] @@ -274,6 +278,11 @@ c2[2] = measure q[4]; 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__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 3 to %Qubit*)) + call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 4 to %Qubit*)) 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) @@ -297,6 +306,8 @@ c2[2] = measure q[4]; declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + declare void @__quantum__qis__reset__body(%Qubit*) #1 + declare void @__quantum__rt__tuple_record_output(i64, i8*) declare void @__quantum__rt__array_record_output(i64, i8*) diff --git a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs index d1f7ec175d..4c1eff65e3 100644 --- a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs +++ b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs @@ -116,8 +116,7 @@ fn barrier_generates_qir() -> miette::Result<(), Vec> { "#; let qsharp = compile_qasm_to_qir(source, Profile::AdaptiveRI)?; - expect![[ - r#" + expect![[r#" %Result = type opaque %Qubit = type opaque @@ -128,6 +127,8 @@ fn barrier_generates_qir() -> miette::Result<(), Vec> { call void @__quantum__qis__barrier__body() call void @__quantum__qis__barrier__body() call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 1 to %Qubit*)) call void @__quantum__rt__array_record_output(i64 1, i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) ret void @@ -137,6 +138,8 @@ fn barrier_generates_qir() -> miette::Result<(), Vec> { declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + declare void @__quantum__qis__reset__body(%Qubit*) #1 + declare void @__quantum__rt__array_record_output(i64, i8*) declare void @__quantum__rt__result_record_output(%Result*, i8*) @@ -153,8 +156,7 @@ fn barrier_generates_qir() -> miette::Result<(), Vec> { !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(()) } @@ -498,6 +500,7 @@ fn simulatable_intrinsic_on_gate_stmt_generates_correct_qir() -> miette::Result< 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__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__rt__tuple_record_output(i64 0, i8* null) ret void } @@ -506,6 +509,8 @@ fn simulatable_intrinsic_on_gate_stmt_generates_correct_qir() -> miette::Result< declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + declare void @__quantum__qis__reset__body(%Qubit*) #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" } diff --git a/compiler/qsc_qasm3/src/tests/statement/include.rs b/compiler/qsc_qasm3/src/tests/statement/include.rs index 03e9e27717..69d9cd1623 100644 --- a/compiler/qsc_qasm3/src/tests/statement/include.rs +++ b/compiler/qsc_qasm3/src/tests/statement/include.rs @@ -53,6 +53,7 @@ fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { let q = QIR.Runtime.AllocateQubitArray(1); my_gate(q[0]); set c w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[0]); + ResetAll(q); Microsoft.Quantum.Arrays.Reversed(c) } }"#]] diff --git a/compiler/qsc_qasm3/src/tests/statement/reset.rs b/compiler/qsc_qasm3/src/tests/statement/reset.rs index d0ef520368..89503d95d3 100644 --- a/compiler/qsc_qasm3/src/tests/statement/reset.rs +++ b/compiler/qsc_qasm3/src/tests/statement/reset.rs @@ -45,6 +45,7 @@ fn reset_calls_are_generated_from_qasm() -> miette::Result<(), Vec> { Reset(q[0]); h(q[0]); set meas w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[0]); + ResetAll(q); Microsoft.Quantum.Arrays.Reversed(meas) } }"#]] @@ -66,14 +67,14 @@ fn reset_with_base_profile_is_rewritten_without_resets() -> miette::Result<(), V "#; let qir = compile_qasm_to_qir(source, Profile::Base)?; - expect![ - r#" + expect![[r#" %Result = type opaque %Qubit = type opaque define void @ENTRYPOINT__main() #0 { block_0: call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 2 to %Qubit*)) call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) call void @__quantum__rt__array_record_output(i64 1, i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) @@ -88,7 +89,9 @@ fn reset_with_base_profile_is_rewritten_without_resets() -> miette::Result<(), V declare void @__quantum__rt__result_record_output(%Result*, i8*) - attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="2" "required_num_results"="1" } + declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="3" "required_num_results"="1" } attributes #1 = { "irreversible" } ; module flags @@ -99,8 +102,7 @@ fn reset_with_base_profile_is_rewritten_without_resets() -> miette::Result<(), V !1 = !{i32 7, !"qir_minor_version", i32 0} !2 = !{i32 1, !"dynamic_qubit_management", i1 false} !3 = !{i32 1, !"dynamic_result_management", i1 false} -"# - ] + "#]] .assert_eq(&qir); Ok(()) @@ -119,8 +121,7 @@ fn reset_with_adaptive_ri_profile_generates_reset_qir() -> miette::Result<(), Ve "#; let qir = compile_qasm_to_qir(source, Profile::AdaptiveRI)?; - expect![ - r#" + expect![[r#" %Result = type opaque %Qubit = type opaque @@ -129,6 +130,7 @@ fn reset_with_adaptive_ri_profile_generates_reset_qir() -> miette::Result<(), Ve call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__h__body(%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__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__rt__array_record_output(i64 1, i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) ret void @@ -156,8 +158,7 @@ fn reset_with_adaptive_ri_profile_generates_reset_qir() -> miette::Result<(), Ve !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/statement/switch.rs b/compiler/qsc_qasm3/src/tests/statement/switch.rs index cafd8d2627..491bbb71db 100644 --- a/compiler/qsc_qasm3/src/tests/statement/switch.rs +++ b/compiler/qsc_qasm3/src/tests/statement/switch.rs @@ -207,6 +207,7 @@ fn spec_case_3() -> miette::Result<(), Vec> { } elif __ResultArrayAsIntBE__(b) == 3 { z(q); }; + Reset(q); b } }"#]] From 2de8db7e2d3554ad57a583dd4438d7a619584631 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Mon, 7 Apr 2025 16:49:27 -0700 Subject: [PATCH 95/98] fix clippy warning --- compiler/qsc_qasm3/src/semantic/symbols.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/qsc_qasm3/src/semantic/symbols.rs b/compiler/qsc_qasm3/src/semantic/symbols.rs index fc6afb4418..b7a7a47b39 100644 --- a/compiler/qsc_qasm3/src/semantic/symbols.rs +++ b/compiler/qsc_qasm3/src/semantic/symbols.rs @@ -488,6 +488,7 @@ impl SymbolTable { } /// Gets all symbols of a given type. + #[must_use] pub fn get_qubit_symbols(&self) -> Vec> { let mut res = Vec::new(); From b3287859647ba360a277e332b5d3c4a2b622123a Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Tue, 8 Apr 2025 08:32:10 -0700 Subject: [PATCH 96/98] release instead of reset --- compiler/qsc_qasm3/src/ast_builder.rs | 28 +++++++++++++++---- compiler/qsc_qasm3/src/compiler.rs | 25 +++++++++-------- .../qsc_qasm3/src/tests/declaration/qubit.rs | 14 +++++----- .../src/tests/expression/function_call.rs | 3 -- compiler/qsc_qasm3/src/tests/output.rs | 15 +++------- .../src/tests/statement/gate_call.rs | 7 ----- .../qsc_qasm3/src/tests/statement/include.rs | 2 +- .../qsc_qasm3/src/tests/statement/reset.rs | 8 ++---- .../qsc_qasm3/src/tests/statement/switch.rs | 2 +- 9 files changed, 50 insertions(+), 54 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm3/src/ast_builder.rs index 9946c67fb4..231e49f371 100644 --- a/compiler/qsc_qasm3/src/ast_builder.rs +++ b/compiler/qsc_qasm3/src/ast_builder.rs @@ -732,12 +732,28 @@ pub(crate) fn build_reset_call(expr: ast::Expr, name_span: Span, operand_span: S build_global_call_with_one_param("Reset", expr, name_span, operand_span) } -pub(crate) fn build_resetall_call( - expr: ast::Expr, - name_span: Span, - operand_span: Span, -) -> ast::Expr { - build_global_call_with_one_param("ResetAll", expr, name_span, operand_span) +pub fn build_qubit_release_call(expr: ast::Expr) -> ast::Stmt { + let expr = build_call_with_param( + "__quantum__rt__qubit_release", + &["QIR", "Runtime"], + expr, + Span::default(), + Span::default(), + Span::default(), + ); + build_stmt_semi_from_expr(expr) +} + +pub fn build_qubitarray_release_call(expr: ast::Expr) -> ast::Stmt { + let expr = build_call_with_param( + "ReleaseQubitArray", + &["QIR", "Runtime"], + expr, + Span::default(), + Span::default(), + Span::default(), + ); + build_stmt_semi_from_expr(expr) } pub(crate) fn build_global_call_with_one_param>( diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index b9a46d2b54..aeb8369a66 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -24,12 +24,13 @@ use crate::{ 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_resetall_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, + build_qasm_import_decl, build_qasm_import_items, build_qubit_release_call, + build_qubitarray_release_call, 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::{ @@ -110,7 +111,7 @@ impl QasmCompiler { self.compile_stmts(&program.statements); if matches!(program_ty, ProgramType::File | ProgramType::Operation) { - self.append_qubit_cleanup_calls(); + self.append_qubit_release_calls(); } let (package, signature) = match program_ty { @@ -442,15 +443,15 @@ impl QasmCompiler { } } - fn append_qubit_cleanup_calls(&mut self) { + /// Appends qubit release calls at the end of the program. + fn append_qubit_release_calls(&mut self) { for symbol in self.symbols.get_qubit_symbols() { let expr = build_path_ident_expr(&symbol.name, Span::default(), Span::default()); - let cleanup_call = if matches!(symbol.ty, Type::Qubit) { - build_reset_call(expr, Span::default(), Span::default()) + let stmt = if matches!(symbol.ty, Type::Qubit) { + build_qubit_release_call(expr) } else { - build_resetall_call(expr, Span::default(), Span::default()) + build_qubitarray_release_call(expr) }; - let stmt = build_stmt_semi_from_expr(cleanup_call); self.stmts.push(stmt); } } diff --git a/compiler/qsc_qasm3/src/tests/declaration/qubit.rs b/compiler/qsc_qasm3/src/tests/declaration/qubit.rs index 3029d72dae..1ee620d9dc 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/qubit.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/qubit.rs @@ -59,7 +59,7 @@ fn single_qubit_decl_with_qsharp_semantics() -> miette::Result<(), Vec> } #[test] -fn fragment_does_not_generate_qubit_cleanup_calls() -> miette::Result<(), Vec> { +fn fragment_does_not_generate_qubit_release_calls() -> miette::Result<(), Vec> { let source = " qubit q; qubit[3] qs; @@ -88,7 +88,7 @@ fn fragment_does_not_generate_qubit_cleanup_calls() -> miette::Result<(), Vec miette::Result<(), Vec> { +fn file_generates_qubit_release_calls() -> miette::Result<(), Vec> { let source = " qubit q; qubit[3] qs; @@ -114,8 +114,8 @@ fn file_generates_qubit_cleanup_calls() -> miette::Result<(), Vec> { operation program() : Unit { let q = QIR.Runtime.__quantum__rt__qubit_allocate(); let qs = QIR.Runtime.AllocateQubitArray(3); - Reset(q); - ResetAll(qs); + QIR.Runtime.__quantum__rt__qubit_release(q); + QIR.Runtime.ReleaseQubitArray(qs); } }"#]] .assert_eq(&qsharp); @@ -123,7 +123,7 @@ fn file_generates_qubit_cleanup_calls() -> miette::Result<(), Vec> { } #[test] -fn operation_generates_qubit_cleanup_calls() -> miette::Result<(), Vec> { +fn operation_generates_qubit_release_calls() -> miette::Result<(), Vec> { let source = " qubit q; qubit[3] qs; @@ -147,8 +147,8 @@ fn operation_generates_qubit_cleanup_calls() -> miette::Result<(), Vec> import QasmStd.Intrinsic.*; let q = QIR.Runtime.__quantum__rt__qubit_allocate(); let qs = QIR.Runtime.AllocateQubitArray(3); - Reset(q); - ResetAll(qs); + QIR.Runtime.__quantum__rt__qubit_release(q); + QIR.Runtime.ReleaseQubitArray(qs); } "#]] .assert_eq(&qsharp); diff --git a/compiler/qsc_qasm3/src/tests/expression/function_call.rs b/compiler/qsc_qasm3/src/tests/expression/function_call.rs index 8fbd5433ba..1599bf4d13 100644 --- a/compiler/qsc_qasm3/src/tests/expression/function_call.rs +++ b/compiler/qsc_qasm3/src/tests/expression/function_call.rs @@ -341,7 +341,6 @@ fn simulatable_intrinsic_on_def_stmt_generates_correct_qir() -> miette::Result<( 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__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__rt__tuple_record_output(i64 0, i8* null) ret void } @@ -350,8 +349,6 @@ fn simulatable_intrinsic_on_def_stmt_generates_correct_qir() -> miette::Result<( declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 - declare void @__quantum__qis__reset__body(%Qubit*) #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" } diff --git a/compiler/qsc_qasm3/src/tests/output.rs b/compiler/qsc_qasm3/src/tests/output.rs index f4c742e9ed..fa820382fc 100644 --- a/compiler/qsc_qasm3/src/tests/output.rs +++ b/compiler/qsc_qasm3/src/tests/output.rs @@ -53,7 +53,7 @@ fn using_re_semantics_removes_output() -> miette::Result<(), Vec> { 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]); - ResetAll(q); + QIR.Runtime.ReleaseQubitArray(q); } }"#]] .assert_eq(&qsharp); @@ -105,7 +105,7 @@ fn using_qasm_semantics_captures_all_classical_decls_as_output() -> miette::Resu 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]); - ResetAll(q); + QIR.Runtime.ReleaseQubitArray(q); (c, gamma, delta) } }"#]] @@ -157,7 +157,7 @@ fn using_qiskit_semantics_only_bit_array_is_captured_and_reversed( 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]); - ResetAll(q); + QIR.Runtime.ReleaseQubitArray(q); Microsoft.Quantum.Arrays.Reversed(c) } }"#]] @@ -224,7 +224,7 @@ c2[2] = measure q[4]; set c2 w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[2]); set c2 w/= 1 <- QIR.Intrinsic.__quantum__qis__m__body(q[3]); set c2 w/= 2 <- QIR.Intrinsic.__quantum__qis__m__body(q[4]); - ResetAll(q); + QIR.Runtime.ReleaseQubitArray(q); (Microsoft.Quantum.Arrays.Reversed(c2), Microsoft.Quantum.Arrays.Reversed(c)) } }"#]] @@ -278,11 +278,6 @@ c2[2] = measure q[4]; 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__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*)) - call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 1 to %Qubit*)) - call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 2 to %Qubit*)) - call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 3 to %Qubit*)) - call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 4 to %Qubit*)) 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) @@ -306,8 +301,6 @@ c2[2] = measure q[4]; declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 - declare void @__quantum__qis__reset__body(%Qubit*) #1 - declare void @__quantum__rt__tuple_record_output(i64, i8*) declare void @__quantum__rt__array_record_output(i64, i8*) diff --git a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs index 4c1eff65e3..896520275d 100644 --- a/compiler/qsc_qasm3/src/tests/statement/gate_call.rs +++ b/compiler/qsc_qasm3/src/tests/statement/gate_call.rs @@ -127,8 +127,6 @@ fn barrier_generates_qir() -> miette::Result<(), Vec> { call void @__quantum__qis__barrier__body() call void @__quantum__qis__barrier__body() call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) - call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*)) - call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 1 to %Qubit*)) call void @__quantum__rt__array_record_output(i64 1, i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) ret void @@ -138,8 +136,6 @@ fn barrier_generates_qir() -> miette::Result<(), Vec> { declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 - declare void @__quantum__qis__reset__body(%Qubit*) #1 - declare void @__quantum__rt__array_record_output(i64, i8*) declare void @__quantum__rt__result_record_output(%Result*, i8*) @@ -500,7 +496,6 @@ fn simulatable_intrinsic_on_gate_stmt_generates_correct_qir() -> miette::Result< 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__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__rt__tuple_record_output(i64 0, i8* null) ret void } @@ -509,8 +504,6 @@ fn simulatable_intrinsic_on_gate_stmt_generates_correct_qir() -> miette::Result< declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 - declare void @__quantum__qis__reset__body(%Qubit*) #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" } diff --git a/compiler/qsc_qasm3/src/tests/statement/include.rs b/compiler/qsc_qasm3/src/tests/statement/include.rs index 69d9cd1623..d81eaab99d 100644 --- a/compiler/qsc_qasm3/src/tests/statement/include.rs +++ b/compiler/qsc_qasm3/src/tests/statement/include.rs @@ -53,7 +53,7 @@ fn programs_with_includes_can_be_parsed() -> miette::Result<(), Vec> { let q = QIR.Runtime.AllocateQubitArray(1); my_gate(q[0]); set c w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[0]); - ResetAll(q); + QIR.Runtime.ReleaseQubitArray(q); Microsoft.Quantum.Arrays.Reversed(c) } }"#]] diff --git a/compiler/qsc_qasm3/src/tests/statement/reset.rs b/compiler/qsc_qasm3/src/tests/statement/reset.rs index 89503d95d3..6e043fcbe4 100644 --- a/compiler/qsc_qasm3/src/tests/statement/reset.rs +++ b/compiler/qsc_qasm3/src/tests/statement/reset.rs @@ -45,7 +45,7 @@ fn reset_calls_are_generated_from_qasm() -> miette::Result<(), Vec> { Reset(q[0]); h(q[0]); set meas w/= 0 <- QIR.Intrinsic.__quantum__qis__m__body(q[0]); - ResetAll(q); + QIR.Runtime.ReleaseQubitArray(q); Microsoft.Quantum.Arrays.Reversed(meas) } }"#]] @@ -74,7 +74,6 @@ fn reset_with_base_profile_is_rewritten_without_resets() -> miette::Result<(), V define void @ENTRYPOINT__main() #0 { block_0: call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) - call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 2 to %Qubit*)) call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) call void @__quantum__rt__array_record_output(i64 1, i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) @@ -89,9 +88,7 @@ fn reset_with_base_profile_is_rewritten_without_resets() -> miette::Result<(), V declare void @__quantum__rt__result_record_output(%Result*, i8*) - declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*) - - attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="3" "required_num_results"="1" } + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="2" "required_num_results"="1" } attributes #1 = { "irreversible" } ; module flags @@ -130,7 +127,6 @@ fn reset_with_adaptive_ri_profile_generates_reset_qir() -> miette::Result<(), Ve call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__qis__h__body(%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__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*)) call void @__quantum__rt__array_record_output(i64 1, i8* null) call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) ret void diff --git a/compiler/qsc_qasm3/src/tests/statement/switch.rs b/compiler/qsc_qasm3/src/tests/statement/switch.rs index 491bbb71db..e40a2dfe46 100644 --- a/compiler/qsc_qasm3/src/tests/statement/switch.rs +++ b/compiler/qsc_qasm3/src/tests/statement/switch.rs @@ -207,7 +207,7 @@ fn spec_case_3() -> miette::Result<(), Vec> { } elif __ResultArrayAsIntBE__(b) == 3 { z(q); }; - Reset(q); + QIR.Runtime.__quantum__rt__qubit_release(q); b } }"#]] From 788cce050652711d0e5b6674c3af9599bca95b87 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Tue, 8 Apr 2025 08:44:37 -0700 Subject: [PATCH 97/98] do not add qubit release calls on `QubitSemantics::QSharp` --- compiler/qsc_qasm3/src/compiler.rs | 4 ++- .../qsc_qasm3/src/tests/declaration/qubit.rs | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index aeb8369a66..18a64a26c2 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -110,7 +110,9 @@ impl QasmCompiler { self.compile_stmts(&program.statements); - if matches!(program_ty, ProgramType::File | ProgramType::Operation) { + if matches!(program_ty, ProgramType::File | ProgramType::Operation) + && matches!(self.config.qubit_semantics, QubitSemantics::Qiskit) + { self.append_qubit_release_calls(); } diff --git a/compiler/qsc_qasm3/src/tests/declaration/qubit.rs b/compiler/qsc_qasm3/src/tests/declaration/qubit.rs index 1ee620d9dc..359ff1fc5d 100644 --- a/compiler/qsc_qasm3/src/tests/declaration/qubit.rs +++ b/compiler/qsc_qasm3/src/tests/declaration/qubit.rs @@ -154,3 +154,36 @@ fn operation_generates_qubit_release_calls() -> miette::Result<(), Vec> .assert_eq(&qsharp); Ok(()) } + +#[test] +fn qsharp_semantics_does_not_generate_qubit_release_calls() -> miette::Result<(), Vec> { + let source = " + qubit q; + qubit[3] qs; + "; + + let config = CompilerConfig::new( + QubitSemantics::QSharp, + OutputSemantics::OpenQasm, + ProgramType::File, + None, + None, + ); + + let unit = compile_with_config(source, config)?; + let qsharp = qsharp_from_qasm_compilation(unit)?; + + expect![[r#" + namespace qasm3_import { + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + @EntryPoint() + operation program() : Unit { + use q = Qubit(); + use qs = Qubit[3]; + } + }"#]] + .assert_eq(&qsharp); + Ok(()) +} From 84b2831a4484b457104f577d5769cad367a53528 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Tue, 8 Apr 2025 09:21:16 -0700 Subject: [PATCH 98/98] set release call span to symbol.span --- compiler/qsc_qasm3/src/ast_builder.rs | 16 ++++++++-------- compiler/qsc_qasm3/src/compiler.rs | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/qsc_qasm3/src/ast_builder.rs b/compiler/qsc_qasm3/src/ast_builder.rs index 231e49f371..53ab6c2905 100644 --- a/compiler/qsc_qasm3/src/ast_builder.rs +++ b/compiler/qsc_qasm3/src/ast_builder.rs @@ -732,26 +732,26 @@ pub(crate) fn build_reset_call(expr: ast::Expr, name_span: Span, operand_span: S build_global_call_with_one_param("Reset", expr, name_span, operand_span) } -pub fn build_qubit_release_call(expr: ast::Expr) -> ast::Stmt { +pub fn build_qubit_release_call(expr: ast::Expr, span: Span) -> ast::Stmt { let expr = build_call_with_param( "__quantum__rt__qubit_release", &["QIR", "Runtime"], expr, - Span::default(), - Span::default(), - Span::default(), + span, + span, + span, ); build_stmt_semi_from_expr(expr) } -pub fn build_qubitarray_release_call(expr: ast::Expr) -> ast::Stmt { +pub fn build_qubitarray_release_call(expr: ast::Expr, span: Span) -> ast::Stmt { let expr = build_call_with_param( "ReleaseQubitArray", &["QIR", "Runtime"], expr, - Span::default(), - Span::default(), - Span::default(), + span, + span, + span, ); build_stmt_semi_from_expr(expr) } diff --git a/compiler/qsc_qasm3/src/compiler.rs b/compiler/qsc_qasm3/src/compiler.rs index 18a64a26c2..7398a95244 100644 --- a/compiler/qsc_qasm3/src/compiler.rs +++ b/compiler/qsc_qasm3/src/compiler.rs @@ -450,9 +450,9 @@ impl QasmCompiler { for symbol in self.symbols.get_qubit_symbols() { let expr = build_path_ident_expr(&symbol.name, Span::default(), Span::default()); let stmt = if matches!(symbol.ty, Type::Qubit) { - build_qubit_release_call(expr) + build_qubit_release_call(expr, symbol.span) } else { - build_qubitarray_release_call(expr) + build_qubitarray_release_call(expr, symbol.span) }; self.stmts.push(stmt); }