diff --git a/compiler/qsc/src/codegen.rs b/compiler/qsc/src/codegen.rs index 80b893b2cf..f926f9aca6 100644 --- a/compiler/qsc/src/codegen.rs +++ b/compiler/qsc/src/codegen.rs @@ -9,7 +9,6 @@ use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCap use qsc_frontend::compile::{PackageStore, SourceMap}; use qsc_partial_eval::ProgramEntry; use qsc_passes::{PackageType, PassContext}; -use qsc_rca::Analyzer; use crate::compile; @@ -59,18 +58,12 @@ pub fn get_qir( .into(), }; - let compute_properties = if capabilities == TargetCapabilityFlags::empty() { - // baseprofchk already handled compliance, run the analyzer to get the compute properties. - let analyzer = Analyzer::init(&fir_store); - Ok(analyzer.analyze_all()) - } else { + let Ok(compute_properties) = PassContext::run_fir_passes_on_fir(&fir_store, fir_package_id, capabilities) - }; - - let Ok(compute_properties) = compute_properties else { + else { // This should never happen, as the program should be checked for errors before trying to // generate code for it. But just in case, simply report the failure. - return Err("Failed to generate QIR. Could not generate compute properties.".to_string()); + return Err("Failed to generate QIR. Program contains errors.".to_string()); }; fir_to_qir(&fir_store, capabilities, Some(compute_properties), &entry) diff --git a/compiler/qsc/src/compile.rs b/compiler/qsc/src/compile.rs index 246238d232..547e019109 100644 --- a/compiler/qsc/src/compile.rs +++ b/compiler/qsc/src/compile.rs @@ -43,6 +43,7 @@ pub fn compile_ast( sources: SourceMap, package_type: PackageType, capabilities: TargetCapabilityFlags, + language_features: LanguageFeatures, ) -> (CompileUnit, Vec) { let unit = qsc_frontend::compile::compile_ast( store, @@ -52,7 +53,7 @@ pub fn compile_ast( capabilities, vec![], ); - process_compile_unit(store, package_type, capabilities, unit) + process_compile_unit(store, package_type, capabilities, unit, language_features) } /// Compiles a package from its source representation. @@ -72,7 +73,7 @@ pub fn compile( capabilities, language_features, ); - process_compile_unit(store, package_type, capabilities, unit) + process_compile_unit(store, package_type, capabilities, unit, language_features) } #[must_use] @@ -82,14 +83,17 @@ fn process_compile_unit( package_type: PackageType, capabilities: TargetCapabilityFlags, mut unit: CompileUnit, + language_features: LanguageFeatures, ) -> (CompileUnit, Vec) { let mut errors = Vec::new(); for error in unit.errors.drain(..) { errors.push(WithSource::from_map(&unit.sources, error.into())); } + let use_baseprofck = + capabilities.is_empty() && !language_features.contains(LanguageFeatures::PreviewQirGen); if errors.is_empty() { - for error in run_default_passes(store.core(), &mut unit, package_type, capabilities) { + for error in run_default_passes(store.core(), &mut unit, package_type, use_baseprofck) { errors.push(WithSource::from_map(&unit.sources, error.into())); } } @@ -126,7 +130,7 @@ pub fn core() -> CompileUnit { #[must_use] pub 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, capabilities); + let pass_errors = run_default_passes(store.core(), &mut unit, PackageType::Lib, false); if pass_errors.is_empty() { unit } else { diff --git a/compiler/qsc/src/incremental.rs b/compiler/qsc/src/incremental.rs index 536affb1a4..100fbdf6d2 100644 --- a/compiler/qsc/src/incremental.rs +++ b/compiler/qsc/src/incremental.rs @@ -74,12 +74,13 @@ impl Compiler { language_features, ); let store = store.open(); - + let use_baseprofck = + capabilities.is_empty() && !language_features.contains(LanguageFeatures::PreviewQirGen); Ok(Self { store, source_package_id, frontend, - passes: PassContext::new(capabilities), + passes: PassContext::new(use_baseprofck), }) } @@ -92,12 +93,13 @@ impl Compiler { let frontend = qsc_frontend::incremental::Compiler::new(&store, [], capabilities, language_features); let store = store.open(); - + let use_baseprofck = + capabilities.is_empty() && !language_features.contains(LanguageFeatures::PreviewQirGen); Ok(Self { store, source_package_id, frontend, - passes: PassContext::new(capabilities), + passes: PassContext::new(use_baseprofck), }) } diff --git a/compiler/qsc_codegen/src/qir_base/tests.rs b/compiler/qsc_codegen/src/qir_base/tests.rs index 8cf1af5fec..25e3730906 100644 --- a/compiler/qsc_codegen/src/qir_base/tests.rs +++ b/compiler/qsc_codegen/src/qir_base/tests.rs @@ -19,13 +19,7 @@ fn check(program: &str, expr: Option<&str>, expect: &Expect) { assert!(run_core_passes(&mut core).is_empty()); let mut store = PackageStore::new(core); let mut std = compile::std(&store, TargetCapabilityFlags::empty()); - assert!(run_default_passes( - store.core(), - &mut std, - PackageType::Lib, - TargetCapabilityFlags::empty() - ) - .is_empty()); + assert!(run_default_passes(store.core(), &mut std, PackageType::Lib, false).is_empty()); let std = store.insert(std); let expr_as_arc: Option> = expr.map(|s| Arc::from(s.to_string())); @@ -39,13 +33,7 @@ fn check(program: &str, expr: Option<&str>, expect: &Expect) { LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); - assert!(run_default_passes( - store.core(), - &mut unit, - PackageType::Exe, - TargetCapabilityFlags::empty() - ) - .is_empty()); + assert!(run_default_passes(store.core(), &mut unit, PackageType::Exe, false).is_empty()); let package = store.insert(unit); let qir = generate_qir(&store, package); diff --git a/compiler/qsc_codegen/src/qsharp/test_utils.rs b/compiler/qsc_codegen/src/qsharp/test_utils.rs index e13d728e7a..98ef9e90f5 100644 --- a/compiler/qsc_codegen/src/qsharp/test_utils.rs +++ b/compiler/qsc_codegen/src/qsharp/test_utils.rs @@ -34,13 +34,7 @@ pub(crate) fn get_compilation(sources: Option) -> (PackageId, Package assert!(run_core_passes(&mut core).is_empty()); let mut store = PackageStore::new(core); let mut std = compile::std(&store, TargetCapabilityFlags::empty()); - assert!(run_default_passes( - store.core(), - &mut std, - PackageType::Lib, - TargetCapabilityFlags::empty() - ) - .is_empty()); + assert!(run_default_passes(store.core(), &mut std, PackageType::Lib, false).is_empty()); let std = store.insert(std); let mut unit = compile( @@ -51,13 +45,7 @@ pub(crate) fn get_compilation(sources: Option) -> (PackageId, Package LanguageFeatures::empty(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); - assert!(run_default_passes( - store.core(), - &mut unit, - PackageType::Lib, - TargetCapabilityFlags::all() - ) - .is_empty()); + assert!(run_default_passes(store.core(), &mut unit, PackageType::Lib, false).is_empty()); let package_id = store.insert(unit); (package_id, store) } diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs index 9aa50bc015..ce151589d8 100644 --- a/compiler/qsc_eval/src/intrinsic/tests.rs +++ b/compiler/qsc_eval/src/intrinsic/tests.rs @@ -154,13 +154,7 @@ fn check_intrinsic(file: &str, expr: &str, out: &mut impl Receiver) -> Result Result Self { + pub fn new(use_baseprofck: bool) -> Self { Self { - capabilities, + use_baseprofck, borrow_check: borrowck::Checker::default(), } } @@ -117,7 +117,7 @@ impl PassContext { ReplaceQubitAllocation::new(core, assigner).visit_package(package); Validator::default().visit_package(package); - let base_prof_errors = if self.capabilities == TargetCapabilityFlags::empty() { + let base_prof_errors = if self.use_baseprofck { baseprofck::check_base_profile_compliance(package) } else { Vec::new() @@ -148,9 +148,9 @@ pub fn run_default_passes( core: &Table, unit: &mut CompileUnit, package_type: PackageType, - capabilities: TargetCapabilityFlags, + use_baseprofck: bool, ) -> Vec { - PassContext::new(capabilities).run_default_passes( + PassContext::new(use_baseprofck).run_default_passes( &mut unit.package, &mut unit.assigner, core, diff --git a/language_service/src/compilation.rs b/language_service/src/compilation.rs index 35f919fa94..961394aada 100644 --- a/language_service/src/compilation.rs +++ b/language_service/src/compilation.rs @@ -77,13 +77,15 @@ impl Compilation { .get(package_id) .expect("expected to find user package"); - run_fir_passes( - &mut errors, - target_profile, - &package_store, - package_id, - unit, - ); + if language_features.contains(LanguageFeatures::PreviewQirGen) { + run_fir_passes( + &mut errors, + target_profile, + &package_store, + package_id, + unit, + ); + } run_linter_passes(lints_config, &mut errors, unit); @@ -133,13 +135,15 @@ impl Compilation { .get(package_id) .expect("expected to find user package"); - run_fir_passes( - &mut errors, - target_profile, - &package_store, - package_id, - unit, - ); + if language_features.contains(LanguageFeatures::PreviewQirGen) { + run_fir_passes( + &mut errors, + target_profile, + &package_store, + package_id, + unit, + ); + } run_linter_passes(lints_config, &mut errors, unit); @@ -257,11 +261,6 @@ fn run_fir_passes( return; } - if target_profile == Profile::Base { - // baseprofchk will handle the case where the target profile is Base - return; - } - if target_profile == Profile::Unrestricted { // no point in running passes on unrestricted profile return; diff --git a/language_service/src/protocol.rs b/language_service/src/protocol.rs index 2ec8c97939..de37d4e376 100644 --- a/language_service/src/protocol.rs +++ b/language_service/src/protocol.rs @@ -10,6 +10,7 @@ use qsc_project::Manifest; pub struct WorkspaceConfigurationUpdate { pub target_profile: Option, pub package_type: Option, + pub language_features: Option, } #[derive(Debug)] diff --git a/language_service/src/state.rs b/language_service/src/state.rs index fa40339fc6..0706bfd9dd 100644 --- a/language_service/src/state.rs +++ b/language_service/src/state.rs @@ -91,9 +91,9 @@ struct PartialConfiguration { } impl PartialConfiguration { - pub fn from_language_features(features: LanguageFeatures) -> Self { + pub fn from_language_features(features: Option) -> Self { Self { - language_features: Some(features), + language_features: features, ..Default::default() } } @@ -128,7 +128,7 @@ pub(super) struct CompilationStateUpdater<'a> { struct LoadManifestResult { compilation_uri: Arc, sources: Vec<(Arc, Arc)>, - language_features: LanguageFeatures, + language_features: Option, lints: Vec, } @@ -180,7 +180,7 @@ impl<'a> CompilationStateUpdater<'a> { LoadManifestResult { compilation_uri: doc_uri.clone(), sources: vec![(doc_uri.clone(), text.clone())], - language_features: LanguageFeatures::default(), + language_features: None, lints: Vec::default(), } }); @@ -228,11 +228,13 @@ impl<'a> CompilationStateUpdater<'a> { Ok(o) => Some(LoadManifestResult { compilation_uri: manifest.compilation_uri(), sources: o.sources, - language_features: manifest - .manifest - .language_features - .iter() - .collect::(), + language_features: Some( + manifest + .manifest + .language_features + .iter() + .collect::(), + ), lints: manifest.manifest.lints.clone(), }), Err(e) => { @@ -254,7 +256,7 @@ impl<'a> CompilationStateUpdater<'a> { &mut self, mut sources: Vec<(Arc, Arc)>, compilation_uri: &Arc, - language_features: LanguageFeatures, + language_features: Option, lints_config: &[LintConfig], ) { self.with_state_mut(|state| { @@ -268,20 +270,22 @@ impl<'a> CompilationStateUpdater<'a> { } } + let compilation_overrides = + PartialConfiguration::from_language_features(language_features); + + let configuration = merge_configurations(&compilation_overrides, &self.configuration); + let compilation = Compilation::new( &sources, - self.configuration.package_type, - self.configuration.target_profile, - language_features, + configuration.package_type, + configuration.target_profile, + configuration.language_features, lints_config, ); state.compilations.insert( compilation_uri.clone(), - ( - compilation, - PartialConfiguration::from_language_features(language_features), - ), + (compilation, compilation_overrides), ); }); } @@ -478,6 +482,11 @@ impl<'a> CompilationStateUpdater<'a> { self.configuration.target_profile = target_profile; } + if let Some(language_features) = configuration.language_features { + need_recompile |= self.configuration.language_features != language_features; + self.configuration.language_features = language_features; + } + // Possible optimization: some projects will have overrides for these configurations, // so workspace updates won't impact them. We could exclude those projects // from recompilation, but we don't right now. @@ -493,9 +502,6 @@ impl<'a> CompilationStateUpdater<'a> { for (compilation, package_specific_configuration) in state.compilations.values_mut() { let configuration = merge_configurations(package_specific_configuration, &self.configuration); - let language_features = package_specific_configuration - .language_features - .unwrap_or_default(); let lints_config = package_specific_configuration .lints_config .clone() @@ -503,7 +509,7 @@ impl<'a> CompilationStateUpdater<'a> { compilation.recompile( configuration.package_type, configuration.target_profile, - language_features, + configuration.language_features, &lints_config, ); } diff --git a/language_service/src/state/tests.rs b/language_service/src/state/tests.rs index ecc93fd48c..9070f7b9df 100644 --- a/language_service/src/state/tests.rs +++ b/language_service/src/state/tests.rs @@ -7,7 +7,7 @@ use super::{CompilationState, CompilationStateUpdater}; use crate::protocol::{DiagnosticUpdate, NotebookMetadata, WorkspaceConfigurationUpdate}; use expect_test::{expect, Expect}; -use qsc::{compile::ErrorKind, target::Profile, PackageType}; +use qsc::{compile::ErrorKind, target::Profile, LanguageFeatures, PackageType}; use qsc_project::{EntryType, JSFileEntry, Manifest, ManifestDescriptor}; use rustc_hash::FxHashMap; use std::{cell::RefCell, fmt::Write, future::ready, rc::Rc, sync::Arc}; @@ -314,6 +314,7 @@ async fn rca_errors_are_reported_when_compilation_succeeds() { updater.update_configuration(WorkspaceConfigurationUpdate { target_profile: Some(Profile::AdaptiveRI), package_type: Some(PackageType::Lib), + language_features: Some(LanguageFeatures::PreviewQirGen), }); updater @@ -358,6 +359,122 @@ async fn rca_errors_are_reported_when_compilation_succeeds() { ); } +#[tokio::test] +async fn base_profile_rca_errors_are_reported_when_compilation_succeeds() { + let errors = RefCell::new(Vec::new()); + let mut updater = new_updater(&errors); + + updater.update_configuration(WorkspaceConfigurationUpdate { + target_profile: Some(Profile::Base), + package_type: Some(PackageType::Lib), + language_features: Some(LanguageFeatures::PreviewQirGen), + }); + + updater + .update_document("single/foo.qs", 1, "namespace Test { operation RcaCheck() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x } }") + .await; + + // we expect two errors, one for `set x = 2` and one for `x` + expect_errors( + &errors, + &expect![[r#" + [ + ( + "single/foo.qs", + Some( + 1, + ), + [ + Pass( + CapabilitiesCk( + UseOfDynamicBool( + Span { + lo: 86, + hi: 103, + }, + ), + ), + ), + Pass( + CapabilitiesCk( + UseOfDynamicDouble( + Span { + lo: 106, + hi: 117, + }, + ), + ), + ), + Pass( + CapabilitiesCk( + UseOfDynamicDouble( + Span { + lo: 121, + hi: 122, + }, + ), + ), + ), + ], + ), + ] + "#]], + ); +} + +#[tokio::test] +async fn baseprofchk_errors_are_reported_when_not_using_preview_qir() { + let errors = RefCell::new(Vec::new()); + let mut updater = new_updater(&errors); + + updater.update_configuration(WorkspaceConfigurationUpdate { + target_profile: Some(Profile::Base), + package_type: Some(PackageType::Lib), + language_features: None, + }); + + updater + .update_document("single/foo.qs", 1, "namespace Test { operation RcaCheck() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x } }") + .await; + + // we expect two errors, one for `set x = 2` and one for `x` + expect_errors( + &errors, + &expect![[r#" + [ + ( + "single/foo.qs", + Some( + 1, + ), + [ + Pass( + BaseProfCk( + ResultComparison( + Span { + lo: 86, + hi: 103, + }, + ), + ), + ), + Pass( + BaseProfCk( + ResultLiteral( + Span { + lo: 100, + hi: 103, + }, + ), + ), + ), + ], + ), + ] + "#]], + ); +} + #[tokio::test] async fn package_type_update_causes_error() { let errors = RefCell::new(Vec::new()); @@ -366,6 +483,7 @@ async fn package_type_update_causes_error() { updater.update_configuration(WorkspaceConfigurationUpdate { target_profile: None, package_type: Some(PackageType::Lib), + language_features: None, }); updater @@ -386,6 +504,7 @@ async fn package_type_update_causes_error() { updater.update_configuration(WorkspaceConfigurationUpdate { target_profile: None, package_type: Some(PackageType::Exe), + language_features: None, }); expect_errors( @@ -418,6 +537,7 @@ async fn target_profile_update_fixes_error() { updater.update_configuration(WorkspaceConfigurationUpdate { target_profile: Some(Profile::Base), package_type: Some(PackageType::Lib), + language_features: None, }); updater @@ -477,6 +597,7 @@ async fn target_profile_update_fixes_error() { updater.update_configuration(WorkspaceConfigurationUpdate { target_profile: Some(Profile::Unrestricted), package_type: None, + language_features: None, }); expect_errors( @@ -516,6 +637,7 @@ async fn target_profile_update_causes_error_in_stdlib() { updater.update_configuration(WorkspaceConfigurationUpdate { target_profile: Some(Profile::Base), package_type: None, + language_features: None, }); expect_errors( diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index cfe08847b7..9d3b55a5f5 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -18,7 +18,7 @@ import { qsharpLanguageId, } from "./common.js"; import { createCompletionItemProvider } from "./completion"; -import { getTarget } from "./config"; +import { getEnablePreviewQirGen, getTarget } from "./config"; import { activateDebugger } from "./debugger/activate"; import { createDefinitionProvider } from "./definition"; import { startCheckingQSharp } from "./diagnostics"; @@ -323,6 +323,22 @@ async function updateLanguageServiceProfile(languageService: ILanguageService) { }); } +async function updateLanguageServiceFeature(languageService: ILanguageService) { + const enablePreviewQir = getEnablePreviewQirGen(); + + if (enablePreviewQir) { + log.debug("Enabling preview QIR generation"); + languageService.updateConfiguration({ + languageFeatures: ["preview-qir-gen"], + }); + } else { + log.debug("Disabling preview QIR generation"); + languageService.updateConfiguration({ + languageFeatures: [], + }); + } +} + async function loadLanguageService(baseUri: vscode.Uri) { const start = performance.now(); const wasmUri = vscode.Uri.joinPath(baseUri, "./wasm/qsc_wasm_bg.wasm"); @@ -334,6 +350,7 @@ async function loadLanguageService(baseUri: vscode.Uri) { getManifest, ); await updateLanguageServiceProfile(languageService); + await updateLanguageServiceFeature(languageService); const end = performance.now(); sendTelemetryEvent( EventType.LoadLanguageService, @@ -350,6 +367,9 @@ function registerConfigurationChangeHandlers( if (event.affectsConfiguration("Q#.qir.targetProfile")) { updateLanguageServiceProfile(languageService); } + if (event.affectsConfiguration("Q#.enablePreviewQirGen")) { + updateLanguageServiceFeature(languageService); + } }); } diff --git a/wasm/src/language_service.rs b/wasm/src/language_service.rs index 304c2ab9f3..e3abf60c33 100644 --- a/wasm/src/language_service.rs +++ b/wasm/src/language_service.rs @@ -100,6 +100,9 @@ impl LanguageService { "exe" => PackageType::Exe, _ => panic!("invalid package type"), }), + language_features: config + .languageFeatures + .map(|features| features.iter().collect::()), }); } @@ -331,10 +334,12 @@ serializable_type! { { pub targetProfile: Option, pub packageType: Option, + pub languageFeatures: Option>, }, r#"export interface IWorkspaceConfiguration { targetProfile?: TargetProfile; packageType?: "exe" | "lib"; + languageFeatures?: LanguageFeatures[]; }"#, IWorkspaceConfiguration } diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index ac1fe0033d..8848a0d2e2 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -494,5 +494,10 @@ pub fn generate_docs( #[wasm_bindgen(typescript_custom_section)] const TARGET_PROFILE: &'static str = r#" -export type TargetProfile = "base" | "adaptive_ri" |"unrestricted"; +export type TargetProfile = "base" | "adaptive_ri" | "unrestricted"; +"#; + +#[wasm_bindgen(typescript_custom_section)] +const LANGUAGE_FEATURES: &'static str = r#" +export type LanguageFeatures = "v2-preview-syntax" | "preview-qir-gen"; "#;