diff --git a/compiler/qsc_frontend/src/compile/preprocess.rs b/compiler/qsc_frontend/src/compile/preprocess.rs index f3c03567ff..8dcebe629d 100644 --- a/compiler/qsc_frontend/src/compile/preprocess.rs +++ b/compiler/qsc_frontend/src/compile/preprocess.rs @@ -3,7 +3,7 @@ use core::str::FromStr; use qsc_ast::{ - ast::{Attr, ExprKind, ItemKind, Namespace, Stmt, StmtKind}, + ast::{Attr, ExprKind, ItemKind, Namespace, Stmt, StmtKind, UnOp}, mut_visit::MutVisitor, }; use qsc_hir::hir; @@ -131,18 +131,43 @@ fn matches_config(attrs: &[Box], capabilities: TargetCapabilityFlags) -> b return true; } let mut found_capabilities = TargetCapabilityFlags::empty(); + let mut disallowed_capabilities = TargetCapabilityFlags::empty(); + let mut base = false; + let mut not_base = false; + // When checking attributes, anything we don't recognize (invalid form or invalid capability) gets + // left in the compilation by returning true. This ensures that later compilation steps, specifically lowering + // from AST to HIR, can check the attributes and return errors as appropriate. for attr in attrs { if let ExprKind::Paren(inner) = attr.arg.kind.as_ref() { match inner.kind.as_ref() { ExprKind::Path(path) => { if let Ok(capability) = TargetCapabilityFlags::from_str(path.name.name.as_ref()) { + if capability.is_empty() { + base = true; + } found_capabilities |= capability; } else { return true; // Unknown capability, so we assume it matches } } + ExprKind::UnOp(UnOp::NotL, inner) => { + if let ExprKind::Path(path) = inner.kind.as_ref() { + if let Ok(capability) = + TargetCapabilityFlags::from_str(path.name.name.as_ref()) + { + if capability.is_empty() { + not_base = true; + } + disallowed_capabilities |= capability; + } else { + return true; // Unknown capability, so we assume it matches + } + } else { + return true; // Unknown config attribute, so we assume it matches + } + } _ => return true, // Unknown config attribute, so we assume it matches } } else { @@ -150,10 +175,21 @@ fn matches_config(attrs: &[Box], capabilities: TargetCapabilityFlags) -> b return true; } } - if found_capabilities == TargetCapabilityFlags::empty() { - // There was at least one config attribute, but it was None - // Therefore, we only match if there are no capabilities - return capabilities == TargetCapabilityFlags::empty(); + if found_capabilities.is_empty() && disallowed_capabilities.is_empty() { + if not_base && !base { + // There was at least one config attribute, but it was "not Base" so + // ensure that the capabilities are not empty. + return capabilities != TargetCapabilityFlags::empty(); + } else if base && !not_base { + // There was at least one config attribute, but it was Base + // Therefore, we only match if there are no capabilities + return capabilities == TargetCapabilityFlags::empty(); + } + + // The config specified both "Base" and "not Base" which is a contradiction, but we + // drop the item in this case. + return false; } capabilities.contains(found_capabilities) + && (disallowed_capabilities.is_empty() || !capabilities.contains(disallowed_capabilities)) } diff --git a/compiler/qsc_frontend/src/lower.rs b/compiler/qsc_frontend/src/lower.rs index a65a93122d..c64c6d2180 100644 --- a/compiler/qsc_frontend/src/lower.rs +++ b/compiler/qsc_frontend/src/lower.rs @@ -226,14 +226,26 @@ impl With<'_> { } }, Ok(hir::Attr::Config) => { - if !matches!(attr.arg.kind.as_ref(), ast::ExprKind::Paren(inner) - if matches!(inner.kind.as_ref(), ast::ExprKind::Path(path) - if TargetCapabilityFlags::from_str(path.name.name.as_ref()).is_ok())) - { - self.lowerer.errors.push(Error::InvalidAttrArgs( - "runtime capability".to_string(), - attr.arg.span, - )); + match &*attr.arg.kind { + // @Config(Capability) + ast::ExprKind::Paren(inner) + if matches!(inner.kind.as_ref(), ast::ExprKind::Path(path) + if TargetCapabilityFlags::from_str(path.name.name.as_ref()).is_ok()) => {} + + // @Config(not Capability) + ast::ExprKind::Paren(inner) + if matches!(inner.kind.as_ref(), ast::ExprKind::UnOp(ast::UnOp::NotL, inner) + if matches!(inner.kind.as_ref(), ast::ExprKind::Path(path) + if TargetCapabilityFlags::from_str(path.as_ref().name.name.as_ref()).is_ok())) => + {} + + // Any other form is not valid so generates an error. + _ => { + self.lowerer.errors.push(Error::InvalidAttrArgs( + "runtime capability".to_string(), + attr.arg.span, + )); + } } None } diff --git a/compiler/qsc_frontend/src/resolve/tests.rs b/compiler/qsc_frontend/src/resolve/tests.rs index 159284852d..186de5e7b9 100644 --- a/compiler/qsc_frontend/src/resolve/tests.rs +++ b/compiler/qsc_frontend/src/resolve/tests.rs @@ -125,11 +125,16 @@ impl Visitor<'_> for Renamer<'_> { } fn check(input: &str, expect: &Expect) { - expect.assert_eq(&resolve_names(input)); + expect.assert_eq(&resolve_names(input, TargetCapabilityFlags::all())); } -fn resolve_names(input: &str) -> String { - let (package, names, _, errors, namespaces) = compile(input, LanguageFeatures::default()); +fn check_with_capabilities(input: &str, capabilities: TargetCapabilityFlags, expect: &Expect) { + expect.assert_eq(&resolve_names(input, capabilities)); +} + +fn resolve_names(input: &str, capabilities: TargetCapabilityFlags) -> String { + let (package, names, _, errors, namespaces) = + compile(input, LanguageFeatures::default(), capabilities); let mut renamer = Renamer::new(&names, namespaces); renamer.visit_package(&package); let mut output = input.to_string(); @@ -146,6 +151,7 @@ fn resolve_names(input: &str) -> String { fn compile( input: &str, language_features: LanguageFeatures, + capabilities: TargetCapabilityFlags, ) -> (Package, Names, Locals, Vec, NamespaceTreeRoot) { let (namespaces, parse_errors) = qsc_parse::namespaces(input, None, language_features); assert!(parse_errors.is_empty(), "parse failed: {parse_errors:#?}"); @@ -161,7 +167,7 @@ fn compile( AstAssigner::new().visit_package(&mut package); - let mut cond_compile = compile::preprocess::Conditional::new(TargetCapabilityFlags::all()); + let mut cond_compile = compile::preprocess::Conditional::new(capabilities); cond_compile.visit_package(&mut package); let dropped_names = cond_compile.into_names(); @@ -2006,8 +2012,37 @@ fn resolve_local_generic() { } #[test] -fn dropped_callable() { - check( +fn dropped_base_callable_from_unrestricted() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(Base) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::all(), + &expect![[r#" + namespace namespace7 { + @Config(Base) + function Dropped() : Unit {} + + function item1() : Unit { + Dropped(); + } + } + + // NotAvailable("Dropped", "A.Dropped", Span { lo: 100, hi: 107 }) + "#]], + ); +} + +#[test] +fn dropped_base_callable_from_adaptive() { + check_with_capabilities( indoc! {" namespace A { @Config(Base) @@ -2018,6 +2053,7 @@ fn dropped_callable() { } } "}, + TargetCapabilityFlags::Adaptive, &expect![[r#" namespace namespace7 { @Config(Base) @@ -2033,6 +2069,462 @@ fn dropped_callable() { ); } +#[test] +fn dropped_not_base_callable_from_base() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(not Base) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::empty(), + &expect![[r#" + namespace namespace7 { + @Config(not Base) + function Dropped() : Unit {} + + function item1() : Unit { + Dropped(); + } + } + + // NotAvailable("Dropped", "A.Dropped", Span { lo: 104, hi: 111 }) + "#]], + ); +} + +#[test] +fn resolved_not_base_callable_from_adaptive() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(not Base) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::Adaptive, + &expect![[r#" + namespace namespace7 { + @Config(not Base) + function item1() : Unit {} + + function item2() : Unit { + item1(); + } + } + "#]], + ); +} + +#[test] +fn dropped_base_and_not_base_callable_from_base() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(Base) + @Config(not Base) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::empty(), + &expect![[r#" + namespace namespace7 { + @Config(Base) + @Config(not Base) + function Dropped() : Unit {} + + function item1() : Unit { + Dropped(); + } + } + + // NotAvailable("Dropped", "A.Dropped", Span { lo: 122, hi: 129 }) + "#]], + ); +} + +#[test] +fn resolved_not_unrestricted_callable_from_base() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(not Unrestricted) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::empty(), + &expect![[r#" + namespace namespace7 { + @Config(not Unrestricted) + function item1() : Unit {} + + function item2() : Unit { + item1(); + } + } + "#]], + ); +} + +#[test] +fn resolved_not_unrestricted_callable_from_adaptive() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(not Unrestricted) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::Adaptive, + &expect![[r#" + namespace namespace7 { + @Config(not Unrestricted) + function item1() : Unit {} + + function item2() : Unit { + item1(); + } + } + "#]], + ); +} + +#[test] +fn dropped_not_unrestricted_callable_from_unrestricted() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(not Unrestricted) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::all(), + &expect![[r#" + namespace namespace7 { + @Config(not Unrestricted) + function Dropped() : Unit {} + + function item1() : Unit { + Dropped(); + } + } + + // NotAvailable("Dropped", "A.Dropped", Span { lo: 112, hi: 119 }) + "#]], + ); +} + +#[test] +fn resolved_adaptive_callable_from_adaptive() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(Adaptive) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::Adaptive, + &expect![[r#" + namespace namespace7 { + @Config(Adaptive) + function item1() : Unit {} + + function item2() : Unit { + item1(); + } + } + "#]], + ); +} + +#[test] +fn resolved_adaptive_callable_from_unrestricted() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(Adaptive) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::all(), + &expect![[r#" + namespace namespace7 { + @Config(Adaptive) + function item1() : Unit {} + + function item2() : Unit { + item1(); + } + } + "#]], + ); +} + +#[test] +fn dropped_not_higher_level_callable_from_unrestricted() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(not HigherLevelConstructs) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::all(), + &expect![[r#" + namespace namespace7 { + @Config(not HigherLevelConstructs) + function Dropped() : Unit {} + + function item1() : Unit { + Dropped(); + } + } + + // NotAvailable("Dropped", "A.Dropped", Span { lo: 121, hi: 128 }) + "#]], + ); +} + +#[test] +fn resolved_not_higher_level_callable_from_adaptive() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(not HigherLevelConstructs) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::Adaptive, + &expect![[r#" + namespace namespace7 { + @Config(not HigherLevelConstructs) + function item1() : Unit {} + + function item2() : Unit { + item1(); + } + } + "#]], + ); +} + +#[test] +fn resolved_not_higher_level_callable_from_base() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(not HigherLevelConstructs) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::empty(), + &expect![[r#" + namespace namespace7 { + @Config(not HigherLevelConstructs) + function item1() : Unit {} + + function item2() : Unit { + item1(); + } + } + "#]], + ); +} + +#[test] +fn dropped_not_higher_level_and_adaptive_callable_from_base() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(Adaptive) + @Config(not HigherLevelConstructs) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::empty(), + &expect![[r#" + namespace namespace7 { + @Config(Adaptive) + @Config(not HigherLevelConstructs) + function Dropped() : Unit {} + + function item1() : Unit { + Dropped(); + } + } + + // NotAvailable("Dropped", "A.Dropped", Span { lo: 143, hi: 150 }) + "#]], + ); +} + +#[test] +fn dropped_not_higher_level_and_adaptive_callable_from_unrestricted() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(Adaptive) + @Config(not HigherLevelConstructs) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::all(), + &expect![[r#" + namespace namespace7 { + @Config(Adaptive) + @Config(not HigherLevelConstructs) + function Dropped() : Unit {} + + function item1() : Unit { + Dropped(); + } + } + + // NotAvailable("Dropped", "A.Dropped", Span { lo: 143, hi: 150 }) + "#]], + ); +} + +#[test] +fn resolved_not_higher_level_and_adaptive_callable_from_adaptive() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(Adaptive) + @Config(not HigherLevelConstructs) + function Dropped() : Unit {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::Adaptive, + &expect![[r#" + namespace namespace7 { + @Config(Adaptive) + @Config(not HigherLevelConstructs) + function item1() : Unit {} + + function item2() : Unit { + item1(); + } + } + "#]], + ); +} + +#[test] +fn dropped_floating_point_from_adaptive() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(FloatingPointComputations) + function Dropped() : Double {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::Adaptive, + &expect![[r#" + namespace namespace7 { + @Config(FloatingPointComputations) + function Dropped() : Double {} + + function item1() : Unit { + Dropped(); + } + } + + // NotAvailable("Dropped", "A.Dropped", Span { lo: 123, hi: 130 }) + "#]], + ); +} + +#[test] +fn resolved_adaptive_and_integer_from_adaptive_and_integer() { + check_with_capabilities( + indoc! {" + namespace A { + @Config(Adaptive) + @Config(IntegerComputations) + function Dropped() : Double {} + + function B() : Unit { + Dropped(); + } + } + "}, + TargetCapabilityFlags::Adaptive | TargetCapabilityFlags::IntegerComputations, + &expect![[r#" + namespace namespace7 { + @Config(Adaptive) + @Config(IntegerComputations) + function item1() : Double {} + + function item2() : Unit { + item1(); + } + } + "#]], + ); +} + #[test] fn multiple_definition_dropped_is_not_found() { check( @@ -2170,7 +2662,11 @@ fn check_locals(input: &str, expect: &Expect) { let cursor_offset = parts[0].len() as u32; let source = parts.join(""); - let (_, _, locals, _, _) = compile(&source, LanguageFeatures::default()); + let (_, _, locals, _, _) = compile( + &source, + LanguageFeatures::default(), + TargetCapabilityFlags::all(), + ); let locals = locals.get_all_at_offset(cursor_offset); let actual = locals.iter().fold(String::new(), |mut output, l| { diff --git a/language_service/src/state/tests.rs b/language_service/src/state/tests.rs index f2de95e54b..bd83337f22 100644 --- a/language_service/src/state/tests.rs +++ b/language_service/src/state/tests.rs @@ -577,31 +577,13 @@ async fn target_profile_update_causes_error_in_stdlib() { 1, ), [ - Frontend( - Error( - Resolve( - NotAvailable( - "ResultAsBool", - "Microsoft.Quantum.Convert.ResultAsBool", - Span { - lo: 121, - hi: 133, - }, - ), - ), - ), - ), - Frontend( - Error( - Type( - Error( - AmbiguousTy( - Span { - lo: 95, - hi: 136, - }, - ), - ), + Pass( + CapabilitiesCk( + UseOfDynamicBool( + Span { + lo: 95, + hi: 136, + }, ), ), ), diff --git a/library/std/convert.qs b/library/std/convert.qs index d3425ce7ae..3e38bb7346 100644 --- a/library/std/convert.qs +++ b/library/std/convert.qs @@ -41,7 +41,6 @@ namespace Microsoft.Quantum.Convert { /// /// # Output /// A `Bool` representing the `input`. - @Config(Adaptive) function ResultAsBool(input : Result) : Bool { input == One } @@ -56,7 +55,6 @@ namespace Microsoft.Quantum.Convert { /// /// # Output /// A `Result` representing the `input`. - @Config(Adaptive) function BoolAsResult(input : Bool) : Result { if input { One } else { Zero } } @@ -183,7 +181,6 @@ namespace Microsoft.Quantum.Convert { /// // The following returns 1 /// let int1 = ResultArrayAsInt([One,Zero]) /// ``` - @Config(Adaptive) function ResultArrayAsInt(results : Result[]) : Int { let nBits = Length(results); Fact(nBits < 64, $"`Length(bits)` must be less than 64, but was {nBits}."); @@ -208,7 +205,6 @@ namespace Microsoft.Quantum.Convert { /// /// # Output /// A `Bool[]` representing the `input`. - @Config(Adaptive) function ResultArrayAsBoolArray(input : Result[]) : Bool[] { mutable output = []; for r in input { @@ -228,7 +224,6 @@ namespace Microsoft.Quantum.Convert { /// /// # Output /// A `Result[]` representing the `input`. - @Config(Adaptive) function BoolArrayAsResultArray(input : Bool[]) : Result[] { mutable output = []; for b in input { diff --git a/library/std/internal.qs b/library/std/internal.qs index 2c4f9430fd..da83205385 100644 --- a/library/std/internal.qs +++ b/library/std/internal.qs @@ -163,7 +163,7 @@ namespace Microsoft.Quantum.Intrinsic { } } - @Config(Base) + @Config(not Adaptive) internal operation AND(control1 : Qubit, control2 : Qubit, target : Qubit) : Unit is Adj { PhaseCCX(control1, control2, target); } diff --git a/library/std/intrinsic.qs b/library/std/intrinsic.qs index 3bb5ff36db..a945a1ce87 100644 --- a/library/std/intrinsic.qs +++ b/library/std/intrinsic.qs @@ -218,7 +218,7 @@ namespace Microsoft.Quantum.Intrinsic { /// ```qsharp /// Measure([PauliZ], [qubit]); /// ``` - @Config(Adaptive) + @Config(QubitReset) operation M(qubit : Qubit) : Result { __quantum__qis__m__body(qubit) } @@ -249,7 +249,7 @@ namespace Microsoft.Quantum.Intrinsic { /// ```qsharp /// Measure([PauliZ], [qubit]); /// ``` - @Config(Base) + @Config(not QubitReset) operation M(qubit : Qubit) : Result { Measure([PauliZ], [qubit]) } @@ -279,7 +279,7 @@ namespace Microsoft.Quantum.Intrinsic { /// # Output /// `Zero` if the +1 eigenvalue is observed, and `One` if /// the -1 eigenvalue is observed. - @Config(Adaptive) + @Config(QubitReset) operation Measure(bases : Pauli[], qubits : Qubit[]) : Result { if Length(bases) != Length(qubits) { fail "Arrays 'bases' and 'qubits' must be of the same length."; @@ -339,7 +339,7 @@ namespace Microsoft.Quantum.Intrinsic { /// /// If the basis array and qubit array are different lengths, then the /// operation will fail. - @Config(Base) + @Config(not QubitReset) operation Measure(bases : Pauli[], qubits : Qubit[]) : Result { if Length(bases) != Length(qubits) { fail "Arrays 'bases' and 'qubits' must be of the same length."; diff --git a/library/std/measurement.qs b/library/std/measurement.qs index 8b9f6ec172..de6af4589c 100644 --- a/library/std/measurement.qs +++ b/library/std/measurement.qs @@ -168,7 +168,6 @@ namespace Microsoft.Quantum.Measurement { /// # Remarks /// This operation resets its input register to the |00...0> state, /// suitable for releasing back to a target machine. - @Config(Adaptive) operation MeasureInteger(target : Qubit[]) : Int { let nBits = Length(target); Fact(nBits < 64, $"`Length(target)` must be less than 64, but was {nBits}."); diff --git a/library/std/unstable_arithmetic_internal.qs b/library/std/unstable_arithmetic_internal.qs index 09c55b0bfd..5125859fa8 100644 --- a/library/std/unstable_arithmetic_internal.qs +++ b/library/std/unstable_arithmetic_internal.qs @@ -285,7 +285,7 @@ namespace Microsoft.Quantum.Unstable.Arithmetic { /// Phys. Rev. A 87, 022328, 2013 /// [arXiv:1212.5069](https://arxiv.org/abs/1212.5069) /// doi:10.1103/PhysRevA.87.022328 - @Config(Base) + @Config(not Adaptive) internal operation ApplyAndAssuming0Target(control1 : Qubit, control2 : Qubit, target : Qubit) : Unit is Adj { H(target); T(target); diff --git a/library/std/unstable_table_lookup.qs b/library/std/unstable_table_lookup.qs index b748aa07ff..96849c77e7 100644 --- a/library/std/unstable_table_lookup.qs +++ b/library/std/unstable_table_lookup.qs @@ -43,7 +43,6 @@ namespace Microsoft.Quantum.Unstable.TableLookup { /// "Windowed arithmetic" /// 3. [arXiv:2211.01133](https://arxiv.org/abs/2211.01133) /// "Space-time optimized table lookup" - @Config(Adaptive) operation Select( data : Bool[][], address : Qubit[], @@ -165,7 +164,6 @@ namespace Microsoft.Quantum.Unstable.TableLookup { /// # References /// - [arXiv:1905.07682](https://arxiv.org/abs/1905.07682) /// "Windowed arithmetic" - @Config(Adaptive) internal operation Unlookup( lookup : (Bool[][], Qubit[], Qubit[]) => Unit, data : Bool[][],