Skip to content

Commit

Permalink
Support logical not in @Config attribute (#1564)
Browse files Browse the repository at this point in the history
This change adds support for logical `not` inside of `@Config`
attributes and makes it work as expected with the additive native of the
attributes. So to specify an item that should only be available when
compiling with `Adaptive` and `IntegerComputations`, but not with
`HigherLevelConstructs` like dynamic strings, you can write:
```qsharp
@config(Adaptive)
@config(IntegerComputations)
@config(not HigherLevelConstructs)
operation Foo() : Unit { }
```
  • Loading branch information
swernli authored May 31, 2024
1 parent 7c48b35 commit 51d517b
Show file tree
Hide file tree
Showing 10 changed files with 577 additions and 59 deletions.
46 changes: 41 additions & 5 deletions compiler/qsc_frontend/src/compile/preprocess.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -131,29 +131,65 @@ fn matches_config(attrs: &[Box<Attr>], 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 {
// Something other than a parenthesized expression, so we assume it matches
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))
}
28 changes: 20 additions & 8 deletions compiler/qsc_frontend/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading

0 comments on commit 51d517b

Please sign in to comment.