diff --git a/.gitignore b/.gitignore index 8cbb2b6615..70b962534d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ __pycache__/ /fuzz/Cargo.lock .mypy_cache/ .pytest_cache/ +.idea/ diff --git a/Cargo.lock b/Cargo.lock index 65557b0bc1..e6f4d28eea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -992,6 +992,7 @@ dependencies = [ "bitflags 2.4.2", "expect-test", "miette", + "rustc-hash", "serde", ] diff --git a/compiler/qsc/src/interpret/debug.rs b/compiler/qsc/src/interpret/debug.rs index 7674fc5e62..c1539c8740 100644 --- a/compiler/qsc/src/interpret/debug.rs +++ b/compiler/qsc/src/interpret/debug.rs @@ -83,7 +83,7 @@ fn get_item_file_name(store: &PackageStore, id: StoreItemId) -> Option { #[must_use] fn get_ns_name(item: &Item) -> Option { if let ItemKind::Namespace(ns, _) = &item.kind { - Some(ns.name.to_string()) + Some(ns.name().to_string()) } else { None } diff --git a/compiler/qsc/src/lib.rs b/compiler/qsc/src/lib.rs index 6666a7f387..c259869993 100644 --- a/compiler/qsc/src/lib.rs +++ b/compiler/qsc/src/lib.rs @@ -34,7 +34,7 @@ pub mod project { pub use qsc_project::{DirEntry, EntryType, FileSystem, Manifest, ManifestDescriptor}; } -pub use qsc_data_structures::{language_features::LanguageFeatures, span::Span}; +pub use qsc_data_structures::{language_features::LanguageFeatures, namespaces::*, span::Span}; pub use qsc_passes::{PackageType, PassContext}; diff --git a/compiler/qsc_ast/src/ast.rs b/compiler/qsc_ast/src/ast.rs index be3f599617..aeb82ed248 100644 --- a/compiler/qsc_ast/src/ast.rs +++ b/compiler/qsc_ast/src/ast.rs @@ -164,7 +164,7 @@ pub struct Namespace { /// The documentation. pub doc: Rc, /// The namespace name. - pub name: Box, + pub name: VecIdent, /// The items in the namespace. pub items: Box<[Box]>, } @@ -259,7 +259,7 @@ pub enum ItemKind { #[default] Err, /// An `open` item for a namespace with an optional alias. - Open(Box, Option>), + Open(VecIdent, Option>), /// A `newtype` declaration. Ty(Box, Box), } @@ -1273,7 +1273,7 @@ pub struct Path { /// The span. pub span: Span, /// The namespace. - pub namespace: Option>, + pub namespace: Option, /// The declaration name. pub name: Box, } @@ -1306,6 +1306,86 @@ pub struct Ident { pub name: Rc, } +/// A [`VecIdent`] represents a sequence of idents. It provides a helpful abstraction +/// that is more powerful than a simple `Vec`, and is primarily used to represent +/// dot-separated paths. +#[derive(Clone, Debug, Eq, Hash, PartialEq, Default)] +pub struct VecIdent(pub Vec); + +impl From for Vec> { + fn from(v: VecIdent) -> Self { + v.0.iter().map(|i| i.name.clone()).collect() + } +} + +impl From<&VecIdent> for Vec> { + fn from(v: &VecIdent) -> Self { + v.0.iter().map(|i| i.name.clone()).collect() + } +} + +impl From> for VecIdent { + fn from(v: Vec) -> Self { + VecIdent(v) + } +} + +impl From for Vec { + fn from(v: VecIdent) -> Self { + v.0 + } +} + +impl Display for VecIdent { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut buf = Vec::with_capacity(self.0.len()); + + for ident in &self.0 { + buf.push(format!("{ident}")); + } + if buf.len() > 1 { + // use square brackets only if there are more than one ident + write!(f, "[{}]", buf.join(", ")) + } else { + write!(f, "{}", buf[0]) + } + } +} + +impl<'a> IntoIterator for &'a VecIdent { + type IntoIter = std::slice::Iter<'a, Ident>; + type Item = &'a Ident; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} +impl VecIdent { + /// constructs an iter over the [Ident]s that this contains. + pub fn iter(&self) -> std::slice::Iter<'_, Ident> { + self.0.iter() + } + + /// the conjoined span of all idents in the `VecIdent` + #[must_use] + pub fn span(&self) -> Span { + Span { + lo: self.0.first().map(|i| i.span.lo).unwrap_or_default(), + hi: self.0.last().map(|i| i.span.hi).unwrap_or_default(), + } + } + + /// The stringified dot-separated path of the idents in this [`VecIdent`] + /// E.g. `a.b.c` + #[must_use] + pub fn name(&self) -> String { + self.0 + .iter() + .map(|i| i.name.to_string()) + .collect::>() + .join(".") + } +} + impl Default for Ident { fn default() -> Self { Ident { diff --git a/compiler/qsc_ast/src/mut_visit.rs b/compiler/qsc_ast/src/mut_visit.rs index 8b47a31bb4..fcd8431d62 100644 --- a/compiler/qsc_ast/src/mut_visit.rs +++ b/compiler/qsc_ast/src/mut_visit.rs @@ -75,6 +75,9 @@ pub trait MutVisitor: Sized { fn visit_ident(&mut self, ident: &mut Ident) { walk_ident(self, ident); } + fn visit_vec_ident(&mut self, ident: &mut crate::ast::VecIdent) { + walk_vec_ident(self, ident); + } fn visit_span(&mut self, _: &mut Span) {} } @@ -89,7 +92,8 @@ pub fn walk_package(vis: &mut impl MutVisitor, package: &mut Package) { pub fn walk_namespace(vis: &mut impl MutVisitor, namespace: &mut Namespace) { vis.visit_span(&mut namespace.span); - vis.visit_ident(&mut namespace.name); + vis.visit_vec_ident(&mut namespace.name); + namespace.items.iter_mut().for_each(|i| vis.visit_item(i)); } @@ -104,7 +108,7 @@ pub fn walk_item(vis: &mut impl MutVisitor, item: &mut Item) { ItemKind::Callable(decl) => vis.visit_callable_decl(decl), ItemKind::Err => {} ItemKind::Open(ns, alias) => { - vis.visit_ident(ns); + vis.visit_vec_ident(ns); alias.iter_mut().for_each(|a| vis.visit_ident(a)); } ItemKind::Ty(ident, def) => { @@ -333,10 +337,20 @@ pub fn walk_qubit_init(vis: &mut impl MutVisitor, init: &mut QubitInit) { pub fn walk_path(vis: &mut impl MutVisitor, path: &mut Path) { vis.visit_span(&mut path.span); - path.namespace.iter_mut().for_each(|n| vis.visit_ident(n)); + if let Some(ref mut namespace) = path.namespace { + vis.visit_vec_ident(namespace); + } + if let Some(ref mut ns) = path.namespace { + vis.visit_vec_ident(ns); + } vis.visit_ident(&mut path.name); } pub fn walk_ident(vis: &mut impl MutVisitor, ident: &mut Ident) { vis.visit_span(&mut ident.span); } +pub fn walk_vec_ident(vis: &mut impl MutVisitor, ident: &mut crate::ast::VecIdent) { + for ref mut ident in &mut ident.0 { + vis.visit_ident(ident); + } +} diff --git a/compiler/qsc_ast/src/visit.rs b/compiler/qsc_ast/src/visit.rs index f9afe8b098..c96b0a84a8 100644 --- a/compiler/qsc_ast/src/visit.rs +++ b/compiler/qsc_ast/src/visit.rs @@ -5,7 +5,7 @@ use crate::ast::{ Attr, Block, CallableBody, CallableDecl, Expr, ExprKind, FunctorExpr, FunctorExprKind, Ident, Item, ItemKind, Namespace, Package, Pat, PatKind, Path, QubitInit, QubitInitKind, SpecBody, SpecDecl, Stmt, StmtKind, StringComponent, TopLevelNode, Ty, TyDef, TyDefKind, TyKind, - Visibility, + VecIdent, Visibility, }; pub trait Visitor<'a>: Sized { @@ -72,6 +72,8 @@ pub trait Visitor<'a>: Sized { } fn visit_ident(&mut self, _: &'a Ident) {} + + fn visit_vec_ident(&mut self, _: &'a VecIdent) {} } pub fn walk_package<'a>(vis: &mut impl Visitor<'a>, package: &'a Package) { @@ -83,7 +85,7 @@ pub fn walk_package<'a>(vis: &mut impl Visitor<'a>, package: &'a Package) { } pub fn walk_namespace<'a>(vis: &mut impl Visitor<'a>, namespace: &'a Namespace) { - vis.visit_ident(&namespace.name); + vis.visit_vec_ident(&namespace.name); namespace.items.iter().for_each(|i| vis.visit_item(i)); } @@ -94,7 +96,7 @@ pub fn walk_item<'a>(vis: &mut impl Visitor<'a>, item: &'a Item) { ItemKind::Err => {} ItemKind::Callable(decl) => vis.visit_callable_decl(decl), ItemKind::Open(ns, alias) => { - vis.visit_ident(ns); + vis.visit_vec_ident(ns); alias.iter().for_each(|a| vis.visit_ident(a)); } ItemKind::Ty(ident, def) => { @@ -300,6 +302,8 @@ pub fn walk_qubit_init<'a>(vis: &mut impl Visitor<'a>, init: &'a QubitInit) { } pub fn walk_path<'a>(vis: &mut impl Visitor<'a>, path: &'a Path) { - path.namespace.iter().for_each(|n| vis.visit_ident(n)); + if let Some(ref ns) = path.namespace { + vis.visit_vec_ident(ns); + } vis.visit_ident(&path.name); } diff --git a/compiler/qsc_circuit/src/operations/tests.rs b/compiler/qsc_circuit/src/operations/tests.rs index f7dcefd619..d350cd5709 100644 --- a/compiler/qsc_circuit/src/operations/tests.rs +++ b/compiler/qsc_circuit/src/operations/tests.rs @@ -30,7 +30,7 @@ fn compile_one_operation(code: &str) -> (Item, String) { }); let mut namespaces = unit.package.items.values().filter_map(|i| { if let ItemKind::Namespace(ident, _) = &i.kind { - Some(ident.name.clone()) + Some(ident.clone()) } else { None } @@ -44,7 +44,7 @@ fn compile_one_operation(code: &str) -> (Item, String) { ); ( only_callable.clone(), - format!("{only_namespace}.{callable_name}"), + format!("{}.{callable_name}", only_namespace.name()), ) } diff --git a/compiler/qsc_data_structures/Cargo.toml b/compiler/qsc_data_structures/Cargo.toml index bff7698695..a5127b5345 100644 --- a/compiler/qsc_data_structures/Cargo.toml +++ b/compiler/qsc_data_structures/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true miette = { workspace = true } serde = { workspace = true } bitflags = { workspace = true } +rustc-hash = { workspace = true } [dev-dependencies] expect-test = { workspace = true } diff --git a/compiler/qsc_data_structures/src/lib.rs b/compiler/qsc_data_structures/src/lib.rs index 53a850c6e0..8b8e4520f4 100644 --- a/compiler/qsc_data_structures/src/lib.rs +++ b/compiler/qsc_data_structures/src/lib.rs @@ -6,4 +6,5 @@ pub mod functors; pub mod index_map; pub mod language_features; pub mod line_column; +pub mod namespaces; pub mod span; diff --git a/compiler/qsc_data_structures/src/namespaces.rs b/compiler/qsc_data_structures/src/namespaces.rs new file mode 100644 index 0000000000..0a6dc3be21 --- /dev/null +++ b/compiler/qsc_data_structures/src/namespaces.rs @@ -0,0 +1,227 @@ +#[cfg(test)] +mod tests; + +use rustc_hash::FxHashMap; +use std::{fmt::Display, iter::Peekable, ops::Deref, rc::Rc}; + +pub const PRELUDE: [[&str; 3]; 4] = [ + ["Microsoft", "Quantum", "Canon"], + ["Microsoft", "Quantum", "Core"], + ["Microsoft", "Quantum", "Intrinsic"], + ["Microsoft", "Quantum", "Measurement"], +]; + +/// An ID that corresponds to a namespace in the global scope. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Default)] +pub struct NamespaceId(usize); +impl NamespaceId { + /// Create a new namespace ID. + #[must_use] + pub fn new(value: usize) -> Self { + Self(value) + } +} + +impl From for NamespaceId { + fn from(value: usize) -> Self { + Self::new(value) + } +} + +impl From for usize { + fn from(value: NamespaceId) -> Self { + value.0 + } +} + +impl From<&NamespaceId> for usize { + fn from(value: &NamespaceId) -> Self { + value.0 + } +} + +impl Deref for NamespaceId { + type Target = usize; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Display for NamespaceId { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Namespace {}", self.0) + } +} + +#[derive(Debug, Clone)] +pub struct NamespaceTreeRoot { + assigner: usize, + tree: NamespaceTreeNode, +} + +impl NamespaceTreeRoot { + /// Create a new namespace tree root. The assigner is used to assign new namespace IDs. + #[must_use] + pub fn new_from_parts(assigner: usize, tree: NamespaceTreeNode) -> Self { + Self { assigner, tree } + } + + /// Get the namespace tree field. This is the root of the namespace tree. + #[must_use] + pub fn tree(&self) -> &NamespaceTreeNode { + &self.tree + } + + /// Insert a namespace into the tree. If the namespace already exists, return its ID. + pub fn insert_or_find_namespace( + &mut self, + ns: impl IntoIterator>, + ) -> NamespaceId { + self.tree + .insert_or_find_namespace(ns.into_iter().peekable(), &mut self.assigner) + .expect("namespace creation should not fail") + } + + pub fn new_namespace_node( + &mut self, + children: FxHashMap, NamespaceTreeNode>, + ) -> NamespaceTreeNode { + self.assigner += 1; + NamespaceTreeNode { + id: NamespaceId::new(self.assigner), + children, + } + } + + pub fn find_namespace(&self, ns: impl Into>>) -> Option { + self.tree.find_namespace(ns) + } + + #[must_use] + pub fn find_id(&self, id: &NamespaceId) -> (Vec>, &NamespaceTreeNode) { + return self.tree.find_id(*id, vec![]); + } + + #[must_use] + pub fn root_id(&self) -> NamespaceId { + self.tree.id + } +} + +impl Default for NamespaceTreeRoot { + fn default() -> Self { + let mut tree = Self { + assigner: 0, + tree: NamespaceTreeNode { + children: FxHashMap::default(), + id: NamespaceId::new(0), + }, + }; + // insert the prelude namespaces using the `NamespaceTreeRoot` API + for ns in &PRELUDE { + let iter = ns.iter().map(|s| Rc::from(*s)).peekable(); + tree.insert_or_find_namespace(iter); + } + tree + } +} + +#[derive(Debug, Clone)] +pub struct NamespaceTreeNode { + pub children: FxHashMap, NamespaceTreeNode>, + pub id: NamespaceId, +} +impl NamespaceTreeNode { + #[must_use] + pub fn new(id: NamespaceId, children: FxHashMap, NamespaceTreeNode>) -> Self { + Self { children, id } + } + + #[must_use] + pub fn children(&self) -> &FxHashMap, NamespaceTreeNode> { + &self.children + } + + fn get(&self, component: &Rc) -> Option<&NamespaceTreeNode> { + self.children.get(component) + } + + #[must_use] + pub fn id(&self) -> NamespaceId { + self.id + } + + #[must_use] + pub fn contains(&self, ns: impl Into>>) -> bool { + self.find_namespace(ns).is_some() + } + + pub fn find_namespace(&self, ns: impl Into>>) -> Option { + // look up a namespace in the tree and return the id + // do a breadth-first search through NamespaceTree for the namespace + // if it's not found, return None + let mut buf = Rc::new(self); + for component in &ns.into() { + if let Some(next_ns) = buf.get(component) { + buf = Rc::new(next_ns); + } else { + return None; + } + } + Some(buf.id) + } + + /// If the namespace already exists, it will not be inserted. + /// Returns the ID of the namespace. + pub fn insert_or_find_namespace( + &mut self, + mut iter: Peekable, + assigner: &mut usize, + ) -> Option + where + I: Iterator>, + { + let next_item = iter.next()?; + let next_node = self.children.get_mut(&next_item); + match (next_node, iter.peek()) { + (Some(next_node), Some(_)) => { + return next_node.insert_or_find_namespace(iter, assigner); + } + (Some(next_node), None) => { + return Some(next_node.id); + } + _ => {} + } + *assigner += 1; + let mut new_node = + NamespaceTreeNode::new(NamespaceId::new(*assigner), FxHashMap::default()); + if iter.peek().is_none() { + let new_node_id = new_node.id; + self.children.insert(next_item, new_node); + Some(new_node_id) + } else { + let id = new_node.insert_or_find_namespace(iter, assigner); + self.children.insert(next_item, new_node); + id + } + } + + fn find_id( + &self, + id: NamespaceId, + names_buf: Vec>, + ) -> (Vec>, &NamespaceTreeNode) { + if self.id == id { + return (names_buf, &self); + } + for (name, node) in &self.children { + let mut new_buf = names_buf.clone(); + new_buf.push(name.clone()); + let (names, node) = node.find_id(id, new_buf); + if !names.is_empty() { + return (names, node); + } + } + (vec![], &self) + } +} diff --git a/compiler/qsc_data_structures/src/namespaces/tests.rs b/compiler/qsc_data_structures/src/namespaces/tests.rs new file mode 100644 index 0000000000..76cabfa508 --- /dev/null +++ b/compiler/qsc_data_structures/src/namespaces/tests.rs @@ -0,0 +1,306 @@ +use expect_test::expect; + +use super::*; + +#[allow(clippy::too_many_lines)] +#[test] +fn test_tree_construction() { + let mut root = NamespaceTreeRoot::default(); + for i in 0..3 { + for j in 'a'..'d' { + root.insert_or_find_namespace( + vec![Rc::from(format!("ns{i}")), Rc::from(format!("ns{j}"))].into_iter(), + ); + } + } + expect![[r#" + NamespaceTreeRoot { + assigner: 18, + tree: NamespaceTreeNode { + children: { + "ns1": NamespaceTreeNode { + children: { + "nsc": NamespaceTreeNode { + children: {}, + id: NamespaceId( + 14, + ), + }, + "nsb": NamespaceTreeNode { + children: {}, + id: NamespaceId( + 13, + ), + }, + "nsa": NamespaceTreeNode { + children: {}, + id: NamespaceId( + 12, + ), + }, + }, + id: NamespaceId( + 11, + ), + }, + "ns0": NamespaceTreeNode { + children: { + "nsc": NamespaceTreeNode { + children: {}, + id: NamespaceId( + 10, + ), + }, + "nsb": NamespaceTreeNode { + children: {}, + id: NamespaceId( + 9, + ), + }, + "nsa": NamespaceTreeNode { + children: {}, + id: NamespaceId( + 8, + ), + }, + }, + id: NamespaceId( + 7, + ), + }, + "Microsoft": NamespaceTreeNode { + children: { + "Quantum": NamespaceTreeNode { + children: { + "Canon": NamespaceTreeNode { + children: {}, + id: NamespaceId( + 3, + ), + }, + "Measurement": NamespaceTreeNode { + children: {}, + id: NamespaceId( + 6, + ), + }, + "Core": NamespaceTreeNode { + children: {}, + id: NamespaceId( + 4, + ), + }, + "Intrinsic": NamespaceTreeNode { + children: {}, + id: NamespaceId( + 5, + ), + }, + }, + id: NamespaceId( + 2, + ), + }, + }, + id: NamespaceId( + 1, + ), + }, + "ns2": NamespaceTreeNode { + children: { + "nsc": NamespaceTreeNode { + children: {}, + id: NamespaceId( + 18, + ), + }, + "nsb": NamespaceTreeNode { + children: {}, + id: NamespaceId( + 17, + ), + }, + "nsa": NamespaceTreeNode { + children: {}, + id: NamespaceId( + 16, + ), + }, + }, + id: NamespaceId( + 15, + ), + }, + }, + id: NamespaceId( + 0, + ), + }, + } + "#]] + .assert_debug_eq(&root); +} + +#[allow(clippy::too_many_lines)] +#[test] +fn test_find_id() { + let mut root = NamespaceTreeRoot::default(); + let mut id_buf = vec![]; + for i in 0..3 { + for j in 'a'..'d' { + id_buf.push(root.insert_or_find_namespace( + vec![Rc::from(format!("ns{i}")), Rc::from(format!("ns{j}"))].into_iter(), + )); + } + } + let mut result_buf = vec![]; + for id in id_buf { + result_buf.push(root.find_id(&id)); + } + expect![[r#" + [ + ( + [ + "ns0", + "nsa", + ], + NamespaceTreeNode { + children: {}, + id: NamespaceId( + 8, + ), + }, + ), + ( + [ + "ns0", + "nsb", + ], + NamespaceTreeNode { + children: {}, + id: NamespaceId( + 9, + ), + }, + ), + ( + [ + "ns0", + "nsc", + ], + NamespaceTreeNode { + children: {}, + id: NamespaceId( + 10, + ), + }, + ), + ( + [ + "ns1", + "nsa", + ], + NamespaceTreeNode { + children: {}, + id: NamespaceId( + 12, + ), + }, + ), + ( + [ + "ns1", + "nsb", + ], + NamespaceTreeNode { + children: {}, + id: NamespaceId( + 13, + ), + }, + ), + ( + [ + "ns1", + "nsc", + ], + NamespaceTreeNode { + children: {}, + id: NamespaceId( + 14, + ), + }, + ), + ( + [ + "ns2", + "nsa", + ], + NamespaceTreeNode { + children: {}, + id: NamespaceId( + 16, + ), + }, + ), + ( + [ + "ns2", + "nsb", + ], + NamespaceTreeNode { + children: {}, + id: NamespaceId( + 17, + ), + }, + ), + ( + [ + "ns2", + "nsc", + ], + NamespaceTreeNode { + children: {}, + id: NamespaceId( + 18, + ), + }, + ), + ] + "#]] + .assert_debug_eq(&result_buf); +} +// test that after inserting lots of namespaces, all ids are unique and sequential +#[test] +fn test_insert_or_find_namespace() { + let mut root = NamespaceTreeRoot::default(); + let mut ids: Vec = vec![]; + for i in 0..3 { + for j in 'a'..'d' { + let id = root.insert_or_find_namespace( + vec![Rc::from(format!("ns{i}")), Rc::from(format!("ns{j}"))].into_iter(), + ); + ids.push(id.into()); + } + } + let mut ids_sorted = ids.clone(); + ids_sorted.sort_unstable(); + ids_sorted.dedup(); + // there should be no duplicate or out-of-order ids + assert_eq!(ids_sorted, ids); + expect![[r" + [ + 8, + 9, + 10, + 12, + 13, + 14, + 16, + 17, + 18, + ] + "]] + .assert_debug_eq(&ids); +} diff --git a/compiler/qsc_doc_gen/src/generate_docs.rs b/compiler/qsc_doc_gen/src/generate_docs.rs index 2a19ce79ff..44b1075501 100644 --- a/compiler/qsc_doc_gen/src/generate_docs.rs +++ b/compiler/qsc_doc_gen/src/generate_docs.rs @@ -163,10 +163,10 @@ fn get_namespace(package: &Package, item: &Item) -> Option> { .expect("Could not resolve parent item id"); match &parent.kind { ItemKind::Namespace(name, _) => { - if name.name.starts_with("QIR") { + if name.starts_with("QIR") { None // We ignore "QIR" namespaces } else { - Some(name.name.clone()) + Some(Rc::from(name.name())) } } _ => None, diff --git a/compiler/qsc_eval/src/lower.rs b/compiler/qsc_eval/src/lower.rs index fd36d962c8..3ea2518397 100644 --- a/compiler/qsc_eval/src/lower.rs +++ b/compiler/qsc_eval/src/lower.rs @@ -150,7 +150,7 @@ impl Lowerer { fn lower_item(&mut self, item: &hir::Item) -> fir::Item { let kind = match &item.kind { hir::ItemKind::Namespace(name, items) => { - let name = self.lower_ident(name); + let name = self.lower_vec_ident(name); let items = items.iter().map(|i| lower_local_item_id(*i)).collect(); fir::ItemKind::Namespace(name, items) } @@ -761,6 +761,13 @@ impl Lowerer { name_span: field.name_span, } } + + fn lower_vec_ident(&mut self, name: &hir::VecIdent) -> fir::VecIdent { + name.iter() + .cloned() + .map(|ident| self.lower_ident(&ident)) + .collect() + } } fn lower_generics(generics: &[qsc_hir::ty::GenericParam]) -> Vec { diff --git a/compiler/qsc_fir/src/fir.rs b/compiler/qsc_fir/src/fir.rs index a0ddfef54c..7e56020876 100644 --- a/compiler/qsc_fir/src/fir.rs +++ b/compiler/qsc_fir/src/fir.rs @@ -698,7 +698,7 @@ pub enum ItemKind { /// A `function` or `operation` declaration. Callable(CallableDecl), /// A `namespace` declaration. - Namespace(Ident, Vec), + Namespace(VecIdent, Vec), /// A `newtype` declaration. Ty(Ident, Udt), } @@ -1423,6 +1423,89 @@ impl Display for PatKind { } } +/// A [`VecIdent`] represents a sequence of idents. It provides a helpful abstraction +/// that is more powerful than a simple `Vec`, and is primarily used to represent +/// dot-separated paths. +#[derive(Clone, Debug, Eq, Hash, PartialEq, Default)] +pub struct VecIdent(pub Vec); + +impl<'a> IntoIterator for &'a VecIdent { + type IntoIter = std::slice::Iter<'a, Ident>; + type Item = &'a Ident; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} +impl From for Vec> { + fn from(v: VecIdent) -> Self { + v.0.iter().map(|i| i.name.clone()).collect() + } +} + +impl From<&VecIdent> for Vec> { + fn from(v: &VecIdent) -> Self { + v.0.iter().map(|i| i.name.clone()).collect() + } +} + +impl From> for VecIdent { + fn from(v: Vec) -> Self { + VecIdent(v) + } +} + +impl From for Vec { + fn from(v: VecIdent) -> Self { + v.0 + } +} + +impl FromIterator for VecIdent { + fn from_iter>(iter: T) -> Self { + VecIdent(iter.into_iter().collect()) + } +} +impl Display for VecIdent { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut buf = Vec::with_capacity(self.0.len()); + + for ident in &self.0 { + buf.push(format!("{ident}")); + } + if buf.len() > 1 { + // use square brackets only if there are more than one ident + write!(f, "[{}]", buf.join(", ")) + } else { + write!(f, "{}", buf[0]) + } + } +} +impl VecIdent { + /// The stringified dot-separated path of the idents in this [`VecIdent`] + /// E.g. `a.b.c` + #[must_use] + pub fn name(&self) -> String { + self.0 + .iter() + .map(|i| i.name.to_string()) + .collect::>() + .join(".") + } + /// constructs an iter over the [Ident]s that this contains. + pub fn iter(&self) -> std::slice::Iter<'_, Ident> { + self.0.iter() + } + + /// the conjoined span of all idents in the `VecIdent` + #[must_use] + pub fn span(&self) -> Span { + Span { + lo: self.0.first().map(|i| i.span.lo).unwrap_or_default(), + hi: self.0.last().map(|i| i.span.hi).unwrap_or_default(), + } + } +} + /// An identifier. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Ident { diff --git a/compiler/qsc_fir/src/global.rs b/compiler/qsc_fir/src/global.rs index 4591597c3c..36b4b3c914 100644 --- a/compiler/qsc_fir/src/global.rs +++ b/compiler/qsc_fir/src/global.rs @@ -5,12 +5,15 @@ use crate::{ fir::{Item, ItemId, ItemKind, Package, PackageId, Visibility}, ty::Scheme, }; -use qsc_data_structures::index_map; +use qsc_data_structures::{ + index_map, + namespaces::{NamespaceId, NamespaceTreeRoot}, +}; use rustc_hash::FxHashMap; use std::rc::Rc; pub struct Global { - pub namespace: Rc, + pub namespace: Vec>, pub name: Rc, pub visibility: Visibility, pub kind: Kind, @@ -33,19 +36,25 @@ pub struct Term { #[derive(Default)] pub struct Table { - tys: FxHashMap, FxHashMap, Ty>>, - terms: FxHashMap, FxHashMap, Term>>, + tys: FxHashMap, Ty>>, + terms: FxHashMap, Term>>, + namespaces: NamespaceTreeRoot, } impl Table { #[must_use] - pub fn resolve_ty(&self, namespace: &str, name: &str) -> Option<&Ty> { - self.tys.get(namespace).and_then(|terms| terms.get(name)) + pub fn resolve_ty(&self, namespace: NamespaceId, name: &str) -> Option<&Ty> { + self.tys.get(&namespace).and_then(|terms| terms.get(name)) } #[must_use] - pub fn resolve_term(&self, namespace: &str, name: &str) -> Option<&Term> { - self.terms.get(namespace).and_then(|terms| terms.get(name)) + pub fn resolve_term(&self, namespace: NamespaceId, name: &str) -> Option<&Term> { + self.terms.get(&namespace).and_then(|terms| terms.get(name)) + } + + pub fn find_namespace(&self, vec: impl Into>>) -> Option { + // find a namespace if it exists and return its id + self.namespaces.find_namespace(vec) } } @@ -53,16 +62,16 @@ impl FromIterator for Table { fn from_iter>(iter: T) -> Self { let mut tys: FxHashMap<_, FxHashMap<_, _>> = FxHashMap::default(); let mut terms: FxHashMap<_, FxHashMap<_, _>> = FxHashMap::default(); + let mut namespaces = NamespaceTreeRoot::default(); for global in iter { + let namespace = namespaces.insert_or_find_namespace(global.namespace.into_iter()); match global.kind { Kind::Ty(ty) => { - tys.entry(global.namespace) - .or_default() - .insert(global.name, ty); + tys.entry(namespace).or_default().insert(global.name, ty); } Kind::Term(term) => { terms - .entry(global.namespace) + .entry(namespace) .or_default() .insert(global.name, term); } @@ -70,7 +79,11 @@ impl FromIterator for Table { } } - Self { tys, terms } + Self { + tys, + terms, + namespaces, + } } } @@ -98,7 +111,7 @@ impl PackageIter<'_> { match (&item.kind, &parent) { (ItemKind::Callable(decl), Some(ItemKind::Namespace(namespace, _))) => Some(Global { - namespace: Rc::clone(&namespace.name), + namespace: namespace.into(), name: Rc::clone(&decl.name.name), visibility: item.visibility, kind: Kind::Term(Term { @@ -109,7 +122,7 @@ impl PackageIter<'_> { }), (ItemKind::Ty(name, def), Some(ItemKind::Namespace(namespace, _))) => { self.next = Some(Global { - namespace: Rc::clone(&namespace.name), + namespace: namespace.into(), name: Rc::clone(&name.name), visibility: item.visibility, kind: Kind::Term(Term { @@ -119,15 +132,15 @@ impl PackageIter<'_> { }); Some(Global { - namespace: Rc::clone(&namespace.name), + namespace: namespace.into(), name: Rc::clone(&name.name), visibility: item.visibility, kind: Kind::Ty(Ty { id }), }) } (ItemKind::Namespace(ident, _), None) => Some(Global { - namespace: "".into(), - name: Rc::clone(&ident.name), + namespace: ident.into(), + name: "".into(), visibility: Visibility::Public, kind: Kind::Namespace, }), diff --git a/compiler/qsc_fir/src/mut_visit.rs b/compiler/qsc_fir/src/mut_visit.rs index 1b144a9f6d..97532eb9ec 100644 --- a/compiler/qsc_fir/src/mut_visit.rs +++ b/compiler/qsc_fir/src/mut_visit.rs @@ -4,6 +4,7 @@ use crate::fir::{ Block, BlockId, CallableDecl, CallableImpl, Expr, ExprId, ExprKind, Ident, Item, ItemKind, Package, Pat, PatId, PatKind, SpecDecl, SpecImpl, Stmt, StmtId, StmtKind, StringComponent, + VecIdent, }; pub trait MutVisitor<'a>: Sized { @@ -49,6 +50,8 @@ pub trait MutVisitor<'a>: Sized { fn visit_ident(&mut self, _: &'a mut Ident) {} + fn visit_vec_ident(&mut self, _: &'a mut VecIdent) {} + fn get_block(&mut self, id: BlockId) -> &'a mut Block; fn get_expr(&mut self, id: ExprId) -> &'a mut Expr; fn get_pat(&mut self, id: PatId) -> &'a mut Pat; @@ -63,7 +66,8 @@ pub fn walk_package<'a>(vis: &mut impl MutVisitor<'a>, package: &'a mut Package) pub fn walk_item<'a>(vis: &mut impl MutVisitor<'a>, item: &'a mut Item) { match &mut item.kind { ItemKind::Callable(decl) => vis.visit_callable_decl(decl), - ItemKind::Namespace(name, _) | ItemKind::Ty(name, _) => vis.visit_ident(name), + ItemKind::Namespace(name, _) => vis.visit_vec_ident(name), + ItemKind::Ty(name, _) => vis.visit_ident(name), }; } diff --git a/compiler/qsc_fir/src/visit.rs b/compiler/qsc_fir/src/visit.rs index 81cb167c47..81de9c0ffd 100644 --- a/compiler/qsc_fir/src/visit.rs +++ b/compiler/qsc_fir/src/visit.rs @@ -4,6 +4,7 @@ use crate::fir::{ Block, BlockId, CallableDecl, CallableImpl, Expr, ExprId, ExprKind, Ident, Item, ItemKind, Package, Pat, PatId, PatKind, SpecDecl, SpecImpl, Stmt, StmtId, StmtKind, StringComponent, + VecIdent, }; pub trait Visitor<'a>: Sized { @@ -49,6 +50,8 @@ pub trait Visitor<'a>: Sized { fn visit_ident(&mut self, _: &'a Ident) {} + fn visit_vec_ident(&mut self, _: &'a VecIdent) {} + fn get_block(&self, id: BlockId) -> &'a Block; fn get_expr(&self, id: ExprId) -> &'a Expr; fn get_pat(&self, id: PatId) -> &'a Pat; @@ -63,7 +66,8 @@ pub fn walk_package<'a>(vis: &mut impl Visitor<'a>, package: &'a Package) { pub fn walk_item<'a>(vis: &mut impl Visitor<'a>, item: &'a Item) { match &item.kind { ItemKind::Callable(decl) => vis.visit_callable_decl(decl), - ItemKind::Namespace(name, _) | ItemKind::Ty(name, _) => vis.visit_ident(name), + ItemKind::Namespace(name, _) => vis.visit_vec_ident(name), + ItemKind::Ty(name, _) => vis.visit_ident(name), }; } diff --git a/compiler/qsc_frontend/src/compile.rs b/compiler/qsc_frontend/src/compile.rs index 65a6d432d9..70b930c928 100644 --- a/compiler/qsc_frontend/src/compile.rs +++ b/compiler/qsc_frontend/src/compile.rs @@ -25,11 +25,12 @@ use qsc_ast::{ use qsc_data_structures::{ index_map::{self, IndexMap}, language_features::LanguageFeatures, + namespaces::NamespaceTreeRoot, span::Span, }; use qsc_hir::{ assigner::Assigner as HirAssigner, - global, + global::{self}, hir::{self, PackageId}, validate::Validator as HirValidator, visit::Visitor as _, @@ -106,6 +107,7 @@ pub struct AstPackage { pub tys: Table, pub names: Names, pub locals: Locals, + pub namespaces: NamespaceTreeRoot, } #[derive(Debug, Default)] @@ -354,7 +356,7 @@ pub fn compile( ast_assigner.visit_package(&mut ast_package); AstValidator::default().visit_package(&ast_package); let mut hir_assigner = HirAssigner::new(); - let (names, locals, name_errors) = resolve_all( + let (names, locals, name_errors, namespaces) = resolve_all( store, dependencies, &mut hir_assigner, @@ -385,6 +387,7 @@ pub fn compile( tys, names, locals, + namespaces, }, assigner: hir_assigner, sources, @@ -489,7 +492,7 @@ fn resolve_all( assigner: &mut HirAssigner, package: &ast::Package, mut dropped_names: Vec, -) -> (Names, Locals, Vec) { +) -> (Names, Locals, Vec, NamespaceTreeRoot) { let mut globals = resolve::GlobalTable::new(); if let Some(unit) = store.get(PackageId::CORE) { globals.add_external_package(PackageId::CORE, &unit.package); @@ -507,9 +510,9 @@ fn resolve_all( let mut errors = globals.add_local_package(assigner, package); let mut resolver = Resolver::new(globals, dropped_names); resolver.with(assigner).visit_package(package); - let (names, locals, mut resolver_errors) = resolver.into_result(); + let (names, locals, mut resolver_errors, namespaces) = resolver.into_result(); errors.append(&mut resolver_errors); - (names, locals, errors) + (names, locals, errors, namespaces) } fn typeck_all( diff --git a/compiler/qsc_frontend/src/compile/preprocess.rs b/compiler/qsc_frontend/src/compile/preprocess.rs index ae84c4b201..39bbb26ee7 100644 --- a/compiler/qsc_frontend/src/compile/preprocess.rs +++ b/compiler/qsc_frontend/src/compile/preprocess.rs @@ -14,7 +14,7 @@ use super::{ConfigAttr, RuntimeCapabilityFlags}; #[derive(PartialEq, Hash, Clone, Debug)] pub struct TrackedName { pub name: Rc, - pub namespace: Rc, + pub namespace: Vec>, } pub(crate) struct Conditional { @@ -51,12 +51,22 @@ impl MutVisitor for Conditional { ItemKind::Callable(callable) => { self.included_names.push(TrackedName { name: callable.name.name.clone(), - namespace: namespace.name.name.clone(), + namespace: namespace + .name + .clone() + .iter() + .map(|x| Rc::from(x.to_string())) + .collect(), }); } ItemKind::Ty(ident, _) => self.included_names.push(TrackedName { name: ident.name.clone(), - namespace: namespace.name.name.clone(), + namespace: namespace + .name + .clone() + .iter() + .map(|x| Rc::from(x.to_string())) + .collect(), }), _ => {} } @@ -66,12 +76,17 @@ impl MutVisitor for Conditional { ItemKind::Callable(callable) => { self.dropped_names.push(TrackedName { name: callable.name.name.clone(), - namespace: namespace.name.name.clone(), + namespace: (&namespace.name).into(), }); } ItemKind::Ty(ident, _) => self.dropped_names.push(TrackedName { name: ident.name.clone(), - namespace: namespace.name.name.clone(), + namespace: namespace + .name + .clone() + .iter() + .map(|x| Rc::from(x.to_string())) + .collect(), }), _ => {} } @@ -89,12 +104,12 @@ impl MutVisitor for Conditional { ItemKind::Callable(callable) => { self.included_names.push(TrackedName { name: callable.name.name.clone(), - namespace: Rc::from(""), + namespace: Vec::default(), }); } ItemKind::Ty(ident, _) => self.included_names.push(TrackedName { name: ident.name.clone(), - namespace: Rc::from(""), + namespace: Vec::default(), }), _ => {} } @@ -103,12 +118,12 @@ impl MutVisitor for Conditional { ItemKind::Callable(callable) => { self.dropped_names.push(TrackedName { name: callable.name.name.clone(), - namespace: Rc::from(""), + namespace: Vec::default(), }); } ItemKind::Ty(ident, _) => self.dropped_names.push(TrackedName { name: ident.name.clone(), - namespace: Rc::from(""), + namespace: Vec::default(), }), _ => {} } diff --git a/compiler/qsc_frontend/src/compile/tests.rs b/compiler/qsc_frontend/src/compile/tests.rs index b5f86a937d..65ef000ae7 100644 --- a/compiler/qsc_frontend/src/compile/tests.rs +++ b/compiler/qsc_frontend/src/compile/tests.rs @@ -3,6 +3,8 @@ #![allow(clippy::needless_raw_string_hashes)] +use std::rc::Rc; + use crate::compile::RuntimeCapabilityFlags; use super::{compile, CompileUnit, Error, PackageStore, SourceMap}; @@ -339,9 +341,13 @@ fn insert_core_call() { impl MutVisitor for Inserter<'_> { fn visit_block(&mut self, block: &mut Block) { + let ns = self + .core + .find_namespace(vec![Rc::from("QIR"), Rc::from("Runtime")]) + .expect("QIR runtime should be inserted at instantiation of core Table"); let allocate = self .core - .resolve_term("QIR.Runtime", "__quantum__rt__qubit_allocate") + .resolve_term(ns, "__quantum__rt__qubit_allocate") .expect("qubit allocation should be in core"); let allocate_ty = allocate .scheme @@ -1182,7 +1188,7 @@ fn reject_use_qubit_block_syntax_if_preview_feature_is_on() { // we have the v2 preview syntax feature enabled X(q); }; - + } } "} @@ -1255,3 +1261,35 @@ fn accept_use_qubit_block_syntax_if_preview_feature_is_off() { ); assert!(unit.errors.is_empty(), "{:#?}", unit.errors); } + +#[test] +fn hierarchical_namespace_basic() { + let lib_sources = SourceMap::new( + [( + "lib".into(), + indoc! {" + namespace Foo.Bar { + operation Baz() : Unit {} + } + namespace Main { + open Foo; + operation Main() : Unit { + Bar.Baz(); + } + } + "} + .into(), + )], + None, + ); + + let store = PackageStore::new(super::core()); + let lib = compile( + &store, + &[], + lib_sources, + RuntimeCapabilityFlags::all(), + LanguageFeatures::default(), + ); + assert!(lib.errors.is_empty(), "{:#?}", lib.errors); +} diff --git a/compiler/qsc_frontend/src/incremental.rs b/compiler/qsc_frontend/src/incremental.rs index 3d435dc7dc..7f83f6d99d 100644 --- a/compiler/qsc_frontend/src/incremental.rs +++ b/compiler/qsc_frontend/src/incremental.rs @@ -134,6 +134,7 @@ impl Compiler { names: self.resolver.names().clone(), locals: self.resolver.locals().clone(), tys: self.checker.table().clone(), + namespaces: self.resolver.namespaces().clone(), }, hir, }) @@ -173,6 +174,7 @@ impl Compiler { names: self.resolver.names().clone(), locals: self.resolver.locals().clone(), tys: self.checker.table().clone(), + namespaces: self.resolver.namespaces().clone(), }, hir, }) diff --git a/compiler/qsc_frontend/src/incremental/tests.rs b/compiler/qsc_frontend/src/incremental/tests.rs index d9ea8a47ff..e7277ca2c6 100644 --- a/compiler/qsc_frontend/src/incremental/tests.rs +++ b/compiler/qsc_frontend/src/incremental/tests.rs @@ -43,7 +43,7 @@ fn one_callable() { output: Type 7 [35-39]: Path: Path 8 [35-39] (Ident 9 [35-39] "Unit") body: Block: Block 10 [40-42]: names: - node_id:2,node_id:5,node_id:8, + node_id:1,node_id:5,node_id:8, terms: node_id:6,node_id:10, locals: @@ -67,9 +67,25 @@ fn one_callable() { hi: 44, }, kind: Namespace( - "Foo", + NamespaceId( + 9, + ), ), - opens: {}, + opens: { + [ + "Foo", + ]: [ + Open { + namespace: NamespaceId( + 9, + ), + span: Span { + lo: 10, + hi: 13, + }, + }, + ], + }, tys: {}, terms: {}, vars: {}, diff --git a/compiler/qsc_frontend/src/lower.rs b/compiler/qsc_frontend/src/lower.rs index 8ba1b6f609..a65f27f8c9 100644 --- a/compiler/qsc_frontend/src/lower.rs +++ b/compiler/qsc_frontend/src/lower.rs @@ -125,7 +125,7 @@ impl With<'_> { pub(super) fn lower_namespace(&mut self, namespace: &ast::Namespace) { let Some(&resolve::Res::Item(hir::ItemId { item: id, .. }, _)) = - self.names.get(namespace.name.id) + self.names.get(namespace.id) else { panic!("namespace should have item ID"); }; @@ -137,7 +137,7 @@ impl With<'_> { .filter_map(|i| self.lower_item(ItemScope::Global, i)) .collect(); - let name = self.lower_ident(&namespace.name); + let name = self.lower_vec_ident(&namespace.name); self.lowerer.items.push(hir::Item { id, span: namespace.span, @@ -737,6 +737,10 @@ impl With<'_> { new_id }) } + + fn lower_vec_ident(&mut self, name: &ast::VecIdent) -> hir::VecIdent { + name.iter().cloned().map(|i| self.lower_ident(&i)).collect() + } } fn lower_visibility(visibility: &ast::Visibility) -> hir::Visibility { diff --git a/compiler/qsc_frontend/src/resolve.rs b/compiler/qsc_frontend/src/resolve.rs index 98cd5249df..652d9c38e4 100644 --- a/compiler/qsc_frontend/src/resolve.rs +++ b/compiler/qsc_frontend/src/resolve.rs @@ -6,10 +6,17 @@ mod tests; use miette::Diagnostic; use qsc_ast::{ - ast::{self, CallableBody, CallableDecl, Ident, NodeId, SpecBody, SpecGen, TopLevelNode}, + ast::{ + self, CallableBody, CallableDecl, Ident, NodeId, SpecBody, SpecGen, TopLevelNode, VecIdent, + }, visit::{self as ast_visit, walk_attr, Visitor as AstVisitor}, }; -use qsc_data_structures::{index_map::IndexMap, span::Span}; + +use qsc_data_structures::{ + index_map::IndexMap, + namespaces::{NamespaceId, NamespaceTreeRoot, PRELUDE}, + span::Span, +}; use qsc_hir::{ assigner::Assigner, global, @@ -17,18 +24,12 @@ use qsc_hir::{ ty::{ParamId, Prim}, }; use rustc_hash::{FxHashMap, FxHashSet}; +use std::cmp::Ordering; use std::{collections::hash_map::Entry, rc::Rc, str::FromStr, vec}; use thiserror::Error; use crate::compile::preprocess::TrackedName; -const PRELUDE: &[&str] = &[ - "Microsoft.Quantum.Canon", - "Microsoft.Quantum.Core", - "Microsoft.Quantum.Intrinsic", - "Microsoft.Quantum.Measurement", -]; - // All AST Path nodes get mapped // All AST Ident nodes get mapped, except those under AST Path nodes pub(super) type Names = IndexMap; @@ -116,7 +117,7 @@ pub struct Scope { span: Span, kind: ScopeKind, /// Open statements. The key is the namespace name or alias. - opens: FxHashMap, Vec>, + opens: FxHashMap>, Vec>, /// Local newtype declarations. tys: FxHashMap, ItemId>, /// Local callable and newtype declarations. @@ -238,25 +239,35 @@ pub enum LocalKind { #[derive(Debug, Clone, Default)] pub struct GlobalScope { - tys: FxHashMap, FxHashMap, Res>>, - terms: FxHashMap, FxHashMap, Res>>, - namespaces: FxHashSet>, + tys: FxHashMap, Res>>, + terms: FxHashMap, Res>>, + namespaces: NamespaceTreeRoot, intrinsics: FxHashSet>, } impl GlobalScope { - fn get(&self, kind: NameKind, namespace: &str, name: &str) -> Option<&Res> { - let namespaces = match kind { + fn find_namespace(&self, ns: impl Into>>) -> Option { + self.namespaces.find_namespace(ns) + } + + fn get(&self, kind: NameKind, namespace: NamespaceId, name: &str) -> Option<&Res> { + let items = match kind { NameKind::Ty => &self.tys, NameKind::Term => &self.terms, }; - namespaces.get(namespace).and_then(|items| items.get(name)) + items.get(&namespace).and_then(|items| items.get(name)) + } + + /// Creates a namespace in the namespace mapping. Note that namespaces are tracked separately from their + /// item contents. This returns a [`NamespaceId`] which you can use to add more tys and terms to the scope. + fn insert_or_find_namespace(&mut self, name: impl Into>>) -> NamespaceId { + self.namespaces.insert_or_find_namespace(name.into()) } } #[derive(Debug, Clone, Eq, PartialEq)] enum ScopeKind { - Namespace(Rc), + Namespace(NamespaceId), Callable, Block, } @@ -269,10 +280,32 @@ enum NameKind { #[derive(Debug, Clone)] struct Open { - namespace: Rc, + namespace: NamespaceId, span: Span, } +impl Eq for Open {} + +impl PartialEq for Open { + fn eq(&self, other: &Self) -> bool { + self.namespace == other.namespace + } +} + +impl PartialOrd for Open { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Open { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let a: usize = self.namespace.into(); + let b: usize = other.namespace.into(); + a.cmp(&b) + } +} + pub(super) struct Resolver { names: Names, dropped_names: Vec, @@ -338,8 +371,13 @@ impl Resolver { } } - pub(super) fn into_result(self) -> (Names, Locals, Vec) { - (self.names, self.locals, self.errors) + pub(super) fn into_result(self) -> (Names, Locals, Vec, NamespaceTreeRoot) { + ( + self.names, + self.locals, + self.errors, + self.globals.namespaces, + ) } pub(super) fn extend_dropped_names(&mut self, dropped_names: Vec) { @@ -349,7 +387,7 @@ impl Resolver { pub(super) fn bind_fragments(&mut self, ast: &ast::Package, assigner: &mut Assigner) { for node in &mut ast.nodes.iter() { match node { - ast::TopLevelNode::Namespace(namespace) => { + TopLevelNode::Namespace(namespace) => { bind_global_items( &mut self.names, &mut self.globals, @@ -358,7 +396,7 @@ impl Resolver { &mut self.errors, ); } - ast::TopLevelNode::Stmt(stmt) => { + TopLevelNode::Stmt(stmt) => { if let ast::StmtKind::Item(item) = stmt.kind.as_ref() { self.bind_local_item(assigner, item); } @@ -413,7 +451,16 @@ impl Resolver { { self.errors.push(Error::NotAvailable( name, - format!("{}.{}", dropped_name.namespace, dropped_name.name), + format!( + "{}.{}", + dropped_name + .namespace + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join("."), + dropped_name.name + ), span, )); } else { @@ -463,20 +510,32 @@ impl Resolver { } } - fn bind_open(&mut self, name: &ast::Ident, alias: &Option>) { - let alias = alias.as_ref().map_or("".into(), |a| Rc::clone(&a.name)); - if self.globals.namespaces.contains(&name.name) { + fn bind_open(&mut self, name: &VecIdent, alias: &Option>) { + let Some(id) = self.globals.find_namespace(name) else { + self.errors.push(Error::NotFound( + name.iter() + .map(|x| x.name.to_string()) + .collect::>() + .join("."), + name.span(), + )); + return; + }; + let alias = alias + .as_ref() + .map_or(name.into(), |a| vec![Rc::clone(&a.name)]); + if self.globals.namespaces.find_namespace(name).is_some() { self.current_scope_mut() .opens .entry(alias) .or_default() .push(Open { - namespace: Rc::clone(&name.name), - span: name.span, + namespace: id, + span: name.span(), }); } else { self.errors - .push(Error::NotFound(name.name.to_string(), name.span)); + .push(Error::NotFound(name.to_string(), name.span())); } } @@ -542,6 +601,10 @@ impl Resolver { self.locals.get_scope_mut(scope_id) } + + pub(crate) fn namespaces(&self) -> NamespaceTreeRoot { + self.globals.namespaces.clone() + } } /// Constructed from a [Resolver] and an [Assigner], this structure implements `Visitor` @@ -593,14 +656,23 @@ impl With<'_> { impl AstVisitor<'_> for With<'_> { fn visit_namespace(&mut self, namespace: &ast::Namespace) { - let kind = ScopeKind::Namespace(Rc::clone(&namespace.name.name)); + let ns = self + .resolver + .globals + .find_namespace(Into::>::into(namespace.name.clone())) + .expect("namespace should exist by this point"); + + let kind = ScopeKind::Namespace(ns); self.with_scope(namespace.span, kind, |visitor| { + // the below line ensures that this namespace opens itself, in case + // we are re-opening a namespace. This is important, as without this, + // a re-opened namespace would only have knowledge of its scopes. + visitor.resolver.bind_open(&namespace.name, &None); for item in &*namespace.items { if let ast::ItemKind::Open(name, alias) = &*item.kind { visitor.resolver.bind_open(name, alias); } } - ast_visit::walk_namespace(visitor, namespace); }); } @@ -612,7 +684,7 @@ impl AstVisitor<'_> for With<'_> { } } - fn visit_callable_decl(&mut self, decl: &ast::CallableDecl) { + fn visit_callable_decl(&mut self, decl: &CallableDecl) { fn collect_param_names(pat: &ast::Pat, names: &mut FxHashSet>) { match &*pat.kind { ast::PatKind::Bind(name, _) => { @@ -639,7 +711,7 @@ impl AstVisitor<'_> for With<'_> { } fn visit_spec_decl(&mut self, decl: &ast::SpecDecl) { - if let ast::SpecBody::Impl(input, block) = &decl.body { + if let SpecBody::Impl(input, block) = &decl.body { self.with_spec_pat(block.span, ScopeKind::Block, input, |visitor| { visitor.visit_block(block); }); @@ -756,15 +828,23 @@ impl GlobalTable { for (name, res) in builtins { core.insert(name, res); } - let mut tys: FxHashMap, FxHashMap, Res>> = FxHashMap::default(); - tys.insert("Microsoft.Quantum.Core".into(), core); + + let mut scope = GlobalScope::default(); + let ns = scope.insert_or_find_namespace(vec![ + Rc::from("Microsoft"), + Rc::from("Quantum"), + Rc::from("Core"), + ]); + + let mut tys = FxHashMap::default(); + tys.insert(ns, core); Self { names: IndexMap::new(), scope: GlobalScope { tys, terms: FxHashMap::default(), - namespaces: FxHashSet::default(), + namespaces: NamespaceTreeRoot::default(), intrinsics: FxHashSet::default(), }, } @@ -800,11 +880,15 @@ impl GlobalTable { global.visibility == hir::Visibility::Public || matches!(&global.kind, global::Kind::Term(t) if t.intrinsic) }) { + let namespace = self + .scope + .insert_or_find_namespace(global.namespace.clone()); + match (global.kind, global.visibility) { (global::Kind::Ty(ty), hir::Visibility::Public) => { self.scope .tys - .entry(global.namespace) + .entry(namespace) .or_default() .insert(global.name, Res::Item(ty.id, global.status)); } @@ -812,7 +896,7 @@ impl GlobalTable { if visibility == hir::Visibility::Public { self.scope .terms - .entry(global.namespace) + .entry(namespace) .or_default() .insert(global.name.clone(), Res::Item(term.id, global.status)); } @@ -821,7 +905,7 @@ impl GlobalTable { } } (global::Kind::Namespace, hir::Visibility::Public) => { - self.scope.namespaces.insert(global.name); + self.scope.insert_or_find_namespace(global.namespace); } (_, hir::Visibility::Internal) => {} } @@ -829,6 +913,7 @@ impl GlobalTable { } } +/// Given some namespace `namespace`, add all the globals declared within it to the global scope. fn bind_global_items( names: &mut IndexMap, scope: &mut GlobalScope, @@ -837,16 +922,17 @@ fn bind_global_items( errors: &mut Vec, ) { names.insert( - namespace.name.id, + namespace.id, Res::Item(intrapackage(assigner.next_item()), ItemStatus::Available), ); - scope.namespaces.insert(Rc::clone(&namespace.name.name)); + + let namespace_id = scope.insert_or_find_namespace(&namespace.name); for item in &*namespace.items { match bind_global_item( names, scope, - &namespace.name.name, + namespace_id, || intrapackage(assigner.next_item()), item, ) { @@ -901,7 +987,7 @@ fn ast_attrs_as_hir_attrs(attrs: &[Box]) -> Vec { fn bind_global_item( names: &mut Names, scope: &mut GlobalScope, - namespace: &Rc, + namespace: NamespaceId, next_id: impl FnOnce() -> ItemId, item: &ast::Item, ) -> Result<(), Vec> { @@ -914,15 +1000,18 @@ fn bind_global_item( let mut errors = Vec::new(); match scope .terms - .entry(Rc::clone(namespace)) + .entry(namespace) .or_default() .entry(Rc::clone(&decl.name.name)) { - Entry::Occupied(_) => errors.push(Error::Duplicate( - decl.name.name.to_string(), - namespace.to_string(), - decl.name.span, - )), + Entry::Occupied(_) => { + let namespace_name = scope.namespaces.find_id(&namespace).0.join("."); + errors.push(Error::Duplicate( + decl.name.name.to_string(), + namespace_name.to_string(), + decl.name.span, + )); + } Entry::Vacant(entry) => { entry.insert(res); } @@ -949,20 +1038,23 @@ fn bind_global_item( match ( scope .terms - .entry(Rc::clone(namespace)) + .entry(namespace) .or_default() .entry(Rc::clone(&name.name)), scope .tys - .entry(Rc::clone(namespace)) + .entry(namespace) .or_default() .entry(Rc::clone(&name.name)), ) { - (Entry::Occupied(_), _) | (_, Entry::Occupied(_)) => Err(vec![Error::Duplicate( - name.name.to_string(), - namespace.to_string(), - name.span, - )]), + (Entry::Occupied(_), _) | (_, Entry::Occupied(_)) => { + let namespace_name = scope.namespaces.find_id(&namespace).0.join("."); + Err(vec![Error::Duplicate( + name.name.to_string(), + namespace_name, + name.span, + )]) + } (Entry::Vacant(term_entry), Entry::Vacant(ty_entry)) => { term_entry.insert(res); ty_entry.insert(res); @@ -974,7 +1066,7 @@ fn bind_global_item( } } -fn decl_is_intrinsic(decl: &ast::CallableDecl) -> bool { +fn decl_is_intrinsic(decl: &CallableDecl) -> bool { if let CallableBody::Specs(specs) = decl.body.as_ref() { specs .iter() @@ -984,49 +1076,59 @@ fn decl_is_intrinsic(decl: &ast::CallableDecl) -> bool { } } +/// Resolves a given symbol and namespace name, according to the Q# shadowing rules. +/// Shadowing rules are as follows: +/// - Local variables shadow everything. They are the first priority. +/// - Next, we check open statements for an explicit open. +/// - Then, we check the prelude. +/// - Lastly, we check the global namespace. +/// In the example `Foo.Bar.Baz()` -- the `provided_namespace_name` would be +///`Foo.Bar` and the `provided_symbol_name` would be `Baz`. +/// +/// In the example `Foo()` -- the `provided_namespace_name` would be `None` and the +/// `provided_symbol_name` would be `Foo`. +/// returns the resolution if successful, or an error if not. fn resolve<'a>( kind: NameKind, globals: &GlobalScope, scopes: impl Iterator, - name: &Ident, - namespace: &Option>, + provided_symbol_name: &Ident, + provided_namespace_name: &Option, ) -> Result { let scopes = scopes.collect::>(); - let mut candidates = FxHashMap::default(); - let mut vars = true; - let name_str = &(*name.name); - let namespace = namespace.as_ref().map_or("", |i| &i.name); - for scope in scopes { - if namespace.is_empty() { - if let Some(res) = resolve_scope_locals(kind, globals, scope, vars, name_str) { - // Local declarations shadow everything. - return Ok(res); - } - } - if let Some(namespaces) = scope.opens.get(namespace) { - candidates = resolve_explicit_opens(kind, globals, namespaces, name_str); - if !candidates.is_empty() { - // Explicit opens shadow prelude and unopened globals. - break; - } - } - - if scope.kind == ScopeKind::Callable { - // Since local callables are not closures, hide local variables in parent scopes. - vars = false; - } + if let Some(value) = check_all_scopes( + kind, + globals, + provided_symbol_name, + provided_namespace_name, + &scopes, + ) { + return value; } - if candidates.is_empty() && namespace.is_empty() { - // Prelude shadows unopened globals. - let candidates = resolve_implicit_opens(kind, globals, PRELUDE, name_str); - if candidates.len() > 1 { - let mut candidates: Vec<_> = candidates.into_iter().collect(); - candidates.sort_by_key(|x| x.1); + // check the prelude + if provided_namespace_name.is_none() { + let prelude_candidates = find_symbol_in_namespaces( + kind, + globals, + provided_namespace_name, + provided_symbol_name, + prelude_namespaces(globals).into_iter().map(|(a, b)| (b, a)), + // there are no aliases in the prelude + &FxHashMap::default(), + ); + + if prelude_candidates.len() > 1 { + // If there are multiple candidates, sort them by namespace and return an error. + let candidates: Vec<_> = prelude_candidates.into_iter().collect(); let mut candidates = candidates .into_iter() - .map(|candidate| candidate.1.to_string()); + .map(|(_candidate, ns_name)| ns_name) + .collect::>(); + candidates.sort(); + + let mut candidates = candidates.into_iter(); let candidate_a = candidates .next() .expect("infallible as per length check above"); @@ -1034,56 +1136,266 @@ fn resolve<'a>( .next() .expect("infallible as per length check above"); return Err(Error::AmbiguousPrelude { - span: name.span, - name: name.name.to_string(), + span: provided_symbol_name.span, + name: provided_symbol_name.name.to_string(), candidate_a, candidate_b, }); } - if let Some((res, _)) = single(candidates) { + // if there is a candidate, return it + if let Some((res, _)) = single(prelude_candidates) { return Ok(res); } } - if candidates.is_empty() { - if let Some(&res) = globals.get(kind, namespace, name_str) { - // An unopened global is the last resort. - return Ok(res); + // lastly, check unopened globals + let global_candidates = find_symbol_in_namespaces( + kind, + globals, + provided_namespace_name, + provided_symbol_name, + vec![(globals.namespaces.root_id(), ())].into_iter(), + // there are no aliases in globals + &FxHashMap::default(), + ); + + // we don't have to throw an error if there are extra candidates here, as we are only looking at the root, + // and that's only one namespace. individual namespaces cannot have duplicate declarations. + if let Some(res) = single(global_candidates.into_keys()) { + return Ok(res); + } + + Err(Error::NotFound( + provided_symbol_name.name.to_string(), + provided_symbol_name.span, + )) +} +/// Checks all given scopes, in the correct order, for a resolution. +/// Calls `check_scoped_resolutions` on each scope, and tracks if we should allow local variables in closures in parent scopes +/// using the `vars` parameter. +fn check_all_scopes( + kind: NameKind, + globals: &GlobalScope, + provided_symbol_name: &Ident, + provided_namespace_name: &Option, + scopes: &Vec<&Scope>, +) -> Option> { + let mut vars = true; + + for scope in scopes { + if let Some(value) = check_scoped_resolutions( + kind, + globals, + provided_symbol_name, + provided_namespace_name, + &mut vars, + scope, + ) { + return Some(value); } } + None +} - if candidates.len() > 1 { - // If there are multiple candidates, remove unimplemented items. This allows resolution to - // succeed in cases where both an older, unimplemented API and newer, implemented API with the - // same name are both in scope without forcing the user to fully qualify the name. - let mut removals = Vec::new(); - for res in candidates.keys() { - if let Res::Item(_, ItemStatus::Unimplemented) = res { - removals.push(*res); +/// This function checks scopes for a given symbol and namespace name. +/// In a given [Scope], check: +/// 1. if any locally declared symbols match `provided_symbol_name` +/// 2. if any aliases in this scope match the provided namespace, and if they contain `provided_symbol_name` +/// 3. if any opens in this scope contain the `provided_symbol_name` +/// It follows the Q# shadowing rules: +/// - Local variables shadow everything. They are the first priority. +/// - Next, we check open statements for an explicit open. +/// - Then, we check the prelude. +/// - Lastly, we check the global namespace. +/// +/// # Parameters +/// +/// * `kind` - The [`NameKind`] of the name +/// * `globals` - The global scope to resolve the name against. +/// * `provided_symbol_name` - The symbol name to resolve. +/// * `provided_namespace_name` - The namespace of the symbol, if any. +/// * `vars` - A mutable reference to a boolean indicating whether to allow local variables in closures in parent scopes. +/// * `scope` - The scope to check for resolutions. +fn check_scoped_resolutions( + kind: NameKind, + globals: &GlobalScope, + provided_symbol_name: &Ident, + provided_namespace_name: &Option, + vars: &mut bool, + scope: &Scope, +) -> Option> { + if provided_namespace_name.is_none() { + if let Some(res) = + resolve_scope_locals(kind, globals, scope, *vars, &provided_symbol_name.name) + { + // Local declarations shadow everything. + return Some(Ok(res)); + } + } + let aliases = scope + .opens + .iter() + .map(|(alias, opens)| { + ( + alias.clone(), + opens.iter().cloned().map(|x| (x.namespace, x)).collect(), + ) + }) + .collect::>(); + + let scope_opens = scope + .opens + .iter() + .flat_map(|(_, open)| open.clone()) + .collect::>(); + let explicit_open_candidates = find_symbol_in_namespaces( + kind, + globals, + provided_namespace_name, + provided_symbol_name, + scope_opens + .into_iter() + .map(|open @ Open { namespace, .. }| (namespace, open.clone())), + &aliases, + ); + match explicit_open_candidates.len() { + 1 => { + return Some(Ok(single(explicit_open_candidates.into_keys()) + .expect("we asserted on the length, so this is infallible"))) + } + len if len > 1 => { + return Some(ambiguous_symbol_error( + globals, + provided_symbol_name, + explicit_open_candidates, + )) + } + _ => (), + } + if scope.kind == ScopeKind::Callable { + // Since local callables are not closures, hide local variables in parent scopes. + *vars = false; + } + None +} + +/// This function always returns an `Err` variant of `Result`. The error is of type `Error::Ambiguous` and contains +/// the name of the ambiguous symbol and the namespaces that contain the conflicting entities. +/// # Arguments +/// +/// * `globals` - The global scope to resolve the name against. +/// * `provided_symbol_name` - The symbol name that is ambiguous. +/// * `candidates` - A map of possible resolutions for the symbol, each associated with the `Open` +/// statement that brought it into scope. Note that only the first two opens in +/// the candidates are actually used in the error message. +fn ambiguous_symbol_error( + globals: &GlobalScope, + provided_symbol_name: &Ident, + candidates: FxHashMap, +) -> Result { + let mut opens: Vec<_> = candidates.into_values().collect(); + opens.sort_unstable_by_key(|open| open.span); + let (first_open_ns, _) = globals.namespaces.find_id(&opens[0].namespace); + let (second_open_ns, _) = globals.namespaces.find_id(&opens[1].namespace); + Err(Error::Ambiguous { + name: provided_symbol_name.name.to_string(), + first_open: first_open_ns.join("."), + second_open: second_open_ns.join("."), + name_span: provided_symbol_name.span, + first_open_span: opens[0].span, + second_open_span: opens[1].span, + }) +} + +fn find_symbol_in_namespaces( + kind: NameKind, + globals: &GlobalScope, + provided_namespace_name: &Option, + provided_symbol_name: &Ident, + namespaces_to_search: T, + aliases: &FxHashMap>, Vec<(NamespaceId, O)>>, +) -> FxHashMap +where + T: Iterator, + O: Clone + std::fmt::Debug, +{ + // check aliases to see if the provided namespace is actually an alias + if let Some(provided_namespace_name) = provided_namespace_name { + if let Some(opens) = aliases.get(&(Into::>>::into(provided_namespace_name))) { + let candidates: Vec<_> = opens + .iter() + .filter_map(|(ns_id, open)| { + globals + .get(kind, *ns_id, &provided_symbol_name.name) + .map(|res| (*res, open.clone())) + }) + .collect(); + if !candidates.is_empty() { + return candidates.into_iter().collect(); } } - for res in removals { - candidates.remove(&res); + } + + let mut candidates = FxHashMap::default(); + for (candidate_namespace_id, open) in namespaces_to_search { + // Retrieve the namespace associated with the candidate_namespace_id from the global namespaces + let candidate_namespace = globals.namespaces.find_id(&candidate_namespace_id).1; + + // Attempt to find a namespace within the candidate_namespace that matches the provided_namespace_name + let namespace = provided_namespace_name + .as_ref() + .and_then(|name| candidate_namespace.find_namespace(name)); + + // if a namespace was provided, but not found, then this is not the correct namespace. + if provided_namespace_name.is_some() && namespace.is_none() { + continue; + } + // Attempt to get the symbol from the global scope. If the namespace is None, use the candidate_namespace_id as a fallback + let res = namespace + .or(if namespace.is_none() { + Some(candidate_namespace_id) + } else { + None + }) + .and_then(|ns_id| globals.get(kind, ns_id, &provided_symbol_name.name)); + + // If a symbol was found, insert it into the candidates map + if let Some(res) = res { + candidates.insert(*res, open); } } if candidates.len() > 1 { - let mut opens: Vec<_> = candidates.into_values().collect(); - opens.sort_unstable_by_key(|open| open.span); - Err(Error::Ambiguous { - name: name_str.to_string(), - first_open: opens[0].namespace.to_string(), - second_open: opens[1].namespace.to_string(), - name_span: name.span, - first_open_span: opens[0].span, - second_open_span: opens[1].span, - }) - } else { - single(candidates.into_keys()) - .ok_or_else(|| Error::NotFound(name_str.to_string(), name.span)) + // If there are multiple candidates, remove unimplemented items. This allows resolution to + // succeed in cases where both an older, unimplemented API and newer, implemented API with the + // same name are both in scope without forcing the user to fully qualify the name. + if candidates.len() > 1 { + candidates.retain(|&res, _| !matches!(res, Res::Item(_, ItemStatus::Unimplemented))); + } } + candidates } +/// Fetch the name and namespace ID of all prelude namespaces. +fn prelude_namespaces(globals: &GlobalScope) -> Vec<(String, NamespaceId)> { + let mut prelude = vec![]; + + // add prelude to the list of candidate namespaces last, as they are the final fallback for a symbol + for prelude_namespace in PRELUDE { + let prelude_name = prelude_namespace + .iter() + .map(|s| Rc::from(*s)) + .collect::>(); + prelude.push(( + prelude_name.join("."), + globals + .namespaces + .find_namespace(prelude_name) + .expect("prelude should always exist in the namespace map"), + )); + } + prelude +} /// Implements shadowing rules within a single scope. /// A local variable always wins out against an item with the same name, even if they're declared in /// the same scope. It is implemented in a way that resembles Rust: @@ -1119,7 +1431,7 @@ fn resolve_scope_locals( } if let ScopeKind::Namespace(namespace) = &scope.kind { - if let Some(&res) = globals.get(kind, namespace, name) { + if let Some(&res) = globals.get(kind, *namespace, name) { return Some(res); } } @@ -1162,39 +1474,7 @@ fn get_scope_locals(scope: &Scope, offset: u32, vars: bool) -> Vec { names } -/// The return type represents the resolution of implicit opens, but also -/// retains the namespace that the resolution comes from. -/// This retained namespace string is used for error reporting. -fn resolve_implicit_opens<'a, 'b>( - kind: NameKind, - globals: &'b GlobalScope, - namespaces: impl IntoIterator, - name: &'b str, -) -> FxHashMap { - let mut candidates = FxHashMap::default(); - for namespace in namespaces { - if let Some(&res) = globals.get(kind, namespace, name) { - candidates.insert(res, *namespace); - } - } - candidates -} - -fn resolve_explicit_opens<'a>( - kind: NameKind, - globals: &GlobalScope, - opens: impl IntoIterator, - name: &str, -) -> FxHashMap { - let mut candidates = FxHashMap::default(); - for open in opens { - if let Some(&res) = globals.get(kind, &open.namespace, name) { - candidates.insert(res, open); - } - } - candidates -} - +/// Creates an [`ItemId`] for an item that is local to this package (internal to it). fn intrapackage(item: LocalItemId) -> ItemId { ItemId { package: None, diff --git a/compiler/qsc_frontend/src/resolve/tests.rs b/compiler/qsc_frontend/src/resolve/tests.rs index da9dd59e3f..e61d48b756 100644 --- a/compiler/qsc_frontend/src/resolve/tests.rs +++ b/compiler/qsc_frontend/src/resolve/tests.rs @@ -11,40 +11,68 @@ use crate::{ }; use expect_test::{expect, Expect}; use indoc::indoc; +use qsc_ast::ast::{Item, ItemKind, VecIdent}; use qsc_ast::{ assigner::Assigner as AstAssigner, ast::{Ident, NodeId, Package, Path, TopLevelNode}, mut_visit::MutVisitor, visit::{self, Visitor}, }; +use qsc_data_structures::namespaces::{NamespaceId, NamespaceTreeRoot}; use qsc_data_structures::{language_features::LanguageFeatures, span::Span}; use qsc_hir::assigner::Assigner as HirAssigner; +use rustc_hash::FxHashMap; use std::fmt::Write; +use std::rc::Rc; + +enum Change { + Res(Res), + NamespaceId(NamespaceId), +} + +impl From for Change { + fn from(res: Res) -> Self { + Self::Res(res) + } +} + +impl From for Change { + fn from(ns_id: NamespaceId) -> Self { + Self::NamespaceId(ns_id) + } +} struct Renamer<'a> { names: &'a Names, - changes: Vec<(Span, Res)>, + changes: Vec<(Span, Change)>, + namespaces: NamespaceTreeRoot, + aliases: FxHashMap>, NamespaceId>, } impl<'a> Renamer<'a> { - fn new(names: &'a Names) -> Self { + fn new(names: &'a Names, namespaces: NamespaceTreeRoot) -> Self { Self { names, changes: Vec::new(), + namespaces, + aliases: FxHashMap::default(), } } fn rename(&self, input: &mut String) { - for (span, res) in self.changes.iter().rev() { - let name = match res { - Res::Item(item, _) => match item.package { - None => format!("item{}", item.item), - Some(package) => format!("package{package}_item{}", item.item), + for (span, change) in self.changes.iter().rev() { + let name = match change { + Change::Res(res) => match res { + Res::Item(item, _) => match item.package { + None => format!("item{}", item.item), + Some(package) => format!("package{package}_item{}", item.item), + }, + Res::Local(node) => format!("local{node}"), + Res::PrimTy(prim) => format!("{prim:?}"), + Res::UnitTy => "Unit".to_string(), + Res::Param(id) => format!("param{id}"), }, - Res::Local(node) => format!("local{node}"), - Res::PrimTy(prim) => format!("{prim:?}"), - Res::UnitTy => "Unit".to_string(), - Res::Param(id) => format!("param{id}"), + Change::NamespaceId(ns_id) => format!("namespace{}", Into::::into(ns_id)), }; input.replace_range((span.lo as usize)..(span.hi as usize), &name); } @@ -54,7 +82,7 @@ impl<'a> Renamer<'a> { impl Visitor<'_> for Renamer<'_> { fn visit_path(&mut self, path: &Path) { if let Some(&id) = self.names.get(path.id) { - self.changes.push((path.span, id)); + self.changes.push((path.span, id.into())); } else { visit::walk_path(self, path); } @@ -62,8 +90,33 @@ impl Visitor<'_> for Renamer<'_> { fn visit_ident(&mut self, ident: &Ident) { if let Some(&id) = self.names.get(ident.id) { - self.changes.push((ident.span, id)); + self.changes.push((ident.span, id.into())); + } + } + + fn visit_item(&mut self, item: &'_ Item) { + if let ItemKind::Open(namespace, Some(alias)) = &*item.kind { + let Some(ns_id) = self.namespaces.find_namespace(namespace) else { + return; + }; + self.aliases.insert(vec![alias.name.clone()], ns_id); } + visit::walk_item(self, item); + } + + fn visit_vec_ident(&mut self, vec_ident: &VecIdent) { + let ns_id = match self.namespaces.find_namespace(vec_ident) { + Some(x) => x, + None => match self + .aliases + .get(&(Into::>>::into(vec_ident))) + .copied() + { + Some(x) => x, + None => return, + }, + }; + self.changes.push((vec_ident.span(), ns_id.into())); } } @@ -72,8 +125,8 @@ fn check(input: &str, expect: &Expect) { } fn resolve_names(input: &str) -> String { - let (package, names, _, errors) = compile(input, LanguageFeatures::default()); - let mut renamer = Renamer::new(&names); + let (package, names, _, errors, namespaces) = compile(input, LanguageFeatures::default()); + let mut renamer = Renamer::new(&names, namespaces); renamer.visit_package(&package); let mut output = input.to_string(); renamer.rename(&mut output); @@ -89,7 +142,7 @@ fn resolve_names(input: &str) -> String { fn compile( input: &str, language_features: LanguageFeatures, -) -> (Package, Names, Locals, Vec) { +) -> (Package, Names, Locals, Vec, NamespaceTreeRoot) { let (namespaces, parse_errors) = qsc_parse::namespaces(input, language_features); assert!(parse_errors.is_empty(), "parse failed: {parse_errors:#?}"); let mut package = Package { @@ -113,9 +166,9 @@ fn compile( let mut errors = globals.add_local_package(&mut assigner, &package); let mut resolver = Resolver::new(globals, dropped_names); resolver.with(&mut assigner).visit_package(&package); - let (names, locals, mut resolve_errors) = resolver.into_result(); + let (names, locals, mut resolve_errors, namespaces) = resolver.into_result(); errors.append(&mut resolve_errors); - (package, names, locals, errors) + (package, names, locals, errors, namespaces) } #[test] @@ -131,7 +184,7 @@ fn global_callable() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit {} function item2() : Unit { @@ -153,7 +206,7 @@ fn global_callable_recursive() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit { item1(); } @@ -175,7 +228,7 @@ fn global_callable_internal() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { internal function item1() : Unit {} function item2() : Unit { @@ -196,7 +249,7 @@ fn global_callable_duplicate_error() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit {} operation item2() : Unit {} } @@ -221,11 +274,11 @@ fn global_path() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit {} } - namespace item2 { + namespace namespace8 { function item3() : Unit { item1(); } @@ -251,12 +304,12 @@ fn open_namespace() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit {} } - namespace item2 { - open Foo; + namespace namespace8 { + open namespace7; function item3() : Unit { item1(); @@ -283,12 +336,12 @@ fn open_alias() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit {} } - namespace item2 { - open Foo as F; + namespace namespace8 { + open namespace7 as F; function item3() : Unit { item1(); @@ -313,11 +366,11 @@ fn prelude_callable() { } "}, &expect![[r#" - namespace item0 { + namespace namespace4 { function item1() : Unit {} } - namespace item2 { + namespace namespace7 { function item3() : Unit { item1(); } @@ -343,11 +396,11 @@ fn parent_namespace_shadows_prelude() { } "}, &expect![[r#" - namespace item0 { + namespace namespace4 { function item1() : Unit {} } - namespace item2 { + namespace namespace7 { function item3() : Unit {} function item4() : Unit { @@ -379,16 +432,16 @@ fn open_shadows_prelude() { } "}, &expect![[r#" - namespace item0 { + namespace namespace4 { function item1() : Unit {} } - namespace item2 { + namespace namespace7 { function item3() : Unit {} } - namespace item4 { - open Foo; + namespace namespace8 { + open namespace7; function item5() : Unit { item3(); @@ -417,15 +470,15 @@ fn ambiguous_prelude() { } "}, &expect![[r#" - namespace item0 { + namespace namespace3 { function item1() : Unit {} } - namespace item2 { + namespace namespace4 { function item3() : Unit {} } - namespace item4 { + namespace namespace7 { function item5() : Unit { A(); } @@ -448,7 +501,7 @@ fn local_var() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Int { let local13 = 0; local13 @@ -474,7 +527,7 @@ fn shadow_local() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Int { let local13 = 0; let local17 = { @@ -499,7 +552,7 @@ fn callable_param() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1(local8 : Int) : Int { local8 } @@ -521,7 +574,7 @@ fn spec_param() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1(local8 : Qubit) : (Qubit[], Qubit) { controlled (local23, ...) { (local23, local8) @@ -548,7 +601,7 @@ fn spec_param_shadow_disallowed() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1(local8 : Qubit[]) : Qubit[] { controlled (local20, ...) { local20 @@ -579,7 +632,7 @@ fn local_shadows_global() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit {} function item2() : Int { @@ -605,7 +658,7 @@ fn shadow_same_block() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Int { let local13 = 0; let local17 = local13 + 1; @@ -635,12 +688,12 @@ fn parent_namespace_shadows_open() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit {} } - namespace item2 { - open Foo; + namespace namespace8 { + open namespace7; function item3() : Unit {} @@ -673,16 +726,16 @@ fn open_alias_shadows_global() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit {} } - namespace item2 { + namespace namespace8 { function item3() : Unit {} } - namespace item4 { - open Foo as Bar; + namespace namespace9 { + open namespace7 as Bar; function item5() : Unit { item1(); @@ -701,7 +754,7 @@ fn shadowing_disallowed_within_parameters() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1(local8: Int, local13: Double, local18: Bool) : Unit {} } @@ -721,7 +774,7 @@ fn shadowing_disallowed_within_local_binding() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1() : Unit { let (local14, local16, local18) = (1, 2, 3); } @@ -743,7 +796,7 @@ fn shadowing_disallowed_within_for_loop() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1() : Unit { for (local15, local17, local19) in [(1, 1, 1)] {} } @@ -765,7 +818,7 @@ fn shadowing_disallowed_within_lambda_param() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1() : Unit { let local13 = (local17, local19, local21) -> local21 + local19 + 1; } @@ -799,17 +852,17 @@ fn merged_aliases() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit {} } - namespace item2 { + namespace namespace8 { function item3() : Unit {} } - namespace item4 { - open Foo as Alias; - open Bar as Alias; + namespace namespace9 { + open namespace7 as Alias; + open namespace8 as Alias; function item5() : Unit { item1(); @@ -830,7 +883,7 @@ fn ty_decl() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { newtype item1 = Unit; function item2(local14 : item1) : Unit {} } @@ -848,7 +901,7 @@ fn ty_decl_duplicate_error() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { newtype item1 = Unit; newtype item2 = Bool; } @@ -867,7 +920,7 @@ fn ty_decl_duplicate_error_on_built_in_ty() { } "}, &expect![[r#" - namespace item0 { + namespace namespace4 { newtype item1 = Unit; } @@ -886,7 +939,7 @@ fn ty_decl_in_ty_decl() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { newtype item1 = Unit; newtype item2 = item1; } @@ -903,7 +956,7 @@ fn ty_decl_recursive() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { newtype item1 = item1; } "#]], @@ -923,7 +976,7 @@ fn ty_decl_cons() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { newtype item1 = Unit; function item2() : item1 { @@ -945,7 +998,7 @@ fn unknown_term() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit { B(); } @@ -965,7 +1018,7 @@ fn unknown_ty() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1(local8 : B) : Unit {} } @@ -996,17 +1049,17 @@ fn open_ambiguous_terms() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit {} } - namespace item2 { + namespace namespace8 { function item3() : Unit {} } - namespace item4 { - open Foo; - open Bar; + namespace namespace9 { + open namespace7; + open namespace8; function item5() : Unit { A(); @@ -1038,17 +1091,17 @@ fn open_ambiguous_tys() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { newtype item1 = Unit; } - namespace item2 { + namespace namespace8 { newtype item3 = Unit; } - namespace item4 { - open Foo; - open Bar; + namespace namespace9 { + open namespace7; + open namespace8; function item5(local28 : A) : Unit {} } @@ -1080,20 +1133,20 @@ fn merged_aliases_ambiguous_terms() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit {} } - namespace item2 { + namespace namespace8 { function item3() : Unit {} } - namespace item4 { - open Foo as Alias; - open Bar as Alias; + namespace namespace9 { + open namespace7 as Alias; + open namespace8 as Alias; function item5() : Unit { - Alias.A(); + namespace8.A(); } } @@ -1122,19 +1175,19 @@ fn merged_aliases_ambiguous_tys() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { newtype item1 = Unit; } - namespace item2 { + namespace namespace8 { newtype item3 = Unit; } - namespace item4 { - open Foo as Alias; - open Bar as Alias; + namespace namespace9 { + open namespace7 as Alias; + open namespace8 as Alias; - function item5(local30 : Alias.A) : Unit {} + function item5(local30 : namespace8.A) : Unit {} } // Ambiguous { name: "A", first_open: "Foo", second_open: "Bar", name_span: Span { lo: 170, hi: 171 }, first_open_span: Span { lo: 107, hi: 110 }, second_open_span: Span { lo: 130, hi: 133 } } @@ -1153,7 +1206,7 @@ fn lambda_param() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit { let local13 = local16 -> local16 + 1; } @@ -1175,7 +1228,7 @@ fn lambda_shadows_local() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Int { let local13 = 1; let local17 = local20 -> local20 + 1; @@ -1199,7 +1252,7 @@ fn for_loop_range() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit { for local14 in 0..9 { let _ = local14; @@ -1223,7 +1276,7 @@ fn for_loop_var() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1(local8 : Int[]) : Unit { for local20 in local8 { let _ = local20; @@ -1248,7 +1301,7 @@ fn repeat_until() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1() : Unit { mutable local13 = false; repeat { @@ -1277,7 +1330,7 @@ fn repeat_until_fixup() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1() : Unit { mutable local13 = false; repeat { @@ -1308,7 +1361,7 @@ fn repeat_until_fixup_scoping() { } }"}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1() : Unit { repeat { mutable local16 = false; @@ -1340,7 +1393,7 @@ fn use_qubit() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1(local8 : Qubit) : Unit { body intrinsic; } @@ -1369,7 +1422,7 @@ fn use_qubit_block() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1(local8 : Qubit) : Unit { body intrinsic; } @@ -1400,7 +1453,7 @@ fn use_qubit_block_qubit_restricted_to_block_scope() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1(local8 : Qubit) : Unit { body intrinsic; } @@ -1429,7 +1482,7 @@ fn local_function() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Int { function item2() : Int { 2 } item2() + 1 @@ -1451,7 +1504,7 @@ fn local_function_use_before_declare() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : () { item2(); function item2() : () {} @@ -1475,7 +1528,7 @@ fn local_function_is_really_local() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : () { function item3() : () {} item3(); @@ -1501,7 +1554,7 @@ fn local_function_is_not_closure() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : () { let local11 = 2; function item2() : Int { x } @@ -1525,7 +1578,7 @@ fn local_type() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : () { newtype item2 = Int; let local18 = item2(5); @@ -1543,8 +1596,8 @@ fn local_open() { namespace B { function Bar() : () {} } "}, &expect![[r#" - namespace item0 { function item1() : () { open B; item3(); } } - namespace item2 { function item3() : () {} } + namespace namespace7 { function item1() : () { open namespace8; item3(); } } + namespace namespace8 { function item3() : () {} } "#]], ); } @@ -1561,12 +1614,12 @@ fn local_open_shadows_parent_item() { namespace B { function Bar() : () {} } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : () {} - function item2() : () { open B; item4(); } + function item2() : () { open namespace8; item4(); } } - namespace item3 { function item4() : () {} } + namespace namespace8 { function item4() : () {} } "#]], ); } @@ -1584,13 +1637,13 @@ fn local_open_shadows_parent_open() { namespace C { function Bar() : () {} } "}, &expect![[r#" - namespace item0 { - open B; - function item1() : () { open C; item5(); } + namespace namespace7 { + open namespace8; + function item1() : () { open namespace9; item5(); } } - namespace item2 { function item3() : () {} } - namespace item4 { function item5() : () {} } + namespace namespace8 { function item3() : () {} } + namespace namespace9 { function item5() : () {} } "#]], ); } @@ -1608,7 +1661,7 @@ fn update_array_index_var() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : () { let local11 = [2]; let local16 = 0; @@ -1632,7 +1685,7 @@ fn update_array_index_expr() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : () { let local11 = [2]; let local16 = 0; @@ -1657,7 +1710,7 @@ fn update_udt_known_field_name() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { newtype item1 = (First : Int, Second : Int); function item2() : () { @@ -1683,7 +1736,7 @@ fn update_udt_known_field_name_expr() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { newtype item1 = (First : Int, Second : Int); function item2() : () { @@ -1711,7 +1764,7 @@ fn update_udt_unknown_field_name() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { newtype item1 = (First : Int, Second : Int); function item2() : () { @@ -1739,7 +1792,7 @@ fn update_udt_unknown_field_name_known_global() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { newtype item1 = (First : Int, Second : Int); function item2() : () {} @@ -1762,7 +1815,7 @@ fn unknown_namespace() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { open Microsoft.Quantum.Fake; } @@ -1782,11 +1835,11 @@ fn empty_namespace_works() { namespace B {} "}, &expect![[r#" - namespace item0 { - open B; + namespace namespace7 { + open namespace8; function item1(): Unit{} } - namespace item2 {} + namespace namespace8 {} "#]], ); } @@ -1809,14 +1862,14 @@ fn cyclic_namespace_dependency_supported() { } "}, &expect![[r#" - namespace item0 { - open B; + namespace namespace7 { + open namespace8; operation item1() : Unit { item3(); } } - namespace item2 { - open A; + namespace namespace8 { + open namespace7; operation item3() : Unit { item1(); } @@ -1841,7 +1894,7 @@ fn bind_items_in_repeat() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1() : Unit { repeat { function item2() : Unit {} @@ -1868,7 +1921,7 @@ fn bind_items_in_qubit_use_block() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1() : Unit { use local13 = Qubit() { function item2() : Unit {} @@ -1893,7 +1946,7 @@ fn use_bound_item_in_another_bound_item() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit { function item2() : Unit { item3(); @@ -1916,7 +1969,7 @@ fn use_unbound_generic() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1(local9: 'U) : 'U { local9 } @@ -1938,7 +1991,7 @@ fn resolve_local_generic() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1(local9: param0) : param0 { local9 } @@ -1961,7 +2014,7 @@ fn dropped_callable() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { @Config(Base) function Dropped() : Unit {} @@ -2002,7 +2055,7 @@ fn multiple_definition_dropped_is_not_found() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { @Config(Unrestricted) operation item1() : Unit {} @Config(Base) @@ -2012,20 +2065,20 @@ fn multiple_definition_dropped_is_not_found() { @Config(Unrestricted) operation item2() : Unit {} } - namespace item3 { + namespace namespace8 { operation item4() : Unit { B(); C(); } operation item5() : Unit { - open A; + open namespace7; item1(); item2(); } } - // NotFound("B", Span { lo: 265, hi: 266 }) - // NotFound("C", Span { lo: 278, hi: 279 }) + // NotAvailable("B", "A.B", Span { lo: 265, hi: 266 }) + // NotAvailable("C", "A.C", Span { lo: 278, hi: 279 }) "#]], ); } @@ -2046,12 +2099,12 @@ fn disallow_duplicate_intrinsic() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { operation item1() : Unit { body intrinsic; } } - namespace item2 { + namespace namespace8 { operation item3() : Unit { body intrinsic; } @@ -2081,15 +2134,15 @@ fn disallow_duplicate_intrinsic_and_non_intrinsic_collision() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { internal operation item1() : Unit { body intrinsic; } } - namespace item2 { + namespace namespace8 { operation item3() : Unit {} } - namespace item4 { + namespace namespace8 { operation item5() : Unit { body intrinsic; } @@ -2112,7 +2165,7 @@ 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()); let locals = locals.get_all_at_offset(cursor_offset); let actual = locals.iter().fold(String::new(), |mut output, l| { @@ -2417,7 +2470,7 @@ fn use_after_scope() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit { { let local16 = 42; @@ -2446,7 +2499,7 @@ fn nested_function_definition() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit { function item2() : Unit { function item3() : Unit {} @@ -2477,7 +2530,7 @@ fn variable_in_nested_blocks() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit { { let local16 = 10; @@ -2510,11 +2563,11 @@ fn function_call_with_namespace_alias() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit {} } - namespace item2 { - open Foo as F; + namespace namespace8 { + open namespace7 as F; function item3() : Unit { item1(); } @@ -2538,7 +2591,7 @@ fn type_alias_in_function_scope() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit { newtype item3 = Int; let local20 : item3 = item3(5); @@ -2569,7 +2622,7 @@ fn lambda_inside_lambda() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit { let local13 = () -> { let local20 = (local24) -> local24 + 1; @@ -2598,10 +2651,10 @@ fn nested_namespaces_with_same_function_name() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { function item1() : Unit {} } - namespace item2 { + namespace namespace8 { function item3() : Unit {} function item4() : Unit { item1(); @@ -2621,7 +2674,7 @@ fn newtype_with_invalid_field_type() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { newtype item1 = (Re: Real, Im: Imaginary); // Imaginary is not a valid type } @@ -2644,7 +2697,7 @@ fn newtype_with_tuple_destructuring() { } "}, &expect![[r#" - namespace item0 { + namespace namespace7 { newtype item1 = (First: Int, Second: Int); function item2(local21: item1) : Int { let (local32, local34) = local21; @@ -2654,3 +2707,115 @@ fn newtype_with_tuple_destructuring() { "#]], ); } + +#[test] +fn items_resolve_according_to_implicit_hierarchy() { + check( + indoc! {" +namespace Foo { + @EntryPoint() + function Main(): Int { + Foo() + } + + function Foo() : Int { + Bar.Baz.Quux() + } +} + +namespace Foo.Bar.Baz { + function Quux() : Int { 6 } +} +"}, + &expect![[r#" + namespace namespace7 { + @EntryPoint() + function item1(): Int { + item2() + } + + function item2() : Int { + item4() + } + } + + namespace namespace9 { + function item4() : Int { 6 } + } + "#]], + ); +} +#[test] +fn basic_hierarchical_namespace() { + check( + indoc! {" + namespace Foo.Bar.Baz { + operation Quux() : Unit {} + } + namespace A { + open Foo; + operation Main() : Unit { + Bar.Baz.Quux(); + } + } + namespace B { + open Foo.Bar; + operation Main() : Unit { + Baz.Quux(); + } + }"}, + &expect![[r#" + namespace namespace9 { + operation item1() : Unit {} + } + namespace namespace10 { + open namespace7; + operation item3() : Unit { + item1(); + } + } + namespace namespace11 { + open namespace8; + operation item5() : Unit { + item1(); + } + }"#]], + ); +} + +#[test] +fn test_katas_shadowing_use_case() { + check( + indoc! {"namespace Kata { + operation ApplyX() : Unit { + // Do nothing. + } +} + +namespace Kata.Verification { + operation CheckSolution() : Bool { + let _ = Kata.ApplyX(); + let _ = ApplyX(); + } + + operation ApplyX() : Unit {} +} +" }, + &expect![[r#" + namespace namespace7 { + operation item1() : Unit { + // Do nothing. + } + } + + namespace namespace8 { + operation item3() : Bool { + let _ = item1(); + let _ = item4(); + } + + operation item4() : Unit {} + } + "#]], + ); +} diff --git a/compiler/qsc_frontend/src/typeck/tests.rs b/compiler/qsc_frontend/src/typeck/tests.rs index 1bc0886f46..e439a4d6ad 100644 --- a/compiler/qsc_frontend/src/typeck/tests.rs +++ b/compiler/qsc_frontend/src/typeck/tests.rs @@ -88,9 +88,8 @@ fn compile(input: &str, entry_expr: &str) -> (Package, super::Table, Vec Int) - #21 104-125 "([true, false, true])" : Bool[] - #22 105-124 "[true, false, true]" : Bool[] - #23 106-110 "true" : Bool - #24 112-117 "false" : Bool - #25 119-123 "true" : Bool - "#]], + &expect![[r##" + #9 58-69 "(xs : 'T[])" : ? + #10 59-68 "xs : 'T[]" : ? + #19 98-125 "Length([true, false, true])" : Int + #20 98-104 "Length" : (Bool[] -> Int) + #23 104-125 "([true, false, true])" : Bool[] + #24 105-124 "[true, false, true]" : Bool[] + #25 106-110 "true" : Bool + #26 112-117 "false" : Bool + #27 119-123 "true" : Bool + "##]], ); } @@ -327,15 +326,15 @@ fn int_as_double_error() { } "}, "Microsoft.Quantum.Convert.IntAsDouble(false)", - &expect![[r#" - #6 62-71 "(a : Int)" : ? - #7 63-70 "a : Int" : ? - #16 103-147 "Microsoft.Quantum.Convert.IntAsDouble(false)" : Double - #17 103-140 "Microsoft.Quantum.Convert.IntAsDouble" : (Int -> Double) - #21 140-147 "(false)" : Bool - #22 141-146 "false" : Bool + &expect![[r##" + #8 62-71 "(a : Int)" : ? + #9 63-70 "a : Int" : ? + #18 103-147 "Microsoft.Quantum.Convert.IntAsDouble(false)" : Double + #19 103-140 "Microsoft.Quantum.Convert.IntAsDouble" : (Int -> Double) + #25 140-147 "(false)" : Bool + #26 141-146 "false" : Bool Error(Type(Error(TyMismatch("Int", "Bool", Span { lo: 103, hi: 147 })))) - "#]], + "##]], ); } @@ -348,19 +347,19 @@ fn length_type_error() { } "}, "Length((1, 2, 3))", - &expect![[r#" - #7 58-69 "(xs : 'T[])" : ? - #8 59-68 "xs : 'T[]" : ? - #17 98-115 "Length((1, 2, 3))" : Int - #18 98-104 "Length" : (?0[] -> Int) - #21 104-115 "((1, 2, 3))" : (Int, Int, Int) - #22 105-114 "(1, 2, 3)" : (Int, Int, Int) - #23 106-107 "1" : Int - #24 109-110 "2" : Int - #25 112-113 "3" : Int + &expect![[r##" + #9 58-69 "(xs : 'T[])" : ? + #10 59-68 "xs : 'T[]" : ? + #19 98-115 "Length((1, 2, 3))" : Int + #20 98-104 "Length" : (?0[] -> Int) + #23 104-115 "((1, 2, 3))" : (Int, Int, Int) + #24 105-114 "(1, 2, 3)" : (Int, Int, Int) + #25 106-107 "1" : Int + #26 109-110 "2" : Int + #27 112-113 "3" : Int Error(Type(Error(TyMismatch("?[]", "(Int, Int, Int)", Span { lo: 98, hi: 115 })))) Error(Type(Error(AmbiguousTy(Span { lo: 98, hi: 104 })))) - "#]], + "##]], ); } @@ -376,21 +375,21 @@ fn single_arg_for_tuple() { use q = Qubit(); Ry(q); }"}, - &expect![[r#" - #6 56-87 "(theta : Double, qubit : Qubit)" : (Double, Qubit) - #7 57-71 "theta : Double" : Double - #12 73-86 "qubit : Qubit" : Qubit - #21 106-108 "{}" : Unit - #22 111-146 "{\n use q = Qubit();\n Ry(q);\n}" : Unit - #23 111-146 "{\n use q = Qubit();\n Ry(q);\n}" : Unit - #25 121-122 "q" : Qubit - #27 125-132 "Qubit()" : Qubit - #29 138-143 "Ry(q)" : Unit - #30 138-140 "Ry" : ((Double, Qubit) => Unit is Adj + Ctl) - #33 140-143 "(q)" : Qubit - #34 141-142 "q" : Qubit + &expect![[r##" + #8 56-87 "(theta : Double, qubit : Qubit)" : (Double, Qubit) + #9 57-71 "theta : Double" : Double + #14 73-86 "qubit : Qubit" : Qubit + #23 106-108 "{}" : Unit + #24 111-146 "{\n use q = Qubit();\n Ry(q);\n}" : Unit + #25 111-146 "{\n use q = Qubit();\n Ry(q);\n}" : Unit + #27 121-122 "q" : Qubit + #29 125-132 "Qubit()" : Qubit + #31 138-143 "Ry(q)" : Unit + #32 138-140 "Ry" : ((Double, Qubit) => Unit is Adj + Ctl) + #35 140-143 "(q)" : Qubit + #36 141-142 "q" : Qubit Error(Type(Error(TyMismatch("(Double, Qubit)", "Qubit", Span { lo: 138, hi: 143 })))) - "#]], + "##]], ); } diff --git a/compiler/qsc_hir/src/global.rs b/compiler/qsc_hir/src/global.rs index 2d51e9a667..3ddf8b5232 100644 --- a/compiler/qsc_hir/src/global.rs +++ b/compiler/qsc_hir/src/global.rs @@ -5,12 +5,16 @@ use crate::{ hir::{Item, ItemId, ItemKind, ItemStatus, Package, PackageId, SpecBody, SpecGen, Visibility}, ty::Scheme, }; -use qsc_data_structures::index_map; +use qsc_data_structures::{ + index_map, + namespaces::{NamespaceId, NamespaceTreeRoot}, +}; use rustc_hash::FxHashMap; use std::rc::Rc; +#[derive(Debug)] pub struct Global { - pub namespace: Rc, + pub namespace: Vec>, pub name: Rc, pub visibility: Visibility, pub status: ItemStatus, @@ -23,6 +27,16 @@ pub enum Kind { Term(Term), } +impl std::fmt::Debug for Kind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Kind::Namespace => write!(f, "Namespace"), + Kind::Ty(ty) => write!(f, "Ty({})", ty.id), + Kind::Term(term) => write!(f, "Term({})", term.id), + } + } +} + pub struct Ty { pub id: ItemId, } @@ -33,21 +47,28 @@ pub struct Term { pub intrinsic: bool, } +/// A lookup table used for looking up global core items for insertion in `qsc_passes`. #[derive(Default)] pub struct Table { - tys: FxHashMap, FxHashMap, Ty>>, - terms: FxHashMap, FxHashMap, Term>>, + tys: FxHashMap, Ty>>, + terms: FxHashMap, Term>>, + namespaces: NamespaceTreeRoot, } impl Table { #[must_use] - pub fn resolve_ty(&self, namespace: &str, name: &str) -> Option<&Ty> { - self.tys.get(namespace).and_then(|terms| terms.get(name)) + pub fn resolve_ty(&self, namespace: NamespaceId, name: &str) -> Option<&Ty> { + self.tys.get(&namespace).and_then(|terms| terms.get(name)) } #[must_use] - pub fn resolve_term(&self, namespace: &str, name: &str) -> Option<&Term> { - self.terms.get(namespace).and_then(|terms| terms.get(name)) + pub fn resolve_term(&self, namespace: NamespaceId, name: &str) -> Option<&Term> { + self.terms.get(&namespace).and_then(|terms| terms.get(name)) + } + + pub fn find_namespace(&self, vec: impl Into>>) -> Option { + // find a namespace if it exists and return its id + self.namespaces.find_namespace(vec) } } @@ -55,16 +76,16 @@ impl FromIterator for Table { fn from_iter>(iter: T) -> Self { let mut tys: FxHashMap<_, FxHashMap<_, _>> = FxHashMap::default(); let mut terms: FxHashMap<_, FxHashMap<_, _>> = FxHashMap::default(); + let mut namespaces = NamespaceTreeRoot::default(); for global in iter { + let namespace = namespaces.insert_or_find_namespace(global.namespace.into_iter()); match global.kind { Kind::Ty(ty) => { - tys.entry(global.namespace) - .or_default() - .insert(global.name, ty); + tys.entry(namespace).or_default().insert(global.name, ty); } Kind::Term(term) => { terms - .entry(global.namespace) + .entry(namespace) .or_default() .insert(global.name, term); } @@ -72,7 +93,11 @@ impl FromIterator for Table { } } - Self { tys, terms } + Self { + tys, + terms, + namespaces, + } } } @@ -101,7 +126,7 @@ impl PackageIter<'_> { match (&item.kind, &parent) { (ItemKind::Callable(decl), Some(ItemKind::Namespace(namespace, _))) => Some(Global { - namespace: Rc::clone(&namespace.name), + namespace: namespace.into(), name: Rc::clone(&decl.name.name), visibility: item.visibility, status, @@ -113,7 +138,7 @@ impl PackageIter<'_> { }), (ItemKind::Ty(name, def), Some(ItemKind::Namespace(namespace, _))) => { self.next = Some(Global { - namespace: Rc::clone(&namespace.name), + namespace: namespace.into(), name: Rc::clone(&name.name), visibility: item.visibility, status, @@ -125,7 +150,7 @@ impl PackageIter<'_> { }); Some(Global { - namespace: Rc::clone(&namespace.name), + namespace: namespace.into(), name: Rc::clone(&name.name), visibility: item.visibility, status, @@ -133,8 +158,8 @@ impl PackageIter<'_> { }) } (ItemKind::Namespace(ident, _), None) => Some(Global { - namespace: "".into(), - name: Rc::clone(&ident.name), + namespace: ident.into(), + name: "".into(), visibility: Visibility::Public, status, kind: Kind::Namespace, diff --git a/compiler/qsc_hir/src/hir.rs b/compiler/qsc_hir/src/hir.rs index 10422ec187..28613f99df 100644 --- a/compiler/qsc_hir/src/hir.rs +++ b/compiler/qsc_hir/src/hir.rs @@ -4,7 +4,6 @@ //! The high-level intermediate representation for Q#. HIR is lowered from the AST. #![warn(missing_docs)] - use crate::ty::{Arrow, FunctorSet, FunctorSetValue, GenericArg, GenericParam, Scheme, Ty, Udt}; use indenter::{indented, Indented}; use num_bigint::BigInt; @@ -333,7 +332,7 @@ pub enum ItemKind { /// A `function` or `operation` declaration. Callable(CallableDecl), /// A `namespace` declaration. - Namespace(Ident, Vec), + Namespace(VecIdent, Vec), /// A `newtype` declaration. Ty(Ident, Udt), } @@ -1135,6 +1134,110 @@ impl Display for QubitInitKind { } } +/// A [`VecIdent`] represents a sequence of idents. It provides a helpful abstraction +/// that is more powerful than a simple `Vec`, and is primarily used to represent +/// dot-separated paths. +#[derive(Clone, Debug, Eq, Hash, PartialEq, Default)] +pub struct VecIdent(pub Vec); + +impl<'a> IntoIterator for &'a VecIdent { + type IntoIter = std::slice::Iter<'a, Ident>; + type Item = &'a Ident; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} +impl From for Vec> { + fn from(v: VecIdent) -> Self { + v.0.iter().map(|i| i.name.clone()).collect() + } +} + +impl From<&VecIdent> for Vec> { + fn from(v: &VecIdent) -> Self { + v.0.iter().map(|i| i.name.clone()).collect() + } +} + +impl From> for VecIdent { + fn from(v: Vec) -> Self { + VecIdent(v) + } +} + +impl From for Vec { + fn from(v: VecIdent) -> Self { + v.0 + } +} + +impl FromIterator for VecIdent { + fn from_iter>(iter: T) -> Self { + VecIdent(iter.into_iter().collect()) + } +} + +impl Display for VecIdent { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut buf = Vec::with_capacity(self.0.len()); + + for ident in &self.0 { + buf.push(format!("{ident}")); + } + if buf.len() > 1 { + // use square brackets only if there are more than one ident + write!(f, "[{}]", buf.join(", ")) + } else { + write!(f, "{}", buf[0]) + } + } +} +impl VecIdent { + /// constructs an iter over the [Ident]s that this contains. + pub fn iter(&self) -> std::slice::Iter<'_, Ident> { + self.0.iter() + } + + /// the conjoined span of all idents in the `VecIdent` + #[must_use] + pub fn span(&self) -> Span { + Span { + lo: self.0.first().map(|i| i.span.lo).unwrap_or_default(), + hi: self.0.last().map(|i| i.span.hi).unwrap_or_default(), + } + } + + /// Whether or not the first ident in this [`VecIdent`] matches `arg` + #[must_use] + pub fn starts_with(&self, arg: &str) -> bool { + self.0.first().is_some_and(|i| &*i.name == arg) + } + + /// Whether or not the first `n` idents in this [`VecIdent`] match `arg` + #[must_use] + pub fn starts_with_sequence(&self, arg: &[&str]) -> bool { + if arg.len() > self.0.len() { + return false; + } + for (i, s) in arg.iter().enumerate() { + if &*self.0[i].name != *s { + return false; + } + } + true + } + + /// The stringified dot-separated path of the idents in this [`VecIdent`] + /// E.g. `a.b.c` + #[must_use] + pub fn name(&self) -> String { + self.0 + .iter() + .map(|i| i.name.to_string()) + .collect::>() + .join(".") + } +} /// An identifier. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Ident { diff --git a/compiler/qsc_hir/src/mut_visit.rs b/compiler/qsc_hir/src/mut_visit.rs index 00333b70d3..bf77b7ed8d 100644 --- a/compiler/qsc_hir/src/mut_visit.rs +++ b/compiler/qsc_hir/src/mut_visit.rs @@ -49,8 +49,15 @@ pub trait MutVisitor: Sized { } fn visit_span(&mut self, _: &mut Span) {} + fn visit_vec_ident(&mut self, ident: &mut crate::hir::VecIdent) { + walk_vec_ident(self, ident); + } +} +pub fn walk_vec_ident(vis: &mut impl MutVisitor, ident: &mut crate::hir::VecIdent) { + for ref mut ident in &mut ident.0 { + vis.visit_ident(ident); + } } - pub fn walk_package(vis: &mut impl MutVisitor, package: &mut Package) { package.items.values_mut().for_each(|i| vis.visit_item(i)); package.stmts.iter_mut().for_each(|s| vis.visit_stmt(s)); @@ -62,7 +69,8 @@ pub fn walk_item(vis: &mut impl MutVisitor, item: &mut Item) { match &mut item.kind { ItemKind::Callable(decl) => vis.visit_callable_decl(decl), - ItemKind::Namespace(name, _) | ItemKind::Ty(name, _) => vis.visit_ident(name), + ItemKind::Namespace(name, _) => vis.visit_vec_ident(name), + ItemKind::Ty(name, _) => vis.visit_ident(name), } } diff --git a/compiler/qsc_hir/src/visit.rs b/compiler/qsc_hir/src/visit.rs index 6f9655c29a..1c6372ab22 100644 --- a/compiler/qsc_hir/src/visit.rs +++ b/compiler/qsc_hir/src/visit.rs @@ -3,7 +3,7 @@ use crate::hir::{ Block, CallableDecl, Expr, ExprKind, Ident, Item, ItemKind, Package, Pat, PatKind, QubitInit, - QubitInitKind, SpecBody, SpecDecl, Stmt, StmtKind, StringComponent, + QubitInitKind, SpecBody, SpecDecl, Stmt, StmtKind, StringComponent, VecIdent, }; pub trait Visitor<'a>: Sized { @@ -44,6 +44,8 @@ pub trait Visitor<'a>: Sized { } fn visit_ident(&mut self, _: &'a Ident) {} + + fn visit_vec_ident(&mut self, _: &'a VecIdent) {} } pub fn walk_package<'a>(vis: &mut impl Visitor<'a>, package: &'a Package) { @@ -55,7 +57,8 @@ pub fn walk_package<'a>(vis: &mut impl Visitor<'a>, package: &'a Package) { pub fn walk_item<'a>(vis: &mut impl Visitor<'a>, item: &'a Item) { match &item.kind { ItemKind::Callable(decl) => vis.visit_callable_decl(decl), - ItemKind::Namespace(name, _) | ItemKind::Ty(name, _) => vis.visit_ident(name), + ItemKind::Namespace(name, _) => vis.visit_vec_ident(name), + ItemKind::Ty(name, _) => vis.visit_ident(name), } } diff --git a/compiler/qsc_parse/src/item.rs b/compiler/qsc_parse/src/item.rs index 14375d5b6c..47e65101ee 100644 --- a/compiler/qsc_parse/src/item.rs +++ b/compiler/qsc_parse/src/item.rs @@ -10,7 +10,7 @@ mod tests; use super::{ expr::expr, keyword::Keyword, - prim::{dot_ident, ident, many, opt, pat, seq, token}, + prim::{ident, many, opt, pat, seq, token}, scan::ParserContext, stmt, ty::{self, ty}, @@ -18,7 +18,7 @@ use super::{ }; use crate::{ lex::{Delim, TokenKind}, - prim::{barrier, recovering, recovering_token, shorten}, + prim::{barrier, path, recovering, recovering_token, shorten}, stmt::check_semis, ty::array_or_arrow, ErrorKind, @@ -136,7 +136,7 @@ fn parse_namespace(s: &mut ParserContext) -> Result { let lo = s.peek().span.lo; let doc = parse_doc(s).unwrap_or_default(); token(s, TokenKind::Keyword(Keyword::Namespace))?; - let name = dot_ident(s)?; + let name = path(s)?; token(s, TokenKind::Open(Delim::Brace))?; let items = barrier(s, &[TokenKind::Close(Delim::Brace)], parse_many)?; recovering_token(s, TokenKind::Close(Delim::Brace)); @@ -144,7 +144,11 @@ fn parse_namespace(s: &mut ParserContext) -> Result { id: NodeId::default(), span: s.span(lo), doc: doc.into(), - name, + name: { + let mut buf = name.namespace.clone().unwrap_or_default(); + buf.0.push(*name.name); + buf + }, items: items.into_boxed_slice(), }) } @@ -202,14 +206,17 @@ fn parse_visibility(s: &mut ParserContext) -> Result { fn parse_open(s: &mut ParserContext) -> Result> { token(s, TokenKind::Keyword(Keyword::Open))?; - let name = dot_ident(s)?; + let mut name = vec![*(ident(s)?)]; + while let Ok(_dot) = token(s, TokenKind::Dot) { + name.push(*(ident(s)?)); + } let alias = if token(s, TokenKind::Keyword(Keyword::As)).is_ok() { - Some(dot_ident(s)?) + Some(ident(s)?) } else { None }; token(s, TokenKind::Semi)?; - Ok(Box::new(ItemKind::Open(name, alias))) + Ok(Box::new(ItemKind::Open(name.into(), alias))) } fn parse_newtype(s: &mut ParserContext) -> Result> { diff --git a/compiler/qsc_parse/src/item/tests.rs b/compiler/qsc_parse/src/item/tests.rs index d6c1de400b..4491d41a84 100644 --- a/compiler/qsc_parse/src/item/tests.rs +++ b/compiler/qsc_parse/src/item/tests.rs @@ -108,7 +108,7 @@ fn open_no_alias() { "open Foo.Bar.Baz;", &expect![[r#" Item _id_ [0-17]: - Open (Ident _id_ [5-16] "Foo.Bar.Baz")"#]], + Open ([Ident _id_ [5-8] "Foo", Ident _id_ [9-12] "Bar", Ident _id_ [13-16] "Baz"])"#]], ); } @@ -119,18 +119,7 @@ fn open_alias() { "open Foo.Bar.Baz as Baz;", &expect![[r#" Item _id_ [0-24]: - Open (Ident _id_ [5-16] "Foo.Bar.Baz") (Ident _id_ [20-23] "Baz")"#]], - ); -} - -#[test] -fn open_alias_dot() { - check( - parse, - "open Foo.Bar.Baz as Bar.Baz;", - &expect![[r#" - Item _id_ [0-28]: - Open (Ident _id_ [5-16] "Foo.Bar.Baz") (Ident _id_ [20-27] "Bar.Baz")"#]], + Open ([Ident _id_ [5-8] "Foo", Ident _id_ [9-12] "Bar", Ident _id_ [13-16] "Baz"]) (Ident _id_ [20-23] "Baz")"#]], ); } diff --git a/compiler/qsc_parse/src/prim.rs b/compiler/qsc_parse/src/prim.rs index 96f647347d..5cbc35ca29 100644 --- a/compiler/qsc_parse/src/prim.rs +++ b/compiler/qsc_parse/src/prim.rs @@ -77,24 +77,6 @@ pub(super) fn ident(s: &mut ParserContext) -> Result> { } } -/// This function parses a [Path] from the given context -/// and converts it into a single ident, which contains dots (`.`) -pub(super) fn dot_ident(s: &mut ParserContext) -> Result> { - let p = path(s)?; - let mut name = String::new(); - if let Some(namespace) = p.namespace { - name.push_str(&namespace.name); - name.push('.'); - } - name.push_str(&p.name.name); - - Ok(Box::new(Ident { - id: p.id, - span: p.span, - name: name.into(), - })) -} - /// A `path` is a dot-separated list of idents like "Foo.Bar.Baz" /// this can be either a namespace name (in an open statement or namespace declaration) or /// it can be a direct reference to something in a namespace, like `Microsoft.Quantum.Diagnostics.DumpMachine()` @@ -107,15 +89,17 @@ pub(super) fn path(s: &mut ParserContext) -> Result> { let name = parts.pop().expect("path should have at least one part"); let namespace = match (parts.first(), parts.last()) { - (Some(first), Some(last)) => { - let lo = first.span.lo; - let hi = last.span.hi; - Some(Box::new(Ident { - id: NodeId::default(), - span: Span { lo, hi }, - name: join(parts.iter().map(|i| &i.name), ".").into(), - })) - } + (Some(_), Some(_)) => Some( + parts + .iter() + .map(|part| Ident { + id: NodeId::default(), + span: part.span, + name: part.name.clone(), + }) + .collect::>() + .into(), + ), _ => None, }; @@ -260,18 +244,6 @@ fn advanced(s: &ParserContext, from: u32) -> bool { s.peek().span.lo > from } -fn join(mut strings: impl Iterator>, sep: &str) -> String { - let mut string = String::new(); - if let Some(s) = strings.next() { - string.push_str(s.as_ref()); - } - for s in strings { - string.push_str(sep); - string.push_str(s.as_ref()); - } - string -} - fn map_rule_name(name: &'static str, error: Error) -> Error { Error(match error.0 { ErrorKind::Rule(_, found, span) => ErrorKind::Rule(name, found, span), diff --git a/compiler/qsc_parse/src/prim/tests.rs b/compiler/qsc_parse/src/prim/tests.rs index 04b664e81c..870daee91a 100644 --- a/compiler/qsc_parse/src/prim/tests.rs +++ b/compiler/qsc_parse/src/prim/tests.rs @@ -100,7 +100,9 @@ fn path_triple() { check( path, "Foo.Bar.Baz", - &expect![[r#"Path _id_ [0-11] (Ident _id_ [0-7] "Foo.Bar") (Ident _id_ [8-11] "Baz")"#]], + &expect![[ + r#"Path _id_ [0-11] ([Ident _id_ [0-3] "Foo", Ident _id_ [4-7] "Bar"]) (Ident _id_ [8-11] "Baz")"# + ]], ); } diff --git a/compiler/qsc_passes/src/common.rs b/compiler/qsc_passes/src/common.rs index d258babad0..8afaea8f30 100644 --- a/compiler/qsc_passes/src/common.rs +++ b/compiler/qsc_passes/src/common.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use qsc_data_structures::span::Span; +use qsc_data_structures::{namespaces::NamespaceId, span::Span}; use qsc_hir::{ assigner::Assigner, global::Table, @@ -81,7 +81,7 @@ impl IdentTemplate { pub(crate) fn create_gen_core_ref( core: &Table, - namespace: &str, + namespace: NamespaceId, name: &str, generics: Vec, span: Span, diff --git a/compiler/qsc_passes/src/invert_block.rs b/compiler/qsc_passes/src/invert_block.rs index f89cc06278..e11d5a0410 100644 --- a/compiler/qsc_passes/src/invert_block.rs +++ b/compiler/qsc_passes/src/invert_block.rs @@ -329,6 +329,9 @@ fn make_range_field(range_id: NodeId, field: PrimField) -> Expr { } fn make_array_index_range_reverse(core: &Table, arr_id: NodeId, arr_ty: &Ty) -> Expr { + let ns = core + .find_namespace(vec!["Microsoft".into(), "Quantum".into(), "Core".into()]) + .expect("prelude namespaces should exist"); let len = Box::new(Expr { id: NodeId::default(), span: Span::default(), @@ -336,7 +339,7 @@ fn make_array_index_range_reverse(core: &Table, arr_id: NodeId, arr_ty: &Ty) -> kind: ExprKind::Call( Box::new(create_gen_core_ref( core, - "Microsoft.Quantum.Core", + ns, "Length", vec![GenericArg::Ty(arr_ty.clone())], Span::default(), diff --git a/compiler/qsc_passes/src/loop_unification.rs b/compiler/qsc_passes/src/loop_unification.rs index 147bbb4e24..49f03f2478 100644 --- a/compiler/qsc_passes/src/loop_unification.rs +++ b/compiler/qsc_passes/src/loop_unification.rs @@ -32,7 +32,6 @@ impl LoopUni<'_> { span: Span, ) -> Expr { let cond_span = cond.span; - let continue_cond_id = self.gen_ident("continue_cond", Ty::Prim(Prim::Bool), cond_span); let continue_cond_init = continue_cond_id.gen_id_init( Mutability::Mutable, @@ -118,6 +117,7 @@ impl LoopUni<'_> { } } + #[allow(clippy::too_many_lines)] fn visit_for_array( &mut self, iter: Pat, @@ -133,9 +133,13 @@ impl LoopUni<'_> { let Ty::Array(item_ty) = &array_id.ty else { panic!("iterator should have array type"); }; + let ns = self + .core + .find_namespace(vec!["Microsoft".into(), "Quantum".into(), "Core".into()]) + .expect("prelude namespaces should exist"); let mut len_callee = create_gen_core_ref( self.core, - "Microsoft.Quantum.Core", + ns, "Length", vec![GenericArg::Ty((**item_ty).clone())], array_id.span, diff --git a/compiler/qsc_passes/src/replace_qubit_allocation.rs b/compiler/qsc_passes/src/replace_qubit_allocation.rs index 4f92984c2d..7c65a5d331 100644 --- a/compiler/qsc_passes/src/replace_qubit_allocation.rs +++ b/compiler/qsc_passes/src/replace_qubit_allocation.rs @@ -238,9 +238,13 @@ impl<'a> ReplaceQubitAllocation<'a> { } fn create_alloc_stmt(&mut self, ident: &IdentTemplate) -> Stmt { + let ns = self + .core + .find_namespace(vec!["QIR".into(), "Runtime".into()]) + .expect("prelude namespaces should exist"); let mut call_expr = create_gen_core_ref( self.core, - "QIR.Runtime", + ns, "__quantum__rt__qubit_allocate", Vec::new(), ident.span, @@ -250,21 +254,24 @@ impl<'a> ReplaceQubitAllocation<'a> { } fn create_array_alloc_stmt(&mut self, ident: &IdentTemplate, array_size: Expr) -> Stmt { - let mut call_expr = create_gen_core_ref( - self.core, - "QIR.Runtime", - "AllocateQubitArray", - Vec::new(), - ident.span, - ); + let ns = self + .core + .find_namespace(vec!["QIR".into(), "Runtime".into()]) + .expect("prelude namespaces should exist"); + let mut call_expr = + create_gen_core_ref(self.core, ns, "AllocateQubitArray", Vec::new(), ident.span); call_expr.id = self.assigner.next_node(); create_general_alloc_stmt(self.assigner, ident, call_expr, Some(array_size)) } fn create_dealloc_stmt(&mut self, ident: &IdentTemplate) -> Stmt { + let ns = self + .core + .find_namespace(vec!["QIR".into(), "Runtime".into()]) + .expect("prelude namespaces should exist"); let mut call_expr = create_gen_core_ref( self.core, - "QIR.Runtime", + ns, "__quantum__rt__qubit_release", Vec::new(), ident.span, @@ -274,13 +281,12 @@ impl<'a> ReplaceQubitAllocation<'a> { } fn create_array_dealloc_stmt(&mut self, ident: &IdentTemplate) -> Stmt { - let mut call_expr = create_gen_core_ref( - self.core, - "QIR.Runtime", - "ReleaseQubitArray", - Vec::new(), - ident.span, - ); + let ns = self + .core + .find_namespace(vec!["QIR".into(), "Runtime".into()]) + .expect("prelude namespaces should exist"); + let mut call_expr = + create_gen_core_ref(self.core, ns, "ReleaseQubitArray", Vec::new(), ident.span); call_expr.id = self.assigner.next_node(); create_general_dealloc_stmt(self.assigner, call_expr, ident) } @@ -412,11 +418,14 @@ fn create_qubit_global_alloc( qubit_init: QubitInit, ) -> StmtKind { fn qubit_alloc_expr(assigner: &mut Assigner, core: &Table, qubit_init: QubitInit) -> Expr { + let ns = core + .find_namespace(vec!["QIR".into(), "Runtime".into()]) + .expect("prelude namespaces should exist"); match qubit_init.kind { QubitInitKind::Array(mut expr) => { let mut call_expr = create_gen_core_ref( core, - "QIR.Runtime", + ns, "AllocateQubitArray", Vec::new(), qubit_init.span, @@ -432,7 +441,7 @@ fn create_qubit_global_alloc( QubitInitKind::Single => { let mut call_expr = create_gen_core_ref( core, - "QIR.Runtime", + ns, "__quantum__rt__qubit_allocate", Vec::new(), qubit_init.span, diff --git a/compiler/qsc_rca/src/overrider.rs b/compiler/qsc_rca/src/overrider.rs index a74dcb08f2..9a4cbc59e5 100644 --- a/compiler/qsc_rca/src/overrider.rs +++ b/compiler/qsc_rca/src/overrider.rs @@ -206,7 +206,7 @@ impl<'a> Visitor<'a> for Overrider<'a> { .items .iter() .filter_map(|(_, item)| match &item.kind { - ItemKind::Namespace(ident, items) => Some((ident.name.to_string(), items)), + ItemKind::Namespace(ident, items) => Some((ident.name().to_string(), items)), _ => None, }); for (namespace_ident, namespace_items) in namespaces { diff --git a/language_service/src/code_lens.rs b/language_service/src/code_lens.rs index 057b273f43..db142d1b16 100644 --- a/language_service/src/code_lens.rs +++ b/language_service/src/code_lens.rs @@ -42,7 +42,7 @@ pub(crate) fn get_code_lenses( .and_then(|parent_id| user_unit.package.items.get(parent_id)) .map(|parent| &parent.kind) { - let namespace = ns.name.to_string(); + let namespace = ns.name().to_string(); let range = into_range(position_encoding, decl.span, &user_unit.sources); let name = decl.name.name.clone(); diff --git a/language_service/src/compilation.rs b/language_service/src/compilation.rs index db2739ef4c..1406cc4255 100644 --- a/language_service/src/compilation.rs +++ b/language_service/src/compilation.rs @@ -12,10 +12,10 @@ use qsc::{ line_column::{Encoding, Position}, resolve, target::Profile, - CompileUnit, LanguageFeatures, PackageStore, PackageType, SourceMap, Span, + CompileUnit, LanguageFeatures, NamespaceId, PackageStore, PackageType, SourceMap, Span, }; use qsc_linter::LintConfig; -use std::sync::Arc; +use std::{rc::Rc, sync::Arc}; /// Represents an immutable compilation state that can be used /// to implement language service features. @@ -218,6 +218,17 @@ impl Compilation { self.user_package_id = new.user_package_id; self.errors = new.errors; } + + #[allow(dead_code)] + pub(crate) fn find_namespace_id(&self, ns: [&str; 3]) -> NamespaceId { + self.package_store + .get(self.user_package_id) + .expect("user package should exist") + .ast + .namespaces + .find_namespace(ns.into_iter().map(Rc::from).collect::>()) + .expect("namespace should exist") + } } impl Lookup for Compilation { diff --git a/language_service/src/completion.rs b/language_service/src/completion.rs index 25a9c0c6ec..6d5d2c7154 100644 --- a/language_service/src/completion.rs +++ b/language_service/src/completion.rs @@ -7,19 +7,21 @@ mod tests; use crate::compilation::{Compilation, CompilationKind}; use crate::protocol::{CompletionItem, CompletionItemKind, CompletionList, TextEdit}; use crate::qsc_utils::{into_range, span_contains}; + use qsc::ast::visit::{self, Visitor}; use qsc::display::{CodeDisplay, Lookup}; + use qsc::hir::{ItemKind, Package, PackageId, Visibility}; use qsc::line_column::{Encoding, Position, Range}; -use qsc::resolve::{Local, LocalKind}; +use qsc::{ + resolve::{Local, LocalKind}, + PRELUDE, +}; use rustc_hash::FxHashSet; use std::rc::Rc; -const PRELUDE: [&str; 3] = [ - "Microsoft.Quantum.Canon", - "Microsoft.Quantum.Core", - "Microsoft.Quantum.Intrinsic", -]; +type NamespaceName = Vec>; +type NamespaceAlias = Rc; pub(crate) fn get_completions( compilation: &Compilation, @@ -68,10 +70,13 @@ pub(crate) fn get_completions( None => String::new(), }; + let mut prelude_ns_ids: Vec<_> = PRELUDE + .into_iter() + .map(|ns| (ns.into_iter().map(Rc::from).collect(), None)) + .collect(); + // The PRELUDE namespaces are always implicitly opened. - context_finder - .opens - .extend(PRELUDE.into_iter().map(|ns| (Rc::from(ns), None))); + context_finder.opens.append(&mut prelude_ns_ids); let mut builder = CompletionListBuilder::new(); @@ -262,9 +267,9 @@ impl CompletionListBuilder { fn push_globals( &mut self, compilation: &Compilation, - opens: &[(Rc, Option>)], + opens: &[(NamespaceName, Option)], insert_open_range: Option, - current_namespace_name: &Option>, + current_namespace_name: &Option>>, indent: &String, ) { let core = &compilation @@ -381,12 +386,16 @@ impl CompletionListBuilder { self.current_sort_group += 1; } + /// Get all callables in a package fn get_callables<'a>( compilation: &'a Compilation, package_id: PackageId, - opens: &'a [(Rc, Option>)], + // name and alias + opens: &'a [(NamespaceName, Option)], + // The range at which to insert an open statement if one is needed insert_open_at: Option, - current_namespace_name: Option>, + // The name of the current namespace, if any -- + current_namespace_name: Option>>, indent: &'a String, ) -> impl Iterator + 'a { let package = &compilation @@ -403,7 +412,7 @@ impl CompletionListBuilder { if let Some(item_id) = i.parent { if let Some(parent) = package.items.get(item_id) { if let ItemKind::Namespace(namespace, _) = &parent.kind { - if namespace.name.starts_with("Microsoft.Quantum.Unstable") { + if namespace.starts_with_sequence(&["Microsoft", "Quantum", "Unstable"]) { return None; } // If the item's visibility is internal, the item may be ignored @@ -414,7 +423,7 @@ impl CompletionListBuilder { // ignore item if the user is not in the item's namespace match ¤t_namespace_name { Some(curr_ns) => { - if *curr_ns != namespace.name { + if *curr_ns != Into::>>::into(namespace) { return None; } } @@ -431,35 +440,35 @@ impl CompletionListBuilder { // Everything that starts with a __ goes last in the list let sort_group = u32::from(name.starts_with("__")); let mut additional_edits = vec![]; - let mut qualification: Option> = None; + let mut qualification: Option>> = None; match ¤t_namespace_name { - Some(curr_ns) if *curr_ns == namespace.name => {} + Some(curr_ns) + if *curr_ns == Into::>::into(namespace) => {} _ => { // open is an option of option of Rc // the first option tells if it found an open with the namespace name // the second, nested option tells if that open has an alias let open = opens.iter().find_map(|(name, alias)| { - if *name == namespace.name { + if *name == Into::>::into(namespace) { Some(alias) } else { None } }); qualification = match open { - Some(alias) => alias.clone(), + Some(alias) => alias.clone().map(|x| vec![x]), None => match insert_open_at { Some(start) => { additional_edits.push(TextEdit { new_text: format!( - "open {};{}", - namespace.name.clone(), - indent, + "open {};{indent}", + namespace.name() ), range: start, }); None } - None => Some(namespace.name.clone()), + None => Some(namespace.into()), }, } } @@ -472,7 +481,7 @@ impl CompletionListBuilder { }; let label = if let Some(qualification) = qualification { - format!("{qualification}.{name}") + format!("{}.{name}", qualification.join(".")) } else { name.to_owned() }; @@ -496,6 +505,7 @@ impl CompletionListBuilder { }) } + /// Get all callables in the core package fn get_core_callables<'a>( compilation: &'a Compilation, package: &'a Package, @@ -526,10 +536,10 @@ impl CompletionListBuilder { fn get_namespaces(package: &'_ Package) -> impl Iterator + '_ { package.items.values().filter_map(|i| match &i.kind { ItemKind::Namespace(namespace, _) - if !namespace.name.starts_with("Microsoft.Quantum.Unstable") => + if !namespace.starts_with_sequence(&["Microsoft", "Quantum", "Unstable"]) => { Some(CompletionItem::new( - namespace.name.to_string(), + namespace.name(), CompletionItemKind::Module, )) } @@ -538,6 +548,7 @@ impl CompletionListBuilder { } } +/// Convert a local into a completion item fn local_completion( candidate: &Local, compilation: &Compilation, @@ -600,9 +611,9 @@ fn local_completion( struct ContextFinder { offset: u32, context: Context, - opens: Vec<(Rc, Option>)>, + opens: Vec<(NamespaceName, Option)>, start_of_namespace: Option, - current_namespace_name: Option>, + current_namespace_name: Option>>, } #[derive(Debug, PartialEq)] @@ -617,7 +628,7 @@ enum Context { impl Visitor<'_> for ContextFinder { fn visit_namespace(&mut self, namespace: &'_ qsc::ast::Namespace) { if span_contains(namespace.span, self.offset) { - self.current_namespace_name = Some(namespace.name.name.clone()); + self.current_namespace_name = Some(namespace.name.clone().into()); self.context = Context::Namespace; self.opens = vec![]; self.start_of_namespace = None; @@ -631,10 +642,8 @@ impl Visitor<'_> for ContextFinder { } if let qsc::ast::ItemKind::Open(name, alias) = &*item.kind { - self.opens.push(( - name.name.clone(), - alias.as_ref().map(|alias| alias.name.clone()), - )); + self.opens + .push((name.into(), alias.as_ref().map(|alias| alias.name.clone()))); } if span_contains(item.span, self.offset) { diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index a4c62855b2..d02bb2ca6f 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -59,7 +59,7 @@ impl<'a> Handler<'a> for HoverGenerator<'a> { ) { let contents = display_callable( &context.current_item_doc, - &context.current_namespace, + &context.current_namespace.join("."), self.display.ast_callable_decl(decl), ); self.hover = Some(Hover { @@ -82,7 +82,7 @@ impl<'a> Handler<'a> for HoverGenerator<'a> { .map_or_else( || Rc::from(""), |parent| match &parent.kind { - qsc::hir::ItemKind::Namespace(namespace, _) => namespace.name.clone(), + qsc::hir::ItemKind::Namespace(namespace, _) => Rc::from(namespace.name()), _ => Rc::from(""), }, ); diff --git a/language_service/src/name_locator.rs b/language_service/src/name_locator.rs index d0d1676b01..e99bf35563 100644 --- a/language_service/src/name_locator.rs +++ b/language_service/src/name_locator.rs @@ -89,7 +89,7 @@ pub(crate) struct LocatorContext<'package> { pub(crate) current_callable: Option<&'package ast::CallableDecl>, pub(crate) lambda_params: Vec<&'package ast::Pat>, pub(crate) current_item_doc: Rc, - pub(crate) current_namespace: Rc, + pub(crate) current_namespace: Vec>, pub(crate) in_params: bool, pub(crate) in_lambda_params: bool, pub(crate) current_udt_id: Option<&'package hir::ItemId>, @@ -113,7 +113,7 @@ impl<'inner, 'package, T> Locator<'inner, 'package, T> { offset, compilation, context: LocatorContext { - current_namespace: Rc::from(""), + current_namespace: Vec::new(), current_callable: None, in_params: false, lambda_params: vec![], @@ -128,7 +128,7 @@ impl<'inner, 'package, T> Locator<'inner, 'package, T> { impl<'inner, 'package, T: Handler<'package>> Visitor<'package> for Locator<'inner, 'package, T> { fn visit_namespace(&mut self, namespace: &'package ast::Namespace) { if span_contains(namespace.span, self.offset) { - self.context.current_namespace = namespace.name.name.clone(); + self.context.current_namespace = namespace.name.clone().into(); walk_namespace(self, namespace); } } diff --git a/language_service/src/references.rs b/language_service/src/references.rs index 6ec3bb6df0..f2ae1dd9f6 100644 --- a/language_service/src/references.rs +++ b/language_service/src/references.rs @@ -188,7 +188,8 @@ impl<'a> ReferenceFinder<'a> { if self.include_declaration { let def_span = match &def.kind { hir::ItemKind::Callable(decl) => decl.name.span, - hir::ItemKind::Namespace(name, _) | hir::ItemKind::Ty(name, _) => name.span, + hir::ItemKind::Namespace(name, _) => name.span(), + hir::ItemKind::Ty(name, _) => name.span, }; locations.push( self.location(