Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions samples/OpenQASM/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# OpenQASM Support

The QDK supports a useful subset of OpenQASM 3 for simulation, debugging,
circuit generation, resource estimation, and Azure Quantum submission. Programs
using OpenQASM hardware-control features can still be edited in VS Code even
when the QDK cannot compile them.

The QDK does not compile these construct families:

* Calibration blocks and `defcal` definitions
* Timing and duration operations such as `delay`
* Hardware qubit addressing
* `extern` declarations
* Mutable array references

Use the `qdk.openqasm.mode` setting to choose how the editor treats a file:

* `auto` is the default. It uses QDK mode until the file contains a construct
the QDK cannot compile, then uses spec mode.
* `qdk` reports unsupported constructs as errors and enables QDK features such
as Run, Debug, circuit generation, resource estimation, and submission.
* `spec` reports OpenQASM syntax and semantic errors while disabling QDK-only
features. A code lens and Command Palette commands switch the file back to
QDK mode when those features are needed.

The sample files in this directory are QDK-compatible examples. The editor's
spec mode is intended for OpenQASM programs that use the standard beyond the
subset the QDK currently compiles.
4 changes: 4 additions & 0 deletions source/language_service/src/code_lens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pub(crate) fn get_code_lenses(
return vec![]; // entrypoint actions don't work in notebooks
}

if compilation.is_openqasm_spec_mode() {
return vec![]; // these lenses all run the program through the QDK
}

if !compilation.project_errors.is_empty()
|| compilation
.compile_errors
Expand Down
32 changes: 32 additions & 0 deletions source/language_service/src/code_lens/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
use super::get_code_lenses;
use crate::{
Encoding,
compilation::Compilation,
protocol::OpenQasmMode,
test_utils::{
compile_notebook_with_fake_stdlib, compile_with_fake_stdlib_and_markers_no_cursor,
},
};
use expect_test::{Expect, expect};
use qsc::PackageType;
use std::sync::Arc;

fn check(source_with_markers: &str, expect: &Expect) {
let (compilation, expected_code_lens_ranges) =
Expand Down Expand Up @@ -240,3 +244,31 @@ fn no_code_lenses_with_compilation_errors() {
"code lenses should not be present when there are compilation errors"
);
}

const OPENQASM_PROGRAM: &str = r#"OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q;
bit[2] c;
h q[0];
cx q[0], q[1];
c = measure q;
"#;

fn openqasm_lenses(mode: OpenQasmMode) -> Vec<crate::protocol::CodeLens> {
let compilation = Compilation::new_qasm(
PackageType::Exe,
vec![(Arc::from("<source>"), Arc::from(OPENQASM_PROGRAM))],
vec![],
&Arc::from("test project"),
mode,
);
get_code_lenses(&compilation, "<source>", Encoding::Utf8)
}

#[test]
fn no_code_lenses_for_openqasm_in_spec_mode() {
// The same program in qdk mode has lenses, so the suppression is the mode's
// doing rather than the program having nothing to offer.
assert!(!openqasm_lenses(OpenQasmMode::Qdk).is_empty());
assert!(openqasm_lenses(OpenQasmMode::Spec).is_empty());
}
99 changes: 91 additions & 8 deletions source/language_service/src/compilation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::protocol::{EffectiveOpenQasmMode, OpenQasmMode};
use log::trace;
use qsc::{
CompileUnit, LanguageFeatures, PackageStore, PackageType, PassContext, SourceMap, Span, ast,
Expand All @@ -12,7 +13,8 @@ use qsc::{
line_column::{Encoding, Position, Range},
openqasm::{
CompileRawQasmResult, CompilerConfig, OutputSemantics, ProgramType, QubitSemantics,
compiler::compile_to_qsharp_ast_with_config,
compiler::compile_to_qsharp_ast_with_config, semantic::AnalysisResult,
source::SourceMap as ParseSourceMap,
},
packages::{BuildableProgram, prepare_package_store},
project, resolve,
Expand All @@ -23,6 +25,9 @@ use qsc_project::{PackageGraphSources, Project, ProjectType};
use std::mem::take;
use std::sync::Arc;

#[cfg(test)]
mod openqasm_mode_tests;

/// Represents an immutable compilation state that can be used
/// to implement language service features.
#[derive(Debug)]
Expand Down Expand Up @@ -56,6 +61,8 @@ pub(crate) enum CompilationKind {
sources: Vec<(Arc<str>, Arc<str>)>,
/// a human-readable name for the package (not a unique URI -- meant to be read by humans)
friendly_name: Arc<str>,
/// The mode this compilation actually ran in.
effective_mode: EffectiveOpenQasmMode,
},
}

Expand Down Expand Up @@ -244,6 +251,7 @@ impl Compilation {
sources: Vec<(Arc<str>, Arc<str>)>,
project_errors: Vec<project::Error>,
friendly_name: &Arc<str>,
requested_mode: OpenQasmMode,
) -> Self {
let config = CompilerConfig::new(
QubitSemantics::Qiskit,
Expand All @@ -252,8 +260,14 @@ impl Compilation {
Some("program".into()),
None,
);

let res = qsc::openqasm::analyze_all(&sources);
let stage_one = stage_one_diagnostics(&res);
let unit = compile_to_qsharp_ast_with_config(res, config);
// Lowering seeds the unit from the stage-1 set and appends, so a longer
// list means stage 2 rejected something the QDK cannot represent.
let stage_two_appended = unit.errors().len() > stage_one.len();
let effective_mode = resolve_openqasm_mode(requested_mode, stage_two_appended);
let target_profile = unit.profile().unwrap_or(Profile::Unrestricted);
let CompileRawQasmResult(store, source_package_id, _, _sig, mut compile_errors, _) =
qsc::openqasm::compile_openqasm(unit, package_type);
Expand All @@ -262,27 +276,45 @@ impl Compilation {
.get(source_package_id)
.expect("expected to find user package");

run_fir_passes(
&mut compile_errors,
target_profile,
&store,
source_package_id,
compile_unit,
);
if effective_mode == EffectiveOpenQasmMode::Spec {
// Everything past semantic analysis describes the QDK's view of the
// program, which spec mode does not report on.
compile_errors = stage_one;
} else {
run_fir_passes(
&mut compile_errors,
target_profile,
&store,
source_package_id,
compile_unit,
);
}

Self {
package_store: store,
user_package_id: source_package_id,
kind: CompilationKind::OpenQASM {
sources,
friendly_name: friendly_name.clone(),
effective_mode,
},
compile_errors,
project_errors,
test_cases: vec![],
}
}

/// Whether this compilation is an OpenQASM compilation running in spec mode.
pub(crate) fn is_openqasm_spec_mode(&self) -> bool {
matches!(
self.kind,
CompilationKind::OpenQASM {
effective_mode: EffectiveOpenQasmMode::Spec,
..
}
)
}

/// Returns a human-readable compilation name if one exists.
/// Notebooks don't have human-readable compilation names.
pub fn friendly_project_name(&self) -> Option<Arc<str>> {
Expand Down Expand Up @@ -346,6 +378,7 @@ impl Compilation {
target_profile: Profile,
language_features: LanguageFeatures,
lints_config: &[LintOrGroupConfig],
openqasm_mode: OpenQasmMode,
) {
let new = match self.kind {
CompilationKind::OpenProject {
Expand Down Expand Up @@ -378,21 +411,71 @@ impl Compilation {
CompilationKind::OpenQASM {
ref sources,
ref friendly_name,
..
} => Self::new_qasm(
package_type,
sources.clone(),
Vec::new(), // project errors will stay the same
friendly_name,
openqasm_mode,
),
};

self.package_store = new.package_store;
self.user_package_id = new.user_package_id;
self.test_cases = new.test_cases;
self.compile_errors = new.compile_errors;
// Carries the freshly resolved OpenQASM mode; equivalent to the old kind
// for the other compilation types.
self.kind = new.kind;
}
}

/// Applies the mode resolution order: an explicit `Qdk` or `Spec` wins, and
/// `Auto` selects `Spec` only when stage 2 rejected something.
///
/// Detection is deliberately stage 2 only. A stage-3 or stage-4 failure means
/// the QDK tried and something else went wrong, and switching to spec mode
/// there would hide the only diagnostic explaining the failure.
fn resolve_openqasm_mode(
requested: OpenQasmMode,
stage_two_appended: bool,
) -> EffectiveOpenQasmMode {
match requested {
OpenQasmMode::Qdk => EffectiveOpenQasmMode::Qdk,
OpenQasmMode::Spec => EffectiveOpenQasmMode::Spec,
OpenQasmMode::Auto => {
if stage_two_appended {
EffectiveOpenQasmMode::Spec
} else {
EffectiveOpenQasmMode::Qdk
}
}
}
}

/// Converts the OpenQASM semantic analysis diagnostics into the compilation's
/// error type.
fn stage_one_diagnostics(res: &AnalysisResult) -> Vec<WithSource<compile::ErrorKind>> {
res.all_errors()
.into_iter()
.map(|e| {
WithSource::from_map(
&to_qsharp_source_map(&res.source_map),
compile::ErrorKind::OpenQasm(e.into_error().into()),
)
})
.collect()
}

fn to_qsharp_source_map(source_map: &ParseSourceMap) -> SourceMap {
let sources = source_map
.iter()
.map(|source| (source.name.clone(), source.contents.clone()));
let entry = source_map.entry().map(|source| source.contents.clone());
SourceMap::new(sources, entry)
}

/// Runs the passes required for code generation
/// appending any errors to the `errors` vector.
/// This function only runs passes if there are no compile
Expand Down
Loading