From 28c271516d67fb3dbf38583d863172982c3b4114 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 2 May 2023 16:56:16 -0700 Subject: [PATCH] Add semantic validation pass (#264) --- compiler/qsc_passes/src/lib.rs | 8 + compiler/qsc_passes/src/semantics.rs | 113 +++++++++++++ compiler/qsc_passes/src/semantics/tests.rs | 181 +++++++++++++++++++++ 3 files changed, 302 insertions(+) create mode 100644 compiler/qsc_passes/src/semantics.rs create mode 100644 compiler/qsc_passes/src/semantics/tests.rs diff --git a/compiler/qsc_passes/src/lib.rs b/compiler/qsc_passes/src/lib.rs index 5ef35554f7..83c5789785 100644 --- a/compiler/qsc_passes/src/lib.rs +++ b/compiler/qsc_passes/src/lib.rs @@ -8,6 +8,7 @@ pub mod entry_point; mod invert_block; mod logic_sep; pub mod loop_unification; +pub mod semantics; pub mod spec_gen; use miette::Diagnostic; @@ -25,12 +26,19 @@ pub enum Error { EntryPoint(entry_point::Error), SpecGen(spec_gen::Error), ConjInvert(conjugate_invert::Error), + Semantic(semantics::Error), } /// Run the default set of passes required for evaluation. pub fn run_default_passes(unit: &mut CompileUnit) -> Vec { let mut errors = Vec::new(); + errors.extend( + semantics::validate_semantics(unit) + .into_iter() + .map(Error::Semantic), + ); + errors.extend( spec_gen::generate_specs(unit) .into_iter() diff --git a/compiler/qsc_passes/src/semantics.rs b/compiler/qsc_passes/src/semantics.rs new file mode 100644 index 0000000000..d6558fae24 --- /dev/null +++ b/compiler/qsc_passes/src/semantics.rs @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#[cfg(test)] +mod tests; + +use miette::Diagnostic; +use qsc_data_structures::span::Span; +use qsc_frontend::compile::CompileUnit; +use qsc_hir::{ + hir::{CallableBody, CallableDecl, CallableKind, Expr, ExprKind, Spec, Stmt, StmtKind, Ty}, + visit::{walk_callable_decl, walk_expr, walk_stmt, Visitor}, +}; +use thiserror::Error; + +#[derive(Clone, Debug, Diagnostic, Error)] +pub enum Error { + #[error("functions cannot use conjugate expressions")] + ConjInFunc(#[label] Span), + + #[error("functions cannot have functor expressions")] + FunctorInFunc(#[label] Span), + + #[error("functions cannot call operations")] + OpCallInFunc(#[label] Span), + + #[error("functions cannot allocate qubits")] + QubitAllocInFunc(#[label] Span), + + #[error("functions cannot use repeat-loop expressions")] + RepeatInFunc(#[label] Span), + + #[error("functions cannot have specializations")] + SpecInFunc(#[label] Span), + + #[error("operations cannot use while-loop expressions")] + WhileInOp(#[label] Span), +} + +#[allow(clippy::module_name_repetitions)] +#[must_use] +pub fn validate_semantics(unit: &CompileUnit) -> Vec { + let mut pass = Validate { + errors: Vec::new(), + in_func: false, + in_op: false, + }; + pass.visit_package(&unit.package); + pass.errors +} + +struct Validate { + errors: Vec, + in_func: bool, + in_op: bool, +} + +impl<'a> Visitor<'a> for Validate { + fn visit_callable_decl(&mut self, decl: &'a CallableDecl) { + self.in_func = decl.kind == CallableKind::Function; + self.in_op = decl.kind == CallableKind::Operation; + + if self.in_func { + match &decl.body { + CallableBody::Specs(specs) => { + if specs.iter().any(|spec| spec.spec != Spec::Body) { + self.errors.push(Error::SpecInFunc(decl.span)); + } + } + CallableBody::Block(_) => {} + } + match &decl.functors { + Some(functors) => self.errors.push(Error::FunctorInFunc(functors.span)), + None => {} + } + } + + walk_callable_decl(self, decl); + self.in_func = false; + self.in_op = false; + } + + fn visit_stmt(&mut self, stmt: &'a Stmt) { + if let StmtKind::Qubit(..) = &stmt.kind { + if self.in_func { + self.errors.push(Error::QubitAllocInFunc(stmt.span)); + } + } + walk_stmt(self, stmt); + } + + fn visit_expr(&mut self, expr: &'a Expr) { + match &expr.kind { + ExprKind::Call(callee, _) + if matches!(callee.ty, Ty::Arrow(CallableKind::Operation, _, _, _)) + && self.in_func => + { + self.errors.push(Error::OpCallInFunc(expr.span)); + } + ExprKind::Conjugate(..) if self.in_func => { + self.errors.push(Error::ConjInFunc(expr.span)); + } + ExprKind::Repeat(..) if self.in_func => { + self.errors.push(Error::RepeatInFunc(expr.span)); + } + ExprKind::While(..) if self.in_op => { + self.errors.push(Error::WhileInOp(expr.span)); + } + _ => {} + } + walk_expr(self, expr); + } +} diff --git a/compiler/qsc_passes/src/semantics/tests.rs b/compiler/qsc_passes/src/semantics/tests.rs new file mode 100644 index 0000000000..7a4c916ac1 --- /dev/null +++ b/compiler/qsc_passes/src/semantics/tests.rs @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(clippy::too_many_lines)] + +use expect_test::{expect, Expect}; +use indoc::indoc; +use qsc_frontend::compile::{compile, PackageStore, SourceMap}; + +use crate::semantics::validate_semantics; + +fn check(file: &str, expect: &Expect) { + let store = PackageStore::new(); + let sources = SourceMap::new([("test".into(), file.into())], None); + let unit = compile(&store, [], sources); + assert!(unit.errors.is_empty(), "{:?}", unit.errors); + + let errors = validate_semantics(&unit); + expect.assert_debug_eq(&errors); +} + +#[test] +fn funcs_cannot_use_conj() { + check( + indoc! {" + namespace Test { + function A() : Unit { + within {} apply {} + } + } + "}, + &expect![[r#" + [ + ConjInFunc( + Span { + lo: 51, + hi: 69, + }, + ), + ] + "#]], + ); +} + +#[test] +fn funcs_cannot_have_functors() { + check( + indoc! {" + namespace Test { + function A() : Unit is Adj {} + } + "}, + &expect![[r#" + [ + FunctorInFunc( + Span { + lo: 44, + hi: 47, + }, + ), + ] + "#]], + ); +} + +#[test] +fn funcs_cannot_call_ops() { + check( + indoc! {" + namespace Test { + operation A() : Unit {} + function B() : Unit { + A(); + } + } + "}, + &expect![[r#" + [ + OpCallInFunc( + Span { + lo: 79, + hi: 82, + }, + ), + ] + "#]], + ); +} + +#[test] +fn funcs_cannot_allocate_qubits() { + check( + indoc! {" + namespace Test { + function A() : Unit { + use q = Qubit(); + } + } + "}, + &expect![[r#" + [ + QubitAllocInFunc( + Span { + lo: 51, + hi: 67, + }, + ), + ] + "#]], + ); +} + +#[test] +fn funcs_cannot_use_repeat() { + check( + indoc! {" + namespace Test { + function A() : Unit { + repeat {} until true; + } + } + "}, + &expect![[r#" + [ + RepeatInFunc( + Span { + lo: 51, + hi: 71, + }, + ), + ] + "#]], + ); +} + +#[test] +fn funcs_cannot_have_specs() { + check( + indoc! {" + namespace Test { + function A() : Unit { + body ... {} + adjoint self; + } + } + "}, + &expect![[r#" + [ + SpecInFunc( + Span { + lo: 21, + hi: 90, + }, + ), + ] + "#]], + ); +} + +#[test] +fn ops_cannot_use_while() { + check( + indoc! {" + namespace Test { + operation B() : Unit { + while true {} + } + } + "}, + &expect![[r#" + [ + WhileInOp( + Span { + lo: 52, + hi: 65, + }, + ), + ] + "#]], + ); +}