Skip to content

Commit

Permalink
Support Unimplemented attribute (#826)
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).

---------

Co-authored-by: Scott Carda <55811729+ScottCarda-MS@users.noreply.github.com>
  • Loading branch information
swernli and ScottCarda-MS authored Nov 9, 2023
1 parent b74867c commit d0f6102
Show file tree
Hide file tree
Showing 14 changed files with 349 additions and 61 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
173 changes: 173 additions & 0 deletions compiler/qsc_frontend/src/compile/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -876,3 +876,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);
}
64 changes: 36 additions & 28 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 @@ -124,7 +124,7 @@ impl With<'_> {
}

pub(super) fn lower_namespace(&mut self, namespace: &ast::Namespace) {
let Some(&resolve::Res::Item(hir::ItemId { item: id, .. })) =
let Some(&resolve::Res::Item(hir::ItemId { item: id, .. }, _)) =
self.names.get(namespace.name.id)
else {
panic!("namespace should have item ID");
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 Expand Up @@ -706,7 +714,7 @@ impl With<'_> {

fn lower_path(&mut self, path: &ast::Path) -> hir::Res {
match self.names.get(path.id) {
Some(&resolve::Res::Item(item)) => hir::Res::Item(item),
Some(&resolve::Res::Item(item, _)) => hir::Res::Item(item),
Some(&resolve::Res::Local(node)) => hir::Res::Local(self.lower_id(node)),
Some(resolve::Res::PrimTy(_) | resolve::Res::UnitTy | resolve::Res::Param(_))
| None => hir::Res::Err,
Expand Down
Loading

0 comments on commit d0f6102

Please sign in to comment.