Skip to content

Commit

Permalink
Support Unimplemented attribute
Browse files Browse the repository at this point in the history
This change introduces the @unimplemented() attributed which can be used to mark an item as something that should not be used. The motivation behind including this and not just treating it as a Not Found error is to give the user more information that this API may have previously existing and is not currently supported (see #699 for context).
  • Loading branch information
swernli committed Oct 30, 2023
1 parent 7a98382 commit 97a5024
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 48 deletions.
3 changes: 2 additions & 1 deletion compiler/qsc_frontend/src/compile/preprocess.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use qsc_ast::{
ast::{Attr, ExprKind, ItemKind, Namespace, Stmt, StmtKind},
mut_visit::MutVisitor,
};
use qsc_hir::hir;
use std::rc::Rc;

use super::TargetProfile;
Expand Down Expand Up @@ -119,7 +120,7 @@ impl MutVisitor for Conditional {

fn matches_target(attrs: &[Box<Attr>], target: TargetProfile) -> bool {
attrs.iter().all(|attr| {
if attr.name.name.as_ref() == "Config" {
if hir::Attr::from_str(attr.name.name.as_ref()) == Ok(hir::Attr::Config) {
if let ExprKind::Paren(inner) = attr.arg.kind.as_ref() {
match inner.kind.as_ref() {
ExprKind::Path(path) => {
Expand Down
178 changes: 176 additions & 2 deletions compiler/qsc_frontend/src/compile/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use qsc_data_structures::span::Span;
use qsc_hir::{
global,
hir::{
Block, Expr, ExprKind, ItemId, ItemKind, Lit, LocalItemId, NodeId, Res, SpecBody, Stmt,
StmtKind,
Block, Expr, ExprKind, ItemId, ItemKind, ItemStatus, Lit, LocalItemId, NodeId, Res,
SpecBody, Stmt, StmtKind,
},
mut_visit::MutVisitor,
ty::{Prim, Ty},
Expand Down Expand Up @@ -268,6 +268,7 @@ fn entry_call_operation() {
&Res::Item(ItemId {
package: None,
item: LocalItemId::from(1),
status: ItemStatus::Normal,
}),
res
);
Expand Down Expand Up @@ -876,3 +877,176 @@ fn two_files_error_eof() {
Namespace (Ident 1 [26-29] "Bar"): <empty>"#]]
.assert_eq(&unit.package.to_string());
}

#[test]
fn unimplemented_call_from_dependency_produces_error() {
let lib_sources = SourceMap::new(
[(
"lib".into(),
indoc! {"
namespace Foo {
@Unimplemented()
operation Bar() : Unit {}
}
"}
.into(),
)],
None,
);
let mut store = PackageStore::new(super::core());
let lib = compile(&store, &[], lib_sources, TargetProfile::Full);
assert!(lib.errors.is_empty(), "{:#?}", lib.errors);
let lib = store.insert(lib);

let sources = SourceMap::new(
[(
"test".into(),
indoc! {"
namespace Test {
open Foo;
operation Main() : Unit {
Bar();
}
}
"}
.into(),
)],
None,
);
let unit = compile(&store, &[lib], sources, TargetProfile::Full);
expect![[r#"
[
Error(
Resolve(
Unimplemented(
"Bar",
Span {
lo: 69,
hi: 72,
},
),
),
),
]
"#]]
.assert_debug_eq(&unit.errors);
}

#[test]
fn unimplemented_attribute_call_within_unit_error() {
let sources = SourceMap::new(
[(
"test".into(),
indoc! {"
namespace Foo {
@Unimplemented()
operation Bar() : Unit {}
operation Baz() : Unit {
Bar();
}
}
"}
.into(),
)],
None,
);
let store = PackageStore::new(super::core());
let unit = compile(&store, &[], sources, TargetProfile::Full);
expect![[r#"
[
Error(
Resolve(
Unimplemented(
"Bar",
Span {
lo: 104,
hi: 107,
},
),
),
),
]
"#]]
.assert_debug_eq(&unit.errors);
}

#[test]
fn unimplemented_attribute_with_non_unit_expr_error() {
let sources = SourceMap::new(
[(
"test".into(),
indoc! {"
namespace Foo {
@Unimplemented(1)
operation Bar() : Unit {}
}
"}
.into(),
)],
None,
);
let store = PackageStore::new(super::core());
let unit = compile(&store, &[], sources, TargetProfile::Full);
expect![[r#"
[
Error(
Lower(
InvalidAttrArgs(
"()",
Span {
lo: 34,
hi: 37,
},
),
),
),
]
"#]]
.assert_debug_eq(&unit.errors);
}

#[test]
fn unimplemented_attribute_avoids_ambiguous_error_with_duplicate_names_in_scope() {
let lib_sources = SourceMap::new(
[(
"lib".into(),
indoc! {"
namespace Foo {
@Unimplemented()
operation Bar() : Unit {}
}
"}
.into(),
)],
None,
);
let mut store = PackageStore::new(super::core());
let lib = compile(&store, &[], lib_sources, TargetProfile::Full);
assert!(lib.errors.is_empty(), "{:#?}", lib.errors);
let lib = store.insert(lib);

let sources = SourceMap::new(
[(
"test".into(),
indoc! {"
namespace Dependency {
operation Bar() : Unit {}
}
namespace Test {
open Foo;
open Dependency;
operation Main() : Unit {
Bar();
}
}
"}
.into(),
)],
None,
);
let unit = compile(&store, &[lib], sources, TargetProfile::Full);
expect![[r#"
[]
"#]]
.assert_debug_eq(&unit.errors);
}
60 changes: 34 additions & 26 deletions compiler/qsc_frontend/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use qsc_hir::{
mut_visit::MutVisitor,
ty::{Arrow, FunctorSetValue, Ty},
};
use std::{clone::Clone, rc::Rc, vec};
use std::{clone::Clone, rc::Rc, str::FromStr, vec};
use thiserror::Error;

#[derive(Clone, Debug, Diagnostic, Error)]
Expand Down Expand Up @@ -167,7 +167,7 @@ impl With<'_> {
};

let resolve_id = |id| match self.names.get(id) {
Some(&resolve::Res::Item(hir::ItemId { item, .. })) => item,
Some(&resolve::Res::Item(item)) => item,
_ => panic!("item should have item ID"),
};

Expand All @@ -176,7 +176,7 @@ impl With<'_> {
ast::ItemKind::Callable(callable) => {
let id = resolve_id(callable.name.id);
let grandparent = self.lowerer.parent;
self.lowerer.parent = Some(id);
self.lowerer.parent = Some(id.item);
let callable = self.lower_callable_decl(callable);
self.lowerer.parent = grandparent;
(id, hir::ItemKind::Callable(callable))
Expand All @@ -186,18 +186,15 @@ impl With<'_> {
let udt = self
.tys
.udts
.get(&hir::ItemId {
package: None,
item: id,
})
.get(&id)
.expect("type item should have lowered UDT");

(id, hir::ItemKind::Ty(self.lower_ident(name), udt.clone()))
}
};

self.lowerer.items.push(hir::Item {
id,
id: id.item,
span: item.span,
parent: self.lowerer.parent,
doc: Rc::clone(&item.doc),
Expand All @@ -206,36 +203,47 @@ impl With<'_> {
kind,
});

Some(id)
Some(id.item)
}

fn lower_attr(&mut self, attr: &ast::Attr) -> Option<hir::Attr> {
if attr.name.name.as_ref() == "EntryPoint" {
match &*attr.arg.kind {
match hir::Attr::from_str(attr.name.name.as_ref()) {
Ok(hir::Attr::EntryPoint) => match &*attr.arg.kind {
ast::ExprKind::Tuple(args) if args.is_empty() => Some(hir::Attr::EntryPoint),
_ => {
self.lowerer
.errors
.push(Error::InvalidAttrArgs("()", attr.arg.span));
None
}
},
Ok(hir::Attr::Unimplemented) => match &*attr.arg.kind {
ast::ExprKind::Tuple(args) if args.is_empty() => Some(hir::Attr::Unimplemented),
_ => {
self.lowerer
.errors
.push(Error::InvalidAttrArgs("()", attr.arg.span));
None
}
},
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 TargetProfile::from_str(path.name.name.as_ref()).is_ok()))
{
self.lowerer
.errors
.push(Error::InvalidAttrArgs("Full or Base", attr.arg.span));
}
None
}
} else if attr.name.name.as_ref() == "Config" {
if !matches!(attr.arg.kind.as_ref(), ast::ExprKind::Paren(inner)
if matches!(inner.kind.as_ref(), ast::ExprKind::Path(path)
if TargetProfile::is_target_str(path.name.name.as_ref())))
{
self.lowerer
.errors
.push(Error::InvalidAttrArgs("Full or Base", attr.arg.span));
Err(()) => {
self.lowerer.errors.push(Error::UnknownAttr(
attr.name.name.to_string(),
attr.name.span,
));
None
}
None
} else {
self.lowerer.errors.push(Error::UnknownAttr(
attr.name.name.to_string(),
attr.name.span,
));
None
}
}

Expand Down
Loading

0 comments on commit 97a5024

Please sign in to comment.