diff --git a/compiler/qsc/benches/rca.rs b/compiler/qsc/benches/rca.rs index 311f67506f..3c9f8ada52 100644 --- a/compiler/qsc/benches/rca.rs +++ b/compiler/qsc/benches/rca.rs @@ -151,7 +151,7 @@ fn lower_hir_package_store(hir_package_store: &HirPackageStore) -> PackageStore for (id, unit) in hir_package_store { fir_store.insert( map_hir_package_to_fir(id), - Lowerer::new().lower_package(&unit.package), + Lowerer::new().lower_package(&unit.package, &fir_store), ); } fir_store diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs index bdad76328f..f6e72383ec 100644 --- a/compiler/qsc/src/interpret.rs +++ b/compiler/qsc/src/interpret.rs @@ -1,16 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -mod debug; - #[cfg(test)] -mod tests; - +mod circuit_tests; +mod debug; #[cfg(test)] mod debugger_tests; - #[cfg(test)] -mod circuit_tests; +mod package_tests; +#[cfg(test)] +mod tests; use std::rc::Rc; @@ -207,12 +206,10 @@ impl Interpreter { let mut fir_store = fir::PackageStore::new(); for (id, unit) in compiler.package_store() { - fir_store.insert( - map_hir_package_to_fir(id), - qsc_lowerer::Lowerer::new() - .with_debug(dbg) - .lower_package(&unit.package), - ); + let pkg = qsc_lowerer::Lowerer::new() + .with_debug(dbg) + .lower_package(&unit.package, &fir_store); + fir_store.insert(map_hir_package_to_fir(id), pkg); } let source_package_id = compiler.source_package_id(); @@ -265,10 +262,8 @@ impl Interpreter { let mut fir_store = fir::PackageStore::new(); for (id, unit) in compiler.package_store() { let mut lowerer = qsc_lowerer::Lowerer::new(); - fir_store.insert( - map_hir_package_to_fir(id), - lowerer.lower_package(&unit.package), - ); + let pkg = lowerer.lower_package(&unit.package, &fir_store); + fir_store.insert(map_hir_package_to_fir(id), pkg); } let source_package_id = compiler.source_package_id(); @@ -634,20 +629,27 @@ impl Interpreter { if self.capabilities != TargetCapabilityFlags::all() { return self.run_fir_passes(unit_addition); } - let fir_package = self.fir_store.get_mut(self.package); - self.lowerer - .lower_and_update_package(fir_package, &unit_addition.hir); + + self.lower_and_update_package(unit_addition); Ok((self.lowerer.take_exec_graph(), None)) } + fn lower_and_update_package(&mut self, unit: &qsc_frontend::incremental::Increment) { + { + let fir_package = self.fir_store.get_mut(self.package); + self.lowerer + .lower_and_update_package(fir_package, &unit.hir); + } + let fir_package: &Package = self.fir_store.get(self.package); + qsc_fir::validate::validate(fir_package, &self.fir_store); + } + fn run_fir_passes( &mut self, unit: &qsc_frontend::incremental::Increment, ) -> std::result::Result<(Vec, Option), Vec> { - let fir_package = self.fir_store.get_mut(self.package); - self.lowerer - .lower_and_update_package(fir_package, &unit.hir); + self.lower_and_update_package(unit); let cap_results = PassContext::run_fir_passes_on_fir(&self.fir_store, self.package, self.capabilities); @@ -860,7 +862,7 @@ impl Debugger { package, self.position_encoding, ); - collector.visit_package(package); + collector.visit_package(package, &self.interpreter.fir_store); let mut spans: Vec<_> = collector.statements.into_iter().collect(); // Sort by start position (line first, column next) diff --git a/compiler/qsc/src/interpret/package_tests.rs b/compiler/qsc/src/interpret/package_tests.rs new file mode 100644 index 0000000000..e4539015e8 --- /dev/null +++ b/compiler/qsc/src/interpret/package_tests.rs @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(clippy::needless_raw_string_hashes)] + +use crate::{interpret::Interpreter, packages::BuildableProgram}; +use indoc::indoc; +use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags}; +use qsc_eval::output::CursorReceiver; +use qsc_frontend::compile::SourceMap; +use qsc_passes::PackageType; +use qsc_project::{PackageGraphSources, PackageInfo}; +use rustc_hash::FxHashMap; + +#[test] +fn import_and_call_reexport() { + let pkg_graph: PackageGraphSources = PackageGraphSources { + root: PackageInfo { + sources: vec![( + "PackageB.qs".into(), + indoc! {" + import Foo.DependencyA.Foo; + function Main() : Unit { + Foo([1, 2]); + Foo.DependencyA.MagicFunction(); + }"} + .into(), + )], + language_features: LanguageFeatures::default(), + dependencies: [("Foo".into(), "PackageAKey".into())].into_iter().collect(), + package_type: None, + }, + packages: [( + "PackageAKey".into(), + PackageInfo { + sources: vec![( + "Foo.qs".into(), + r#" + namespace DependencyA { + function MagicFunction() : Unit { + Message("hello from dependency A!"); + } + export MagicFunction, Microsoft.Quantum.Core.Length as Foo; + } + "# + .into(), + )], + language_features: LanguageFeatures::default(), + dependencies: FxHashMap::default(), + package_type: None, + }, + )] + .into_iter() + .collect(), + }; + + // This builds all the dependencies + let buildable_program = BuildableProgram::new(TargetCapabilityFlags::all(), pkg_graph); + + assert!( + buildable_program.dependency_errors.is_empty(), + "dependencies should be built without errors" + ); + + let BuildableProgram { + store, + user_code, + user_code_dependencies, + .. + } = buildable_program; + + let user_code = SourceMap::new(user_code.sources, None); + + let mut interpreter = Interpreter::new( + user_code, + PackageType::Exe, + TargetCapabilityFlags::all(), + LanguageFeatures::default(), + store, + &user_code_dependencies, + ) + .expect("interpreter creation should succeed"); + + let mut cursor = std::io::Cursor::new(Vec::::new()); + let mut receiver = CursorReceiver::new(&mut cursor); + let res = interpreter.eval_entry(&mut receiver); + + assert!(res.is_ok(), "evaluation should succeed"); + + let output = String::from_utf8(cursor.into_inner()).expect("output should be valid utf-8"); + + assert_eq!(output, "hello from dependency A!\n"); +} + +#[test] +fn directly_call_reexport() { + let pkg_graph: PackageGraphSources = PackageGraphSources { + root: PackageInfo { + sources: vec![( + "PackageB.qs".into(), + indoc! {" + function Main() : Unit { + Foo.DependencyA.Foo([1, 2]); + Foo.DependencyA.MagicFunction(); + }"} + .into(), + )], + language_features: LanguageFeatures::default(), + dependencies: [("Foo".into(), "PackageAKey".into())].into_iter().collect(), + package_type: None, + }, + packages: [( + "PackageAKey".into(), + PackageInfo { + sources: vec![( + "Foo.qs".into(), + r#" + namespace DependencyA { + function MagicFunction() : Unit { + Message("hello from dependency A!"); + } + export MagicFunction, Microsoft.Quantum.Core.Length as Foo; + } + "# + .into(), + )], + language_features: LanguageFeatures::default(), + dependencies: FxHashMap::default(), + package_type: None, + }, + )] + .into_iter() + .collect(), + }; + + // This builds all the dependencies + let buildable_program = BuildableProgram::new(TargetCapabilityFlags::all(), pkg_graph); + + assert!( + buildable_program.dependency_errors.is_empty(), + "dependencies should be built without errors" + ); + + let BuildableProgram { + store, + user_code, + user_code_dependencies, + .. + } = buildable_program; + + let user_code = SourceMap::new(user_code.sources, None); + + let mut interpreter = Interpreter::new( + user_code, + PackageType::Exe, + TargetCapabilityFlags::all(), + LanguageFeatures::default(), + store, + &user_code_dependencies, + ) + .expect("interpreter creation should succeed"); + + let mut cursor = std::io::Cursor::new(Vec::::new()); + let mut receiver = CursorReceiver::new(&mut cursor); + let res = interpreter.eval_entry(&mut receiver); + + assert!(res.is_ok(), "evaluation should succeed"); + + let output = String::from_utf8(cursor.into_inner()).expect("output should be valid utf-8"); + + assert_eq!(output, "hello from dependency A!\n"); +} diff --git a/compiler/qsc_ast/src/ast.rs b/compiler/qsc_ast/src/ast.rs index 160908083a..71582b0882 100644 --- a/compiler/qsc_ast/src/ast.rs +++ b/compiler/qsc_ast/src/ast.rs @@ -23,7 +23,7 @@ fn set_indentation<'a, 'b>( 0 => indent.with_str(""), 1 => indent.with_str(" "), 2 => indent.with_str(" "), - _ => unimplemented!("intentation level not supported"), + _ => unimplemented!("indentation level not supported"), } } diff --git a/compiler/qsc_codegen/src/qir.rs b/compiler/qsc_codegen/src/qir.rs index 568e7e733c..45f7c04f82 100644 --- a/compiler/qsc_codegen/src/qir.rs +++ b/compiler/qsc_codegen/src/qir.rs @@ -20,7 +20,7 @@ use qsc_rir::{ fn lower_store(package_store: &qsc_frontend::compile::PackageStore) -> qsc_fir::fir::PackageStore { let mut fir_store = qsc_fir::fir::PackageStore::new(); for (id, unit) in package_store { - let package = qsc_lowerer::Lowerer::new().lower_package(&unit.package); + let package = qsc_lowerer::Lowerer::new().lower_package(&unit.package, &fir_store); fir_store.insert(map_hir_package_to_fir(id), package); } fir_store diff --git a/compiler/qsc_data_structures/src/namespaces.rs b/compiler/qsc_data_structures/src/namespaces.rs index 1a1ca33b1d..2f3fc8bc79 100644 --- a/compiler/qsc_data_structures/src/namespaces.rs +++ b/compiler/qsc_data_structures/src/namespaces.rs @@ -5,7 +5,7 @@ mod tests; use rustc_hash::{FxHashMap, FxHashSet}; -use std::{cell::RefCell, fmt::Display, iter::Peekable, ops::Deref, rc::Rc}; +use std::{cell::RefCell, collections::BTreeMap, fmt::Display, iter::Peekable, ops::Deref, rc::Rc}; pub const PRELUDE: [[&str; 3]; 4] = [ ["Microsoft", "Quantum", "Canon"], @@ -15,7 +15,7 @@ pub const PRELUDE: [[&str; 3]; 4] = [ ]; /// An ID that corresponds to a namespace in the global scope. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Default)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Default, PartialOrd, Ord)] pub struct NamespaceId(usize); impl NamespaceId { /// Create a new namespace ID. @@ -196,7 +196,75 @@ impl NamespaceTreeRoot { .borrow_mut() .insert_or_find_namespace(ns.into_iter().peekable(), &mut self.assigner); - id.expect("empty name should not be passed into namespace insertion") + id.expect("empty name checked for above") + } + + pub fn insert_or_find_namespace_from_root_with_id( + &mut self, + mut ns: Vec>, + root: NamespaceId, + base_id: NamespaceId, + ) { + if ns.is_empty() { + return; + } + let (_root_name, root_contents) = self.find_namespace_by_id(&root); + // split `ns` into [0..len - 1] and [len - 1] + let suffix = ns.split_off(ns.len() - 1)[0].clone(); + let prefix = ns; + + // if the prefix is empty, we are inserting into the root + if prefix.is_empty() { + self.insert_with_id(Some(root), base_id, &suffix); + } else { + let prefix_id = root_contents + .borrow_mut() + .insert_or_find_namespace(prefix.into_iter().peekable(), &mut self.assigner) + .expect("empty name checked for above"); + + self.insert_with_id(Some(prefix_id), base_id, &suffix); + } + } + + /// Each item in this iterator is the same, single namespace. The reason there are multiple paths for it, + /// each represented by a `Vec>`, is because there may be multiple paths to the same + /// namespace, through aliasing or re-exports. + pub fn iter(&self) -> std::collections::btree_map::IntoValues>>> { + let mut stack = vec![(vec![], self.tree.clone())]; + let mut result: Vec<(NamespaceId, Vec>)> = vec![]; + while let Some((names, node)) = stack.pop() { + result.push((node.borrow().id, names.clone())); + for (name, child) in node.borrow().children() { + let mut new_names = names.clone(); + new_names.push(name.clone()); + stack.push((new_names, child.clone())); + } + if node.borrow().children().is_empty() { + result.push((node.borrow().id, names)); + } + } + + // flatten the result into a list of paths + + // use a btree map here instead of a hash map for deterministic iteration -- + // while it shouldn't be consequential, any nondeterminism in a compiler makes + // things more difficult to track down then they go wrong. + let mut flattened_result = BTreeMap::default(); + for (id, names) in result { + let entry = flattened_result.entry(id).or_insert_with(Vec::new); + entry.push(names); + } + + flattened_result.into_values() + } +} + +impl IntoIterator for &NamespaceTreeRoot { + type Item = Vec>>; + type IntoIter = std::collections::btree_map::IntoValues>>>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() } } diff --git a/compiler/qsc_data_structures/src/namespaces/tests.rs b/compiler/qsc_data_structures/src/namespaces/tests.rs index 859cf802cb..8ecad1562b 100644 --- a/compiler/qsc_data_structures/src/namespaces/tests.rs +++ b/compiler/qsc_data_structures/src/namespaces/tests.rs @@ -329,3 +329,190 @@ fn test_get_namespace_id() { "#]] .assert_debug_eq(&result_buf); } + +#[allow(clippy::too_many_lines)] +#[test] +fn test_tree_iter() { + let mut root = NamespaceTreeRoot::default(); + for i in 0..3 { + for j in 'a'..'d' { + let _ = root.insert_or_find_namespace( + vec![Rc::from(format!("ns{i}")), Rc::from(format!("ns{j}"))].into_iter(), + ); + } + } + + let result = root.iter().collect::>(); + expect![[r#" + [ + [ + [], + ], + [ + [ + "Microsoft", + ], + ], + [ + [ + "Microsoft", + "Quantum", + ], + ], + [ + [ + "Microsoft", + "Quantum", + "Canon", + ], + [ + "Microsoft", + "Quantum", + "Canon", + ], + ], + [ + [ + "Microsoft", + "Quantum", + "Core", + ], + [ + "Microsoft", + "Quantum", + "Core", + ], + ], + [ + [ + "Microsoft", + "Quantum", + "Intrinsic", + ], + [ + "Microsoft", + "Quantum", + "Intrinsic", + ], + ], + [ + [ + "Microsoft", + "Quantum", + "Measurement", + ], + [ + "Microsoft", + "Quantum", + "Measurement", + ], + ], + [ + [ + "ns0", + ], + ], + [ + [ + "ns0", + "nsa", + ], + [ + "ns0", + "nsa", + ], + ], + [ + [ + "ns0", + "nsb", + ], + [ + "ns0", + "nsb", + ], + ], + [ + [ + "ns0", + "nsc", + ], + [ + "ns0", + "nsc", + ], + ], + [ + [ + "ns1", + ], + ], + [ + [ + "ns1", + "nsa", + ], + [ + "ns1", + "nsa", + ], + ], + [ + [ + "ns1", + "nsb", + ], + [ + "ns1", + "nsb", + ], + ], + [ + [ + "ns1", + "nsc", + ], + [ + "ns1", + "nsc", + ], + ], + [ + [ + "ns2", + ], + ], + [ + [ + "ns2", + "nsa", + ], + [ + "ns2", + "nsa", + ], + ], + [ + [ + "ns2", + "nsb", + ], + [ + "ns2", + "nsb", + ], + ], + [ + [ + "ns2", + "nsc", + ], + [ + "ns2", + "nsc", + ], + ], + ] + "#]] + .assert_debug_eq(&result); +} diff --git a/compiler/qsc_doc_gen/src/generate_docs.rs b/compiler/qsc_doc_gen/src/generate_docs.rs index 8b0c4fd199..f8f1d84f53 100644 --- a/compiler/qsc_doc_gen/src/generate_docs.rs +++ b/compiler/qsc_doc_gen/src/generate_docs.rs @@ -255,6 +255,7 @@ impl Display for Metadata { MetadataKind::Function => "function", MetadataKind::Operation => "operation", MetadataKind::Udt => "udt", + MetadataKind::Export => "export", }; write!( f, @@ -277,6 +278,7 @@ enum MetadataKind { Function, Operation, Udt, + Export, } fn get_metadata(ns: Rc, item: &Item, display: &CodeDisplay) -> Option { @@ -295,6 +297,12 @@ fn get_metadata(ns: Rc, item: &Item, display: &CodeDisplay) -> Option None, + ItemKind::Export(name, _) => Some(( + name.name.clone(), + // If we want to show docs for exports, we could do that here. + String::new(), + MetadataKind::Export, + )), }?; let summary = parse_doc_for_summary(&item.doc) @@ -307,6 +315,7 @@ fn get_metadata(ns: Rc, item: &Item, display: &CodeDisplay) -> Option format!("{name} function"), MetadataKind::Operation => format!("{name} operation"), MetadataKind::Udt => format!("{name} user defined type"), + MetadataKind::Export => format!("{name} exported item"), }, topic: "managed-reference".to_string(), kind, diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs index fa241b4700..4e8dda193a 100644 --- a/compiler/qsc_eval/src/intrinsic/tests.rs +++ b/compiler/qsc_eval/src/intrinsic/tests.rs @@ -149,13 +149,14 @@ impl Backend for CustomSim { fn check_intrinsic(file: &str, expr: &str, out: &mut impl Receiver) -> Result { let mut core = compile::core(); run_core_passes(&mut core); - let core_fir = qsc_lowerer::Lowerer::new().lower_package(&core.package); + let fir_store = fir::PackageStore::new(); + let core_fir = qsc_lowerer::Lowerer::new().lower_package(&core.package, &fir_store); let mut store = PackageStore::new(core); let mut std = compile::std(&store, TargetCapabilityFlags::all()); assert!(std.errors.is_empty()); assert!(run_default_passes(store.core(), &mut std, PackageType::Lib).is_empty()); - let std_fir = qsc_lowerer::Lowerer::new().lower_package(&std.package); + let std_fir = qsc_lowerer::Lowerer::new().lower_package(&std.package, &fir_store); let std_id = store.insert(std); let sources = SourceMap::new([("test".into(), file.into())], Some(expr.into())); @@ -168,7 +169,7 @@ fn check_intrinsic(file: &str, expr: &str, out: &mut impl Receiver) -> Result Some(Global::Callable(callable)), ItemKind::Namespace(..) => None, ItemKind::Ty(..) => Some(Global::Udt), + ItemKind::Export(_name, _id) => None, } } @@ -707,6 +708,8 @@ pub enum ItemKind { Namespace(Ident, Vec), /// A `newtype` declaration. Ty(Ident, Udt), + /// An export referring to another item + Export(Ident, ItemId), } impl Display for ItemKind { @@ -727,6 +730,7 @@ impl Display for ItemKind { } } ItemKind::Ty(name, udt) => write!(f, "Type ({name}): {udt}"), + ItemKind::Export(name, item) => write!(f, "Export ({name}): {item}"), } } } diff --git a/compiler/qsc_fir/src/mut_visit.rs b/compiler/qsc_fir/src/mut_visit.rs index e2785264b6..46b91deb4a 100644 --- a/compiler/qsc_fir/src/mut_visit.rs +++ b/compiler/qsc_fir/src/mut_visit.rs @@ -64,7 +64,9 @@ 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, _) | ItemKind::Ty(name, _) | ItemKind::Export(name, _) => { + vis.visit_ident(name); + } }; } diff --git a/compiler/qsc_fir/src/validate.rs b/compiler/qsc_fir/src/validate.rs index cacc77392b..32254eb2b7 100644 --- a/compiler/qsc_fir/src/validate.rs +++ b/compiler/qsc_fir/src/validate.rs @@ -10,16 +10,16 @@ pub struct Validator<'a> { pub package: &'a Package, } -pub fn validate(package: &Package) { +pub fn validate(package: &Package, store: &crate::fir::PackageStore) { let mut v = Validator { package }; - v.validate(); + v.validate(store); } /// Validates that the FIR is well-formed. /// Running `validate` will validate the entire package. impl Validator<'_> { - pub fn validate(&mut self) { - self.visit_package(self.package); + pub fn validate(&mut self, store: &crate::fir::PackageStore) { + self.visit_package(self.package, store); } } diff --git a/compiler/qsc_fir/src/visit.rs b/compiler/qsc_fir/src/visit.rs index 450238202d..0bc86051ea 100644 --- a/compiler/qsc_fir/src/visit.rs +++ b/compiler/qsc_fir/src/visit.rs @@ -8,7 +8,7 @@ use crate::fir::{ }; pub trait Visitor<'a>: Sized { - fn visit_package(&mut self, package: &'a Package) { + fn visit_package(&mut self, package: &'a Package, _: &crate::fir::PackageStore) { walk_package(self, package); } @@ -65,6 +65,9 @@ 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::Export(name, _) => { + vis.visit_ident(name); + } }; } diff --git a/compiler/qsc_frontend/src/compile.rs b/compiler/qsc_frontend/src/compile.rs index 6e1d5588f4..30a4f4339e 100644 --- a/compiler/qsc_frontend/src/compile.rs +++ b/compiler/qsc_frontend/src/compile.rs @@ -380,7 +380,12 @@ pub fn compile_ast( 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 ResolveResult { + names, + locals, + errors: name_errors, + namespaces, + } = resolve_all( store, dependencies, &mut hir_assigner, @@ -391,7 +396,7 @@ pub fn compile_ast( let mut lowerer = Lowerer::new(); let package = lowerer .with(&mut hir_assigner, &names, &tys) - .lower_package(&ast_package); + .lower_package(&ast_package, namespaces); HirValidator::default().visit_package(&package); let lower_errors = lowerer.drain_errors(); @@ -510,16 +515,23 @@ fn parse_all( (package, errors) } +pub(crate) struct ResolveResult { + pub names: Names, + pub locals: Locals, + pub namespaces: qsc_data_structures::namespaces::NamespaceTreeRoot, + pub errors: Vec, +} + fn resolve_all( store: &PackageStore, dependencies: &Dependencies, assigner: &mut HirAssigner, package: &ast::Package, mut dropped_names: Vec, -) -> (Names, Locals, Vec) { +) -> ResolveResult { let mut globals = resolve::GlobalTable::new(); if let Some(unit) = store.get(PackageId::CORE) { - globals.add_external_package(PackageId::CORE, &unit.package, &None); + globals.add_external_package(PackageId::CORE, &unit.package, store, &None); dropped_names.extend(unit.dropped_names.iter().cloned()); } @@ -527,7 +539,7 @@ fn resolve_all( let unit = store .get(*id) .expect("dependency should be in package store before compilation"); - globals.add_external_package(*id, &unit.package, alias); + globals.add_external_package(*id, &unit.package, store, alias); dropped_names.extend(unit.dropped_names.iter().cloned()); } @@ -540,9 +552,15 @@ fn resolve_all( // resolve all symbols resolver.with(assigner).visit_package(package); - let (names, locals, mut resolver_errors, _namespaces) = resolver.into_result(); + let (names, locals, mut resolver_errors, namespaces) = resolver.into_result(); errors.append(&mut resolver_errors); - (names, locals, errors) + + ResolveResult { + names, + locals, + namespaces, + errors, + } } fn typeck_all( @@ -553,7 +571,7 @@ fn typeck_all( ) -> (typeck::Table, Vec) { let mut globals = typeck::GlobalTable::new(); if let Some(unit) = store.get(PackageId::CORE) { - globals.add_external_package(PackageId::CORE, &unit.package); + globals.add_external_package(PackageId::CORE, &unit.package, store); } for (id, _alias) in dependencies { @@ -564,7 +582,7 @@ fn typeck_all( // typechecker doesn't do any name resolution -- it only operates on item ids. // because of this, the typechecker doesn't actually need to care about visibility // or the names of items at all. - globals.add_external_package(*id, &unit.package); + globals.add_external_package(*id, &unit.package, store); } let mut checker = Checker::new(globals); diff --git a/compiler/qsc_frontend/src/compile/tests.rs b/compiler/qsc_frontend/src/compile/tests.rs index ad6c9c7c88..85df730179 100644 --- a/compiler/qsc_frontend/src/compile/tests.rs +++ b/compiler/qsc_frontend/src/compile/tests.rs @@ -973,17 +973,17 @@ fn unimplemented_call_from_dependency_produces_error() { ); expect![[r#" [ - Error( - Resolve( - Unimplemented( - "Bar", - Span { - lo: 69, - hi: 72, + Error( + Resolve( + Unimplemented( + "Bar", + Span { + lo: 69, + hi: 72, }, - ), - ), - ), + ), + ), + ), ] "#]] .assert_debug_eq(&unit.errors); @@ -1471,180 +1471,3 @@ fn test_longest_common_prefix_only_root_common() { fn test_longest_common_prefix_only_root_common_no_leading() { expect![""].assert_eq(longest_common_prefix(&["a/b", "b/c"])); } - -#[test] -fn multiple_packages_reference_exports() { - let mut store = PackageStore::new(super::core()); - - let package_a = SourceMap::new( - [( - "PackageA.qs".into(), - indoc! {" - function FunctionA() : Int { - 1 - } - export FunctionA; - "} - .into(), - )], - None, - ); - - let package_a = compile( - &store, - &[], - package_a, - TargetCapabilityFlags::all(), - LanguageFeatures::default(), - ); - assert!(package_a.errors.is_empty(), "{:#?}", package_a.errors); - - let package_b = SourceMap::new( - [( - "PackageB".into(), - indoc! {" - function FunctionB() : Int { - 1 - } - export FunctionB; - "} - .into(), - )], - None, - ); - - let package_b = compile( - &store, - &[], - package_b, - TargetCapabilityFlags::all(), - LanguageFeatures::default(), - ); - - assert!(package_b.errors.is_empty(), "{:#?}", package_b.errors); - - let package_a = store.insert(package_a); - let package_b = store.insert(package_b); - - let user_code = SourceMap::new( - [( - "UserCode".into(), - indoc! {" - import A.PackageA.FunctionA; - import B.PackageB.FunctionB; - @EntryPoint() - function Main() : Unit { - FunctionA(); - FunctionB(); - } - "} - .into(), - )], - None, - ); - - let user_code = compile( - &store, - &[ - (package_a, Some(Arc::from("A"))), - (package_b, Some(Arc::from("B"))), - ], - user_code, - TargetCapabilityFlags::all(), - LanguageFeatures::default(), - ); - - assert!(user_code.errors.is_empty(), "{:#?}", user_code.errors); -} - -#[test] -#[allow(clippy::too_many_lines)] -fn multiple_packages_disallow_unexported_imports() { - let mut store = PackageStore::new(super::core()); - - let package_a = SourceMap::new( - [( - "PackageA.qs".into(), - indoc! {" - function FunctionA() : Int { - 1 - } - "} - .into(), - )], - None, - ); - - let package_a = compile( - &store, - &[], - package_a, - TargetCapabilityFlags::all(), - LanguageFeatures::default(), - ); - assert!(package_a.errors.is_empty(), "{:#?}", package_a.errors); - - let package_a = store.insert(package_a); - - let user_code = SourceMap::new( - [( - "UserCode".into(), - indoc! {" - import A.PackageA.FunctionA; - @EntryPoint() - function Main() : Unit { - FunctionA(); - } - "} - .into(), - )], - None, - ); - - let user_code = compile( - &store, - &[(package_a, Some(Arc::from("A")))], - user_code, - TargetCapabilityFlags::all(), - LanguageFeatures::default(), - ); - - expect![[r#" - [ - Error( - Resolve( - NotFound( - "A.PackageA.FunctionA", - Span { - lo: 7, - hi: 27, - }, - ), - ), - ), - Error( - Resolve( - NotFound( - "FunctionA", - Span { - lo: 71, - hi: 80, - }, - ), - ), - ), - Error( - Type( - Error( - AmbiguousTy( - Span { - lo: 71, - hi: 82, - }, - ), - ), - ), - ), - ]"#]] - .assert_eq(&format!("{:#?}", user_code.errors)); -} diff --git a/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs b/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs index c7eaa32516..c55f4ab935 100644 --- a/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs +++ b/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs @@ -6,6 +6,7 @@ use crate::compile::TargetCapabilityFlags; use crate::compile::core; use expect_test::expect; use expect_test::Expect; +use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; use qsc_hir::hir::PackageId; @@ -146,6 +147,304 @@ fn namespaces_named_main_treated_as_root() { ]); } +#[test] +fn multiple_packages_reference_exports() { + multiple_package_check(vec![ + ( + "PackageA", + indoc! {" + operation Foo(x: Int, y: Bool) : Int { + x + } + export Foo; + "}, + ), + ( + "PackageB", + indoc! {" + import PackageA.PackageA.Foo; + export Foo; + "}, + ), + ( + "PackageC", + indoc! {" + import PackageB.PackageB.Foo; + @EntryPoint() + operation Main() : Unit { + Foo(10, true); + } + "}, + ), + ]); +} + +#[test] +#[allow(clippy::too_many_lines)] +fn multiple_packages_disallow_unexported_imports() { + multiple_package_check_expect_err( + vec![ + ( + "PackageA", + indoc! {" + function FunctionA() : Int { + 1 + } + "}, + ), + ( + "PackageB", + indoc! {" + import PackageA.PackageA.FunctionA; + @EntryPoint() + function Main() : Unit { + FunctionA(); + } + "}, + ), + ], + &expect![[r#" + [ + Error( + Resolve( + NotFound( + "PackageA.PackageA.FunctionA", + Span { + lo: 7, + hi: 34, + }, + ), + ), + ), + Error( + Resolve( + NotFound( + "FunctionA", + Span { + lo: 78, + hi: 87, + }, + ), + ), + ), + Error( + Type( + Error( + AmbiguousTy( + Span { + lo: 78, + hi: 89, + }, + ), + ), + ), + ), + ]"#]], + ); +} + +#[test] +fn reexport() { + multiple_package_check(vec![ + ( + "PackageA", + indoc! {" + export Microsoft.Quantum.Core.Length as Foo; + "}, + ), + ( + "PackageB", + indoc! {" + + import PackageA.PackageA.Foo; + @EntryPoint() + function Main() : Unit { + use qs = Qubit[2]; + let len = Foo(qs); + } + "}, + ), + ]); +} + +#[test] +fn reexport_export_has_alias() { + multiple_package_check(vec![ + ( + "PackageA", + indoc! {" + operation Foo(x: Int, y: Bool) : Int { + x + } + export Foo as Bar; + "}, + ), + ( + "PackageB", + indoc! {" + import PackageA.PackageA.Bar; + "}, + ), + ]); +} + +#[test] +fn reexport_import_has_alias() { + multiple_package_check(vec![ + ( + "PackageA", + "operation Foo(x: Int, y: Bool) : Int { + x + } + export Foo; + ", + ), + ( + "PackageB", + " + import PackageA.PackageA.Foo as Bar; + + export Bar; + ", + ), + ]); +} + +#[test] +fn reexport_reexport_has_alias() { + multiple_package_check(vec![ + ( + "PackageA", + " + operation Foo(x: Int, y: Bool) : Int { + x + } + export Foo; + ", + ), + ( + "PackageB", + " + import PackageA.PackageA.Foo; + export Foo as Bar; + ", + ), + ( + "PackageC", + " + import PackageB.PackageB.Bar; + @EntryPoint() + operation Main() : Unit { + Bar(10, true); + } + ", + ), + ]); +} + +#[test] +fn reexport_callable_combined_aliases() { + multiple_package_check(vec![ + ( + "PackageA", + " + operation Foo(x: Int, y: Bool) : Int { + x + } + export Foo; + ", + ), + ( + "PackageB", + " + import PackageA.PackageA.Foo; + import PackageA.PackageA.Foo as Foo2; + export Foo, Foo as Bar, Foo2, Foo2 as Bar2; + ", + ), + ( + "PackageC", + " + import PackageB.PackageB.Foo, PackageB.PackageB.Bar, PackageB.PackageB.Foo2, PackageB.PackageB.Bar2; + @EntryPoint() + operation Main() : Unit { + Foo(10, true); + Foo2(10, true); + Bar(10, true); + Bar2(10, true); + } + ", + ), + ]); +} + +#[test] +fn direct_reexport() { + multiple_package_check(vec![ + ( + "A", + "operation Foo(x: Int, y: Bool) : Int { + x + } + export Foo as Bar;", + ), + ("B", "export A.A.Bar as Baz;"), + ( + "C", + "import B.B.Baz as Quux; + @EntryPoint() + operation Main() : Unit { + Quux(10, true); + }", + ), + ]); +} + +#[test] +fn reexports_still_type_check() { + multiple_package_check_expect_err( + vec![ + ( + "A", + "operation Foo(x: Int, y: Bool) : Int { + x + } + export Foo as Bar;", + ), + ( + "B", + " + export A.A.Bar as Baz;", + ), + ( + "C", + "import B.B.Baz as Quux; + @EntryPoint() + operation Main() : Unit { + Quux(10, 10); + }", + ), + ], + &expect![[r#" + [ + Error( + Type( + Error( + TyMismatch( + "Bool", + "Int", + Span { + lo: 128, + hi: 140, + }, + ), + ), + ), + ), + ]"#]], + ); +} + #[test] fn namespaces_named_lowercase_main_not_treated_as_root() { multiple_package_check(vec![ @@ -167,3 +466,46 @@ fn namespaces_named_lowercase_main_not_treated_as_root() { ), ]); } + +#[test] +fn aliased_export_via_aliased_import() { + multiple_package_check(vec![ + ( + "MyGithubLibrary", + r#" + namespace TestPackage { + + import Subpackage.Subpackage.Hello as SubHello; + + export HelloFromGithub; + export SubHello; + + /// This is a Doc String! + function HelloFromGithub() : Unit { + SubHello(); + } + } + + namespace Subpackage.Subpackage { + function Hello() : Unit {} + export Hello; + } + + "#, + ), + ( + "UserCode", + r#" + import MyGithubLibrary.TestPackage.SubHello; + import MyGithubLibrary.TestPackage.HelloFromGithub; + import MyGithubLibrary.Subpackage.Subpackage as P; + + function Main() : Unit { + HelloFromGithub(); + SubHello(); + P.Hello(); + } + "#, + ), + ]); +} diff --git a/compiler/qsc_frontend/src/incremental.rs b/compiler/qsc_frontend/src/incremental.rs index ffef95ad4f..4935face60 100644 --- a/compiler/qsc_frontend/src/incremental.rs +++ b/compiler/qsc_frontend/src/incremental.rs @@ -70,8 +70,8 @@ impl Compiler { let mut typeck_globals = typeck::GlobalTable::new(); let mut dropped_names = Vec::new(); if let Some(unit) = store.get(PackageId::CORE) { - resolve_globals.add_external_package(PackageId::CORE, &unit.package, &None); - typeck_globals.add_external_package(PackageId::CORE, &unit.package); + resolve_globals.add_external_package(PackageId::CORE, &unit.package, store, &None); + typeck_globals.add_external_package(PackageId::CORE, &unit.package, store); dropped_names.extend(unit.dropped_names.iter().cloned()); } @@ -79,8 +79,8 @@ impl Compiler { let unit = store .get(*id) .expect("dependency should be added to package store before compilation"); - resolve_globals.add_external_package(*id, &unit.package, alias); - typeck_globals.add_external_package(*id, &unit.package); + resolve_globals.add_external_package(*id, &unit.package, store, alias); + typeck_globals.add_external_package(*id, &unit.package, store); dropped_names.extend(unit.dropped_names.iter().cloned()); } @@ -269,7 +269,13 @@ impl Compiler { self.checker.check_package(self.resolver.names(), ast); self.checker.solve(self.resolver.names()); - let package = self.lower(&mut unit.assigner, &*ast); + let package = self.lower( + &mut unit.assigner, + &*ast, + // not an ideal clone, but it is once per fragment, and the namespace tree is + // relatively lightweight + self.resolver.namespaces().clone(), + ); let errors = self .resolver @@ -376,10 +382,15 @@ impl Compiler { (package, with_source(errors, sources, offset)) } - fn lower(&mut self, hir_assigner: &mut HirAssigner, package: &ast::Package) -> hir::Package { + fn lower( + &mut self, + hir_assigner: &mut HirAssigner, + package: &ast::Package, + namespaces: qsc_data_structures::namespaces::NamespaceTreeRoot, + ) -> hir::Package { self.lowerer .with(hir_assigner, self.resolver.names(), self.checker.table()) - .lower_package(package) + .lower_package(package, namespaces) } } diff --git a/compiler/qsc_frontend/src/incremental/tests.rs b/compiler/qsc_frontend/src/incremental/tests.rs index 8332e9df18..fe2fe642d3 100644 --- a/compiler/qsc_frontend/src/incremental/tests.rs +++ b/compiler/qsc_frontend/src/incremental/tests.rs @@ -3,14 +3,14 @@ use super::{Compiler, Increment}; use crate::{ - compile::{self, CompileUnit, PackageStore}, + compile::{self, CompileUnit, PackageStore, SourceMap}, incremental::Error, }; use expect_test::{expect, Expect}; use indoc::indoc; use miette::Diagnostic; use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags}; -use std::fmt::Write; +use std::{fmt::Write, sync::Arc}; #[allow(clippy::too_many_lines)] #[test] @@ -273,8 +273,8 @@ fn conditional_compilation_not_available() { #[test] fn errors_across_multiple_lines() { - let mut store = PackageStore::new(crate::compile::core()); - let std_id = store.insert(crate::compile::std(&store, TargetCapabilityFlags::all())); + let mut store = PackageStore::new(compile::core()); + let std_id = store.insert(compile::std(&store, TargetCapabilityFlags::all())); let mut compiler = Compiler::new( &store, &[(std_id, None)], @@ -468,7 +468,213 @@ fn continue_after_lower_error() { ] "#]].assert_debug_eq(&errors); } +#[test] +fn import_foo() { + multi_package_test( + vec![( + "PackageA.qs", + indoc! {" + operation Foo(x: Int, y: Bool) : Int { + x + } + export Foo; + "}, + )], + vec![( + "PackageB.qs", + indoc! {" + import A.PackageA.Foo; + "}, + )], + &[("A", "PackageA")], + "", + ); +} + +#[test] +fn import_foo_with_alias() { + multi_package_test( + vec![( + "PackageA.qs", + indoc! {" + operation Foo(x: Int, y: Bool) : Int { + x + } + export Foo; + "}, + )], + vec![( + "PackageB.qs", + indoc! {" + import A.PackageA.Foo as Foo2; + "}, + )], + &[("A", "PackageA")], + "", + ); +} + +#[test] +fn export_foo_with_alias() { + multi_package_test( + vec![( + "PackageA.qs", + indoc! {" + operation Foo(x: Int, y: Bool) : Int { + x + } + export Foo; + "}, + )], + vec![( + "PackageB.qs", + indoc! {" + import A.PackageA.Foo; + export Foo as Bar; + "}, + )], + &[("A", "PackageA")], + "", + ); +} + +#[test] +fn combined_import_export() { + multi_package_test( + vec![( + "PackageA.qs", + indoc! {" + operation Foo(x: Int, y: Bool) : Int { + x + } + export Foo; + "}, + )], + vec![( + "PackageB.qs", + indoc! {" + import A.PackageA.Foo; + import A.PackageA.Foo as Foo2; + export Foo, Foo as Bar, Foo2, Foo2 as Bar2; + "}, + )], + &[("A", "PackageA")], + indoc! {" + import B.PackageB.Foo, B.PackageB.Bar, B.PackageB.Foo2, B.PackageB.Bar2; + @EntryPoint() + function Main() : Unit { + Foo(10, true); + Foo2(10, true); + Bar(10, true); + Bar2(10, true); + } + "}, + ); +} + +#[test] +fn reexport_operation_from_a_dependency() { + multi_package_test( + vec![( + "PackageA.qs", + indoc! {" + operation Foo(x: Int, y: Bool) : Int { + x + } + export Foo; + "}, + )], + vec![( + "PackageB.qs", + indoc! {" + import A.PackageA.Foo; + export Foo as Bar; + "}, + )], + &[("A", "PackageA")], + indoc! {" + import B.PackageB.Bar; + @EntryPoint() + function Main() : Unit { + Bar(10, true); + } + "}, + ); +} + +fn multi_package_test( + packages: Vec<(&str, &str)>, + dependencies: Vec<(&str, &str)>, + imports: &[(&str, &str)], + user_code: &str, +) { + let mut store = PackageStore::new(compile::core()); + + let packages = packages + .into_iter() + .map(|(name, code)| { + let source_map = SourceMap::new([(name.into(), code.into())], None); + let compiled_package = compile::compile( + &store, + &[], + source_map, + TargetCapabilityFlags::all(), + LanguageFeatures::default(), + ); + assert!( + compiled_package.errors.is_empty(), + "{:#?}", + compiled_package.errors + ); + store.insert(compiled_package) + }) + .collect::>(); + + let dependencies = dependencies + .into_iter() + .map(|(name, code)| { + let source_map = SourceMap::new([(name.into(), code.into())], None); + let compiled_package = compile::compile( + &store, + &imports + .iter() + .map(|(alias, _)| (packages[0], Some(Arc::from(*alias)))) + .collect::>(), + source_map, + TargetCapabilityFlags::all(), + LanguageFeatures::default(), + ); + assert!( + compiled_package.errors.is_empty(), + "{:#?}", + compiled_package.errors + ); + store.insert(compiled_package) + }) + .collect::>(); + let mut compiler = Compiler::new( + &store, + &dependencies + .iter() + .map(|&pkg| (pkg, Some(Arc::from("B")))) + .collect::>(), + TargetCapabilityFlags::all(), + LanguageFeatures::default(), + ); + let mut unit = CompileUnit::default(); + + let mut errors = Vec::new(); + + compiler + .compile_fragments(&mut unit, "UserCode", user_code, |e| -> Result<(), ()> { + errors = e; + Ok(()) + }) + .expect("compile_fragments should succeed"); + + expect!["[]"].assert_eq(&format!("{errors:#?}")); +} fn check_unit(expect: &Expect, actual: &Increment) { let ast = format!("ast:\n{}", actual.ast.package); diff --git a/compiler/qsc_frontend/src/lower.rs b/compiler/qsc_frontend/src/lower.rs index bd9bf51dde..a1033a7692 100644 --- a/compiler/qsc_frontend/src/lower.rs +++ b/compiler/qsc_frontend/src/lower.rs @@ -94,7 +94,11 @@ pub(super) struct With<'a> { } impl With<'_> { - pub(super) fn lower_package(&mut self, package: &ast::Package) -> hir::Package { + pub(super) fn lower_package( + &mut self, + package: &ast::Package, + namespaces: qsc_data_structures::namespaces::NamespaceTreeRoot, + ) -> hir::Package { let mut stmts = Vec::new(); for node in &*package.nodes { match node { @@ -111,6 +115,7 @@ impl With<'_> { let items = self.lowerer.items.drain(..).map(|i| (i.id, i)).collect(); hir::Package { items, + namespaces, stmts, entry, } @@ -126,15 +131,23 @@ impl With<'_> { self.lowerer.parent = Some(id); // Exports are `Res` items, which contain `hir::ItemId`s. - let exports = namespace + // The second element in the tuple is the optional alias + let exports: Vec<(_, Option)> = namespace .exports() - .filter_map(|item| self.names.get(item.name().id)) + .filter_map(|item| { + self.names + .get(item.path.id) + .map(|x| (x, item.alias.clone())) + }) .collect::>(); let exported_hir_ids = exports .iter() - .filter_map(|res| match res { - resolve::Res::Item(hir::ItemId { item: id, .. }, _) => Some(*id), + .filter_map(|(res, alias)| match res { + resolve::Res::ExportedItem(id, hir_alias) => { + Some((*id, hir_alias.as_ref().or(alias.as_ref()))) + } + resolve::Res::Item(id, _) => Some((*id, alias.as_ref())), _ => None, }) .collect::>(); @@ -146,6 +159,7 @@ impl With<'_> { .collect::>(); let name = self.lower_idents(&namespace.name); + self.lowerer.items.push(hir::Item { id, span: namespace.span, @@ -162,26 +176,57 @@ impl With<'_> { fn lower_item( &mut self, item: &ast::Item, - exported_ids: &[hir::LocalItemId], + // the optional ident is the export alias, if any + exported_ids: &[(hir::ItemId, Option<&ast::Ident>)], ) -> Option { - let attrs = item + let attrs: Vec<_> = item .attrs .iter() .filter_map(|a| self.lower_attr(a)) .collect(); let resolve_id = |id| match self.names.get(id) { - Some(&resolve::Res::Item(item, _)) => item, - _ => panic!("item should have item ID"), + Some(&resolve::Res::ExportedItem(item, ref hir_alias)) => { + Some((item, hir_alias.clone())) + } + Some(&resolve::Res::Item(item, _)) => Some((item, None)), + _otherwise => None, }; let (id, kind) = match &*item.kind { - ast::ItemKind::Err | ast::ItemKind::Open(..) | - // exports are handled in namespace resolution (see resolve.rs) -- we don't need them in any lowered representations + ast::ItemKind::Err | ast::ItemKind::Open(..) => return None, - ast::ItemKind::ImportOrExport(_) => return None, + ast::ItemKind::ImportOrExport(item) => { + if item.is_import() { + return None; + } + for item in item.items.iter() { + let Some((id, alias)) = resolve_id(item.name().id) else { + continue; + }; + let is_reexport = id.package.is_some() || alias.is_some(); + // if the package is Some, then this is a re-export and we + // need to preserve the reference to the original `ItemId` + if is_reexport { + let mut name = self.lower_ident(item.name()); + name.id = self.assigner.next_node(); + let kind = hir::ItemKind::Export(name, id); + self.lowerer.items.push(hir::Item { + id: self.assigner.next_item(), + span: item.span(), + parent: self.lowerer.parent, + doc: "".into(), + // attrs on exports not supported + attrs: Vec::new(), + visibility: Visibility::Public, + kind, + }); + } + } + return None; + } ast::ItemKind::Callable(callable) => { - let id = resolve_id(callable.name.id); + let (id, _) = resolve_id(callable.name.id)?; let grandparent = self.lowerer.parent; self.lowerer.parent = Some(id.item); let callable = self.lower_callable_decl(callable); @@ -189,7 +234,7 @@ impl With<'_> { (id, hir::ItemKind::Callable(callable)) } ast::ItemKind::Ty(name, _) => { - let id = resolve_id(name.id); + let (id, _) = resolve_id(name.id)?; let udt = self .tys .udts @@ -199,7 +244,7 @@ impl With<'_> { (id, hir::ItemKind::Ty(self.lower_ident(name), udt.clone())) } ast::ItemKind::Struct(decl) => { - let id = resolve_id(decl.name.id); + let (id, _) = resolve_id(decl.name.id)?; let strct = self .tys .udts @@ -213,10 +258,28 @@ impl With<'_> { } }; - let visibility = if exported_ids.contains(&id.item) { - Visibility::Public - } else { - Visibility::Internal + let export_info = exported_ids.iter().find(|(hir_id, _)| hir_id == &id); + let visibility = match export_info { + Some((id, Some(alias))) => { + // this is the special case where this item _is_ exported, + // but it is exported with an alias. + // We want to hide the original item, making it private, but make + // the alias itself public. + let mut alias = self.lower_ident(alias); + alias.id = self.assigner.next_node(); + self.lowerer.items.push(hir::Item { + id: self.assigner.next_item(), + span: alias.span, + parent: self.lowerer.parent, + doc: Rc::clone(&item.doc), + attrs: attrs.clone(), + visibility: Visibility::Public, + kind: hir::ItemKind::Export(alias, *id), + }); + Visibility::Internal + } + Some((_, None)) => Visibility::Public, + None => Visibility::Internal, }; self.lowerer.items.push(hir::Item { @@ -781,6 +844,9 @@ impl With<'_> { match self.names.get(id) { Some(&resolve::Res::Item(item, _)) => hir::Res::Item(item), Some(&resolve::Res::Local(node)) => hir::Res::Local(self.lower_id(node)), + // Exported items are just pass-throughs to the items they reference, and should be + // treated as Res to that original item. + Some(&resolve::Res::ExportedItem(item_id, _)) => hir::Res::Item(item_id), Some(resolve::Res::PrimTy(_) | resolve::Res::UnitTy | resolve::Res::Param(_)) | None => hir::Res::Err, } diff --git a/compiler/qsc_frontend/src/resolve.rs b/compiler/qsc_frontend/src/resolve.rs index 3479bdf0b0..e776741937 100644 --- a/compiler/qsc_frontend/src/resolve.rs +++ b/compiler/qsc_frontend/src/resolve.rs @@ -57,7 +57,7 @@ pub fn path_as_field_accessor( /// A resolution. This connects a usage of a name with the declaration of that name by uniquely /// identifying the node that declared it. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum Res { /// A global or local item. Item(ItemId, ItemStatus), @@ -69,6 +69,8 @@ pub enum Res { PrimTy(Prim), /// The unit type. UnitTy, + /// An export, which could be from another package. + ExportedItem(ItemId, Option), } #[derive(Clone, Debug, Diagnostic, Error, PartialEq)] @@ -160,6 +162,16 @@ pub(super) enum Error { #[error("glob exports are not supported")] #[diagnostic(code("Qsc.Resolve.GlobExportNotSupported"))] GlobExportNotSupported(#[label] Span), + + #[error("aliasing a glob import is invalid")] + #[diagnostic(help("try `import {namespace_name} as {alias}` instead"))] + #[diagnostic(code("Qsc.Resolve.GlobImportAliasNotSupported"))] + GlobImportAliasNotSupported { + namespace_name: String, + alias: String, + #[label] + span: Span, + }, } #[derive(Debug, Clone)] @@ -199,10 +211,11 @@ impl ScopeItemEntry { } } -#[derive(PartialEq, Debug, Clone, Default, Copy)] +#[derive(PartialEq, Debug, Clone, Default)] pub enum ItemSource { Exported, - Imported, + // if the item was imported with an alias, the alias is stored here + Imported(Option), #[default] Declared, } @@ -365,6 +378,16 @@ impl GlobalScope { ) -> NamespaceId { self.namespaces.insert_or_find_namespace_from_root(ns, root) } + + fn insert_or_find_namespace_from_root_with_id( + &mut self, + name: Vec>, + root: NamespaceId, + base_id: NamespaceId, + ) { + self.namespaces + .insert_or_find_namespace_from_root_with_id(name, root, base_id); + } } #[derive(Debug, Clone, Eq, PartialEq)] @@ -536,6 +559,10 @@ impl Resolver { &self.names } + pub(crate) fn namespaces(&self) -> &qsc_data_structures::namespaces::NamespaceTreeRoot { + &self.globals.namespaces + } + pub(super) fn locals(&self) -> &Locals { &self.locals } @@ -585,7 +612,7 @@ impl Resolver { } } - fn check_item_status(&mut self, res: Res, name: String, span: Span) { + fn check_item_status(&mut self, res: &Res, name: String, span: Span) { if let Res::Item(_, ItemStatus::Unimplemented) = res { self.errors.push(Error::Unimplemented(name, span)); } @@ -600,7 +627,7 @@ impl Resolver { &None, ) { Ok(res) => { - self.check_item_status(res, name.name.to_string(), name.span); + self.check_item_status(&res, name.name.to_string(), name.span); self.names.insert(name.id, res); } Err(err) => self.errors.push(err), @@ -627,7 +654,7 @@ impl Resolver { ) { Ok(res) if matches!(res, Res::Local(_)) => { // The path is a field accessor. - self.names.insert(first.id, res); + self.names.insert(first.id, res.clone()); return Ok(res); } Err(err) if !matches!(err, Error::NotFound(_, _)) => return Err(err), // Local was found but has issues. @@ -646,8 +673,8 @@ impl Resolver { segments, ) { Ok(res) => { - self.check_item_status(res, path.name.name.to_string(), path.span); - self.names.insert(path.id, res); + self.check_item_status(&res, path.name.name.to_string(), path.span); + self.names.insert(path.id, res.clone()); Ok(res) } Err(err) => { @@ -834,7 +861,7 @@ impl Resolver { let current_namespace_name: Option> = current_namespace_name.map(Idents::name); let is_export = decl.is_export(); - for item in decl + for decl_item in decl .items() // filter out any dropped names // this is so you can still export an item that has been conditionally removed from compilation @@ -854,22 +881,29 @@ impl Resolver { }) .collect::>() { - if item.is_glob { - self.bind_glob_import_or_export(item, decl.is_export()); + let mut decl_alias = decl_item.alias.clone(); + if decl_item.is_glob { + self.bind_glob_import_or_export(decl_item, decl.is_export()); continue; } + let (term_result, ty_result) = ( - self.resolve_path(NameKind::Term, &item.path), - self.resolve_path(NameKind::Ty, &item.path), + self.resolve_path(NameKind::Term, &decl_item.path), + self.resolve_path(NameKind::Ty, &decl_item.path), ); if let (Err(err), Err(_)) = (&term_result, &ty_result) { // try to see if it is a namespace - self.handle_namespace_import_or_export(is_export, item, current_namespace, err); + self.handle_namespace_import_or_export( + is_export, + decl_item, + current_namespace, + err, + ); continue; }; - let local_name = item.name().name.clone(); + let local_name = decl_item.name().name.clone(); { let scope = self.current_scope_mut(); @@ -881,31 +915,43 @@ impl Resolver { (true, Some(entry), _) | (true, _, Some(entry)) if entry.source == ItemSource::Exported => { - let err = Error::DuplicateExport(local_name.to_string(), item.name().span); + let err = + Error::DuplicateExport(local_name.to_string(), decl_item.name().span); self.errors.push(err); continue; } (false, Some(entry), _) | (true, _, Some(entry)) - if entry.source == ItemSource::Imported => + if matches!(entry.source, ItemSource::Imported(..)) => { let err = - Error::ImportedDuplicate(local_name.to_string(), item.name().span); + Error::ImportedDuplicate(local_name.to_string(), decl_item.name().span); self.errors.push(err); continue; } + // special case: + // if this is an export of an import with an alias, + // we treat it as an aliased export of the original underlying item + (true, Some(entry), _) | (true, _, Some(entry)) => { + if let ItemSource::Imported(Some(ref alias)) = entry.source { + decl_alias = decl_alias.or(Some(alias.clone())); + } + } _ => (), } } + let local_name = decl_alias + .as_ref() + .map(|x| x.name.clone()) + .unwrap_or(local_name); + let item_source = if is_export { ItemSource::Exported } else { - ItemSource::Imported + ItemSource::Imported(decl_item.alias.clone()) }; - // if self.dropped_names.contains(TrackedName { name: item.name(), namespace: () } - - if let Ok(Res::Item(id, _)) = term_result { + if let Ok(Res::Item(id, _) | Res::ExportedItem(id, _)) = term_result { if is_export { if let Some(namespace) = current_namespace { self.globals @@ -915,12 +961,13 @@ impl Resolver { } } let scope = self.current_scope_mut(); - scope - .terms - .insert(local_name.clone(), ScopeItemEntry::new(id, item_source)); + scope.terms.insert( + local_name.clone(), + ScopeItemEntry::new(id, item_source.clone()), + ); } - if let Ok(Res::Item(id, _)) = ty_result { + if let Ok(Res::Item(id, _) | Res::ExportedItem(id, _)) = ty_result { if is_export { if let Some(namespace) = current_namespace { self.globals @@ -930,30 +977,56 @@ impl Resolver { } } let scope = self.current_scope_mut(); - scope - .tys - .insert(local_name.clone(), ScopeItemEntry::new(id, item_source)); + scope.tys.insert( + local_name.clone(), + ScopeItemEntry::new(id, item_source.clone()), + ); } + // This is kind of a messy match, it is merged and formatted this way + // to appease clippy and rustfmt. let res = match (term_result, ty_result) { - (Ok(res @ Res::Item(..)), _) | (_, Ok(res @ Res::Item(..))) => res, + // If either a term or a ty exists for this item already, + // as either an item or an export, then we should use that res. + (Ok(res @ (Res::Item(..) | Res::ExportedItem(..))), _) + | (_, Ok(res @ (Res::Item(..) | Res::ExportedItem(..)))) => res, + // Then, if the item was found as either a term or ty but is _not_ an item or export, this export + // refers to an invalid res. (Ok(_), _) | (_, Ok(_)) => { let err = if is_export { Error::ExportedNonItem } else { Error::ImportedNonItem }; - let err = err(item.path.span); + let err = err(decl_item.path.span); self.errors.push(err); continue; } + // Lastly, if neither was found, use the error from the term_result to report a not + // found error. (Err(err), _) => { self.errors.push(err); continue; } }; - // insert the item into the names we know about - self.names.insert(item.name().id, res); + match res { + // There's a bit of special casing here -- if this item is an export, + // and it originates from another package, we want to track the res as + // a separate exported item which points to the original package where + // the definition comes from. + Res::Item(item_id, _) if item_id.package.is_some() && is_export => { + self.names + .insert(decl_item.name().id, Res::ExportedItem(item_id, decl_alias)); + } + Res::Item(underlying_item_id, _) if decl_alias.is_some() && is_export => { + // insert the export's alias + self.names.insert( + decl_item.name().id, + Res::ExportedItem(underlying_item_id, decl_alias), + ); + } + _ => self.names.insert(decl_item.name().id, res), + } } } @@ -966,9 +1039,19 @@ impl Resolver { .push(Error::GlobExportNotSupported(item.path.span)); return; } + + if let Some(alias) = &item.alias { + self.errors.push(Error::GlobImportAliasNotSupported { + span: item.span(), + namespace_name: Into::::into(item.path.clone()).name().to_string(), + alias: alias.name.to_string(), + }); + return; + } + let items = Into::::into(item.path.clone()); let ns = self.globals.find_namespace(items.str_iter()); - let alias = item.alias.as_ref().map(|x| Box::new(x.clone())); + let Some(ns) = ns else { self.errors.push(Error::GlobImportNamespaceNotFound( item.path.name.to_string(), @@ -977,7 +1060,7 @@ impl Resolver { return; }; if !is_export { - self.bind_open(&items, &alias, ns); + self.bind_open(&items, &None, ns); } } @@ -1363,15 +1446,35 @@ impl GlobalTable { &mut self, id: PackageId, package: &hir::Package, + store: &crate::compile::PackageStore, alias: &Option>, ) { + // if there is a package-level alias defined, use that for the root namespace. let root = match alias { Some(alias) => self .scope .insert_or_find_namespace(vec![Rc::from(&**alias)]), + // otherwise, these namespaces will be inserted into the root of the local package + // without any alias. None => self.scope.namespaces.root_id(), }; + // iterate over the tree from the package and recreate it here + for names_for_same_namespace in &package.namespaces { + let mut names_iter = names_for_same_namespace.into_iter(); + let base_id = self.scope.insert_or_find_namespace_from_root( + names_iter + .next() + .expect("should always be at least one name"), + root, + ); + + for name in names_iter { + self.scope + .insert_or_find_namespace_from_root_with_id(name, root, base_id); + } + } + for global in global::iter_package(Some(id), package).filter(|global| { global.visibility == hir::Visibility::Public || matches!(&global.kind, global::Kind::Term(t) if t.intrinsic) @@ -1410,12 +1513,53 @@ impl GlobalTable { (global::Kind::Namespace, hir::Visibility::Public) => { self.scope.insert_or_find_namespace(global.namespace); } + (global::Kind::Export(item_id), _) => { + let Some(item) = find_item(store, item_id, id) else { + return; + }; + match item.kind { + hir::ItemKind::Callable(..) => { + self.scope + .terms + .get_mut_or_default(namespace) + .insert(global.name.clone(), Res::ExportedItem(item_id, None)); + } + hir::ItemKind::Namespace(ns, _items) => { + self.scope.insert_or_find_namespace(ns); + } + hir::ItemKind::Ty(..) => { + self.scope + .tys + .get_mut_or_default(namespace) + .insert(global.name.clone(), Res::ExportedItem(item_id, None)); + } + hir::ItemKind::Export(_, _) => { + unreachable!("find_item will never return an Export") + } + }; + } (_, hir::Visibility::Internal) => {} } } } } +fn find_item( + store: &crate::compile::PackageStore, + item: ItemId, + this_package: PackageId, +) -> Option { + let package_id = item.package.unwrap_or(this_package); + let package = store.get(package_id)?; + let item = package.package.items.get(item.item)?; + Some(match &item.kind { + hir::ItemKind::Callable(_) | hir::ItemKind::Namespace(_, _) | hir::ItemKind::Ty(_, _) => { + item.clone() + } + hir::ItemKind::Export(_alias, item) => return find_item(store, *item, package_id), + }) +} + /// Given some namespace `namespace`, add all the globals declared within it to the global scope. fn bind_global_items( names: &mut IndexMap, @@ -1491,7 +1635,7 @@ fn bind_global_item( names: &mut Names, scope: &mut GlobalScope, namespace: NamespaceId, - next_id: impl FnOnce() -> ItemId, + mut next_id: impl FnMut() -> ItemId, item: &ast::Item, ) -> Result<(), Vec> { match &*item.kind { @@ -1500,7 +1644,52 @@ fn bind_global_item( } ast::ItemKind::Ty(name, _) => bind_ty(name, namespace, next_id, item, names, scope), ast::ItemKind::Struct(decl) => bind_ty(&decl.name, namespace, next_id, item, names, scope), - ast::ItemKind::Err | ast::ItemKind::Open(..) | ast::ItemKind::ImportOrExport(..) => Ok(()), + ast::ItemKind::ImportOrExport(decl) => { + if decl.is_import() { + Ok(()) + } else { + for decl_item in decl.items.iter() { + // if the item is a namespace, bind it here as an item + let Some(ns) = scope + .namespaces + .get_namespace_id(Into::::into(decl_item.path.clone()).str_iter()) + else { + continue; + }; + let item_id = next_id(); + let res = Res::Item(item_id, ItemStatus::Available); + names.insert(decl_item.name().id, res.clone()); + match scope + .terms + .get_mut_or_default(namespace) + .entry(Rc::clone(&decl_item.name().name)) + { + Entry::Occupied(_) => { + let namespace_name = scope + .namespaces + .find_namespace_by_id(&namespace) + .0 + .join("."); + return Err(vec![Error::Duplicate( + decl_item.name().name.to_string(), + namespace_name, + decl_item.name().span, + )]); + } + Entry::Vacant(entry) => { + entry.insert(res); + } + } + + // and update the namespace tree + scope + .namespaces + .insert_with_id(Some(namespace), ns, &decl_item.name().name); + } + Ok(()) + } + } + ast::ItemKind::Err | ast::ItemKind::Open(..) => Ok(()), } } @@ -1515,7 +1704,7 @@ fn bind_callable( let item_id = next_id(); let status = ItemStatus::from_attrs(&ast_attrs_as_hir_attrs(item.attrs.as_ref())); let res = Res::Item(item_id, status); - names.insert(decl.name.id, res); + names.insert(decl.name.id, res.clone()); let mut errors = Vec::new(); match scope .terms @@ -1565,7 +1754,7 @@ fn bind_ty( let status = ItemStatus::from_attrs(&ast_attrs_as_hir_attrs(item.attrs.as_ref())); let res = Res::Item(item_id, status); - names.insert(name.id, res); + names.insert(name.id, res.clone()); match ( scope .terms @@ -1589,7 +1778,7 @@ fn bind_ty( )]) } (Entry::Vacant(term_entry), Entry::Vacant(ty_entry)) => { - term_entry.insert(res); + term_entry.insert(res.clone()); ty_entry.insert(res); Ok(()) } @@ -1770,6 +1959,7 @@ fn check_scoped_resolutions( return Some(Ok(res)); } } + let aliases = scope .opens .iter() @@ -1889,13 +2079,13 @@ where candidates.extend(&mut opens.iter().filter_map(|(ns_id, open)| { globals .get(kind, *ns_id, &provided_symbol_name.name) - .map(|res| (*res, open.clone())) + .map(|res| (res.clone(), open.clone())) })); } } for (candidate_namespace_id, open) in namespaces_to_search { - if find_symbol_in_namespace( + find_symbol_in_namespace( kind, globals, provided_namespace_name, @@ -1903,16 +2093,14 @@ where &mut candidates, candidate_namespace_id, open, - ) { - continue; - } + ); } 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. - candidates.retain(|&res, _| !matches!(res, Res::Item(_, ItemStatus::Unimplemented))); + candidates.retain(|res, _| !matches!(res, &Res::Item(_, ItemStatus::Unimplemented))); } candidates } @@ -1927,8 +2115,7 @@ fn find_symbol_in_namespace( candidates: &mut FxHashMap, candidate_namespace_id: NamespaceId, open: O, -) -> bool -where +) where O: Clone + std::fmt::Debug, { // Retrieve the namespace associated with the candidate_namespace_id from the global namespaces @@ -1947,7 +2134,7 @@ where // for example, if the query is `Foo.Bar.Baz`, we know there must exist a `Foo.Bar` somewhere. // If we didn't find it above, then even if we find `Baz` here, it is not the correct location. if provided_namespace_name.is_some() && namespace.is_none() { - return true; + return; } // Attempt to get the symbol from the global scope. If the namespace is None, use the candidate_namespace_id as a fallback @@ -1955,9 +2142,8 @@ where // If a symbol was found, insert it into the candidates map if let Some(res) = res { - candidates.insert(*res, open); + candidates.insert(res.clone(), open); } - false } /// Fetch the name and namespace ID of all prelude namespaces. @@ -2011,8 +2197,8 @@ fn resolve_scope_locals( } if let ScopeKind::Namespace(namespace) = &scope.kind { - if let Some(&res) = globals.get(kind, *namespace, name) { - return Some(res); + if let Some(res) = globals.get(kind, *namespace, name) { + return Some(res.clone()); } } diff --git a/compiler/qsc_frontend/src/resolve/tests.rs b/compiler/qsc_frontend/src/resolve/tests.rs index 54e0035fb3..2283ed77b9 100644 --- a/compiler/qsc_frontend/src/resolve/tests.rs +++ b/compiler/qsc_frontend/src/resolve/tests.rs @@ -84,22 +84,26 @@ impl<'a> Renamer<'a> { Res::PrimTy(prim) => format!("{prim:?}"), Res::UnitTy => "Unit".to_string(), Res::Param(id) => format!("param{id}"), + Res::ExportedItem(item, _) => match item.package { + None => format!("exported_item{}", item.item), + Some(package) => format!("reexport_from_{package}:{}", item.item), + }, } } } 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.into())); + if let Some(res) = self.names.get(path.id) { + self.changes.push((path.span, res.clone().into())); } else { visit::walk_path(self, path); } } fn visit_ident(&mut self, ident: &Ident) { - if let Some(&id) = self.names.get(ident.id) { - self.changes.push((ident.span, id.into())); + if let Some(res) = self.names.get(ident.id) { + self.changes.push((ident.span, res.clone().into())); } } @@ -113,8 +117,8 @@ impl Visitor<'_> for Renamer<'_> { } ItemKind::ImportOrExport(export) => { for item in export.items() { - if let Some(resolved_id) = self.names.get(item.path.id) { - self.changes.push((item.span(), (*resolved_id).into())); + if let Some(res) = self.names.get(item.path.id) { + self.changes.push((item.span(), (res.clone()).into())); } else if let Some(namespace_id) = self .namespaces .get_namespace_id(Into::::into(item.clone().path).str_iter()) @@ -132,8 +136,8 @@ impl Visitor<'_> for Renamer<'_> { fn visit_idents(&mut self, vec_ident: &Idents) { let parts: Vec = vec_ident.clone().into(); let first = parts.first().expect("should contain at least one item"); - if let Some(&id) = self.names.get(first.id) { - self.changes.push((first.span, id.into())); + if let Some(res) = self.names.get(first.id) { + self.changes.push((first.span, res.clone().into())); return; } @@ -4742,11 +4746,11 @@ fn export_namespace() { operation item2() : Unit {} } namespace namespace8 { - export namespace7; + export item4; } namespace namespace9 { open namespace7; - operation item5() : Unit { + operation item6() : Unit { item1(); item2(); } @@ -4777,11 +4781,11 @@ fn export_namespace_contains_children() { operation item1() : Unit {} } namespace namespace9 { - export namespace7; + export item3; } namespace namespace10 { open namespace8; - operation item4() : Unit { + operation item5() : Unit { item1(); } } @@ -4810,12 +4814,12 @@ fn export_namespace_cyclic() { export namespace8; } namespace namespace8 { - export namespace7; - operation item2() : Unit {} + export item2; + operation item3() : Unit {} } namespace namespace9 { open namespace8; - operation item4() : Unit { item2(); } + operation item5() : Unit { item3(); } } "#]], ); @@ -4836,12 +4840,12 @@ fn export_direct_cycle() { "}, &expect![[r#" namespace namespace7 { - export namespace7; + export item1; } namespace namespace8 { open namespace7; - operation item2() : Unit { } + operation item3() : Unit { } } "#]], ); @@ -4874,7 +4878,7 @@ fn export_namespace_with_alias() { } namespace namespace10 { open namespace8; - operation item4() : Unit { + operation item5() : Unit { item1(); item1(); } @@ -5090,3 +5094,23 @@ fn import_newtype() { }"#]], ); } + +#[test] +fn disallow_glob_alias_import() { + check( + indoc! {r#" + namespace Bar {} + namespace Main { + import Bar.* as B; + } + "#}, + &expect![[r#" + namespace namespace7 {} + namespace namespace8 { + import namespace7; + } + + // GlobImportAliasNotSupported { namespace_name: "Bar", alias: "B", span: Span { lo: 45, hi: 55 } } + "#]], + ); +} diff --git a/compiler/qsc_frontend/src/typeck/check.rs b/compiler/qsc_frontend/src/typeck/check.rs index 3d85d53f1f..c0a96bb445 100644 --- a/compiler/qsc_frontend/src/typeck/check.rs +++ b/compiler/qsc_frontend/src/typeck/check.rs @@ -37,26 +37,61 @@ impl GlobalTable { } } - pub(crate) fn add_external_package(&mut self, id: PackageId, package: &hir::Package) { + pub(crate) fn add_external_package( + &mut self, + package_id: PackageId, + package: &hir::Package, + store: &crate::compile::PackageStore, + ) { for item in package.items.values() { - let item_id = ItemId { - package: Some(id), - item: item.id, - }; - - match &item.kind { - hir::ItemKind::Callable(decl) => { - self.terms.insert(item_id, decl.scheme().with_package(id)) - } - hir::ItemKind::Namespace(..) => None, - hir::ItemKind::Ty(_, udt) => { - self.udts.insert(item_id, udt.clone()); - self.terms - .insert(item_id, udt.cons_scheme(item_id).with_package(id)) - } - }; + self.handle_item(item, package_id, store); } } + + fn handle_item( + &mut self, + item: &hir::Item, + package_id: PackageId, + store: &crate::compile::PackageStore, + ) { + let item_id = ItemId { + package: Some(package_id), + item: item.id, + }; + match &item.kind { + hir::ItemKind::Callable(decl) => { + self.terms + .insert(item_id, decl.scheme().with_package(package_id)); + } + hir::ItemKind::Namespace(..) => (), + hir::ItemKind::Ty(_, udt) => { + self.udts.insert(item_id, udt.clone()); + self.terms + .insert(item_id, udt.cons_scheme(item_id).with_package(package_id)); + } + hir::ItemKind::Export( + _, + ItemId { + package: other_package, + item: exported_item, + }, + ) => { + // If this item is an export, then we need to grab the ID that it references. + // It could be from the same package, or it could be from another package. + let package_id = other_package.unwrap_or(package_id); + // So, we get the correct package first, + let package = store.get(package_id).expect("package should exist"); + // find the actual item + let resolved_export = package + .package + .items + .get(*exported_item) + .expect("exported item should exist"); + // and recursively resolve it (it could be another export, i.e. a chain of exports. + self.handle_item(resolved_export, package_id, store); + } + }; + } } pub(crate) struct Checker { diff --git a/compiler/qsc_frontend/src/typeck/convert.rs b/compiler/qsc_frontend/src/typeck/convert.rs index 9f2c5caf4a..71eaf3176f 100644 --- a/compiler/qsc_frontend/src/typeck/convert.rs +++ b/compiler/qsc_frontend/src/typeck/convert.rs @@ -74,10 +74,16 @@ pub(super) fn ty_from_path(names: &Names, path: &Path) -> Ty { // as there is a syntactic difference between // paths and parameters. // So realistically, by construction, `Param` here is unreachable. - Some(resolve::Res::Local(_) | resolve::Res::Param(_)) => unreachable!( - "A path should never resolve \ + // A path can also never resolve to an export, because in typeck/check, + // we resolve exports to their original definition. + Some( + resolve::Res::Local(_) | resolve::Res::Param(_) | resolve::Res::ExportedItem(_, _), + ) => { + unreachable!( + "A path should never resolve \ to a local or a parameter, as there is syntactic differentiation." - ), + ) + } None => Ty::Err, } } diff --git a/compiler/qsc_frontend/src/typeck/rules.rs b/compiler/qsc_frontend/src/typeck/rules.rs index 6a7a68680f..7cb9679c59 100644 --- a/compiler/qsc_frontend/src/typeck/rules.rs +++ b/compiler/qsc_frontend/src/typeck/rules.rs @@ -114,7 +114,13 @@ impl<'a> Context<'a> { // as there is a syntactic difference between // paths and parameters. // So realistically, by construction, `Param` here is unreachable. - Some(resolve::Res::Local(_) | resolve::Res::Param(_)) => unreachable!( + // A path can also never resolve to an export, because in typeck/check, + // we resolve exports to their original definition. + Some( + resolve::Res::Local(_) + | resolve::Res::Param(_) + | resolve::Res::ExportedItem(_, _), + ) => unreachable!( "A path should never resolve \ to a local or a parameter, as there is syntactic differentiation." ), @@ -582,6 +588,13 @@ impl<'a> Context<'a> { .expect("local should have type") .clone(), ), + Some(Res::ExportedItem(item, _)) => { + // get the underlying item this refers to + let item_scheme = self.globals.get(item).expect("item should have scheme"); + let (ty, args) = self.inferrer.instantiate(item_scheme, expr.span); + self.table.generics.insert(expr.id, args); + converge(Ty::Arrow(Box::new(ty))) + } Some(Res::PrimTy(_) | Res::UnitTy | Res::Param(_)) => { panic!("expression should not resolve to type reference") } diff --git a/compiler/qsc_hir/src/global.rs b/compiler/qsc_hir/src/global.rs index cc9f230d46..754daf4846 100644 --- a/compiler/qsc_hir/src/global.rs +++ b/compiler/qsc_hir/src/global.rs @@ -2,7 +2,9 @@ // Licensed under the MIT License. use crate::{ - hir::{Item, ItemId, ItemKind, ItemStatus, Package, PackageId, SpecBody, SpecGen, Visibility}, + hir::{ + self, Item, ItemId, ItemKind, ItemStatus, Package, PackageId, SpecBody, SpecGen, Visibility, + }, ty::Scheme, }; use qsc_data_structures::{ @@ -25,6 +27,7 @@ pub enum Kind { Namespace, Ty(Ty), Term(Term), + Export(ItemId), } impl std::fmt::Debug for Kind { @@ -33,6 +36,7 @@ impl std::fmt::Debug for Kind { Kind::Namespace => write!(f, "Namespace"), Kind::Ty(ty) => write!(f, "Ty({})", ty.id), Kind::Term(term) => write!(f, "Term({})", term.id), + Kind::Export(id) => write!(f, "Export({id:?})"), } } } @@ -92,7 +96,7 @@ impl FromIterator for Table { .or_default() .insert(global.name, term); } - Kind::Namespace => {} + Kind::Namespace | Kind::Export(_) => {} } } @@ -121,17 +125,31 @@ impl PackageIter<'_> { .expect("parent should exist") .kind }); - let id = ItemId { - package: self.id, - item: item.id, + let (id, visibility, alias) = match &item.kind { + ItemKind::Export(name, item_id) => ( + ItemId { + package: item_id.package.or(self.id), + item: item_id.item, + }, + hir::Visibility::Public, + Some(name), + ), + _ => ( + ItemId { + package: self.id, + item: item.id, + }, + item.visibility, + None, + ), }; let status = ItemStatus::from_attrs(item.attrs.as_ref()); match (&item.kind, &parent) { (ItemKind::Callable(decl), Some(ItemKind::Namespace(namespace, _))) => Some(Global { namespace: namespace.into(), - name: Rc::clone(&decl.name.name), - visibility: item.visibility, + name: alias.map_or_else(|| Rc::clone(&decl.name.name), |alias| alias.name.clone()), + visibility, status, kind: Kind::Term(Term { id, @@ -142,8 +160,8 @@ impl PackageIter<'_> { (ItemKind::Ty(name, def), Some(ItemKind::Namespace(namespace, _))) => { self.next = Some(Global { namespace: namespace.into(), - name: Rc::clone(&name.name), - visibility: item.visibility, + name: alias.map_or_else(|| Rc::clone(&name.name), |alias| alias.name.clone()), + visibility, status, kind: Kind::Term(Term { id, @@ -155,7 +173,7 @@ impl PackageIter<'_> { Some(Global { namespace: namespace.into(), name: Rc::clone(&name.name), - visibility: item.visibility, + visibility, status, kind: Kind::Ty(Ty { id }), }) @@ -167,6 +185,24 @@ impl PackageIter<'_> { status, kind: Kind::Namespace, }), + ( + ItemKind::Export(name, ItemId { package, .. }), + Some(ItemKind::Namespace(namespace, _)), + ) => { + if package.is_none() && alias.is_none() { + // if there is no package, then this was declared in this package + // and this is a noop -- it will be marked as public on export + None + } else { + Some(Global { + namespace: namespace.into(), + name: name.name.clone(), + visibility, + status, + kind: Kind::Export(id), + }) + } + } _ => None, } } diff --git a/compiler/qsc_hir/src/hir.rs b/compiler/qsc_hir/src/hir.rs index 06806823e0..e727dad5f7 100644 --- a/compiler/qsc_hir/src/hir.rs +++ b/compiler/qsc_hir/src/hir.rs @@ -25,7 +25,7 @@ fn set_indentation<'a, 'b>( 0 => indent.with_str(""), 1 => indent.with_str(" "), 2 => indent.with_str(" "), - _ => unimplemented!("intentation level not supported"), + _ => unimplemented!("indentation level not supported"), } } @@ -253,6 +253,8 @@ impl Display for Res { pub struct Package { /// The items in the package. pub items: IndexMap, + /// The namespace tree defined by this package + pub namespaces: qsc_data_structures::namespaces::NamespaceTreeRoot, /// The top-level statements in the package. pub stmts: Vec, /// The entry expression for an executable package. @@ -335,6 +337,8 @@ pub enum ItemKind { Namespace(Idents, Vec), /// A `newtype` declaration. Ty(Ident, Udt), + /// An export of an item. + Export(Ident, ItemId), } impl Display for ItemKind { @@ -355,6 +359,7 @@ impl Display for ItemKind { } } ItemKind::Ty(name, udt) => write!(f, "Type ({name}): {udt}"), + ItemKind::Export(name, export) => write!(f, "Export ({name}): {export}"), } } } diff --git a/compiler/qsc_hir/src/mut_visit.rs b/compiler/qsc_hir/src/mut_visit.rs index c016b73d6a..be3904a1ac 100644 --- a/compiler/qsc_hir/src/mut_visit.rs +++ b/compiler/qsc_hir/src/mut_visit.rs @@ -71,7 +71,7 @@ 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, _) => vis.visit_idents(name), - ItemKind::Ty(name, _) => vis.visit_ident(name), + ItemKind::Ty(name, _) | ItemKind::Export(name, _) => vis.visit_ident(name), } } diff --git a/compiler/qsc_hir/src/ty.rs b/compiler/qsc_hir/src/ty.rs index 7a852a6e46..0ea18ed9a9 100644 --- a/compiler/qsc_hir/src/ty.rs +++ b/compiler/qsc_hir/src/ty.rs @@ -147,6 +147,7 @@ impl Display for Ty { } } +#[derive(Debug)] /// A type scheme. pub struct Scheme { params: Vec, diff --git a/compiler/qsc_hir/src/visit.rs b/compiler/qsc_hir/src/visit.rs index 1a47312677..a6e8a6fd51 100644 --- a/compiler/qsc_hir/src/visit.rs +++ b/compiler/qsc_hir/src/visit.rs @@ -64,7 +64,7 @@ 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, _) => vis.visit_idents(name), - ItemKind::Ty(name, _) => vis.visit_ident(name), + ItemKind::Ty(name, _) | ItemKind::Export(name, _) => vis.visit_ident(name), } } diff --git a/compiler/qsc_lowerer/src/lib.rs b/compiler/qsc_lowerer/src/lib.rs index ff83e42a88..e59d5b3fc5 100644 --- a/compiler/qsc_lowerer/src/lib.rs +++ b/compiler/qsc_lowerer/src/lib.rs @@ -87,7 +87,11 @@ impl Lowerer { .collect() } - pub fn lower_package(&mut self, package: &hir::Package) -> fir::Package { + pub fn lower_package( + &mut self, + package: &hir::Package, + store: &fir::PackageStore, + ) -> fir::Package { let entry = package.entry.as_ref().map(|e| self.lower_expr(e)); let entry_exec_graph = self.exec_graph.drain(..).collect(); let items: IndexMap = package @@ -116,7 +120,7 @@ impl Lowerer { pats, stmts, }; - qsc_fir::validate::validate(&package); + qsc_fir::validate::validate(&package, store); package } @@ -153,8 +157,6 @@ impl Lowerer { } fir_package.entry = entry; - - qsc_fir::validate::validate(fir_package); } pub fn revert_last_increment(&mut self, package: &mut fir::Package) { @@ -224,6 +226,12 @@ impl Lowerer { fir::ItemKind::Ty(name, udt) } + hir::ItemKind::Export(name, item) => { + let name = self.lower_ident(name); + let item = lower_item_id(item); + + fir::ItemKind::Export(name, item) + } }; let attrs = lower_attrs(&item.attrs); fir::Item { diff --git a/compiler/qsc_partial_eval/src/tests.rs b/compiler/qsc_partial_eval/src/tests.rs index 74b57372e9..84be502718 100644 --- a/compiler/qsc_partial_eval/src/tests.rs +++ b/compiler/qsc_partial_eval/src/tests.rs @@ -160,10 +160,8 @@ fn lower_hir_package_store(hir_package_store: &HirPackageStore) -> PackageStore let mut fir_store = PackageStore::new(); for (id, unit) in hir_package_store { let mut lowerer = Lowerer::new(); - fir_store.insert( - map_hir_package_to_fir(id), - lowerer.lower_package(&unit.package), - ); + let lowered_package = lowerer.lower_package(&unit.package, &fir_store); + fir_store.insert(map_hir_package_to_fir(id), lowered_package); } fir_store } diff --git a/compiler/qsc_passes/src/capabilitiesck.rs b/compiler/qsc_passes/src/capabilitiesck.rs index 0b068a8a6b..3ccfac3195 100644 --- a/compiler/qsc_passes/src/capabilitiesck.rs +++ b/compiler/qsc_passes/src/capabilitiesck.rs @@ -39,7 +39,7 @@ pub fn lower_store( ) -> qsc_fir::fir::PackageStore { let mut fir_store = qsc_fir::fir::PackageStore::new(); for (id, unit) in package_store { - let package = qsc_lowerer::Lowerer::new().lower_package(&unit.package); + let package = qsc_lowerer::Lowerer::new().lower_package(&unit.package, &fir_store); fir_store.insert(map_hir_package_to_fir(id), package); } fir_store @@ -55,8 +55,12 @@ pub fn run_rca_pass( let fir_package = fir_store.get(package_id); let package_compute_properties = compute_properties.get(package_id); - let mut errors = - check_supported_capabilities(fir_package, package_compute_properties, capabilities); + let mut errors = check_supported_capabilities( + fir_package, + package_compute_properties, + capabilities, + fir_store, + ); if errors.is_empty() { Ok(compute_properties) @@ -74,6 +78,7 @@ pub fn check_supported_capabilities( package: &Package, compute_properties: &PackageComputeProperties, capabilities: TargetCapabilityFlags, + store: &qsc_fir::fir::PackageStore, ) -> Vec { let checker = Checker { package, @@ -81,6 +86,7 @@ pub fn check_supported_capabilities( target_capabilities: capabilities, current_callable: None, missing_features_map: FxHashMap::::default(), + store, }; checker.check_all() @@ -92,6 +98,7 @@ struct Checker<'a> { target_capabilities: TargetCapabilityFlags, current_callable: Option, missing_features_map: FxHashMap, + store: &'a qsc_fir::fir::PackageStore, } impl<'a> Visitor<'a> for Checker<'a> { @@ -111,7 +118,7 @@ impl<'a> Visitor<'a> for Checker<'a> { self.package.get_stmt(id) } - fn visit_package(&mut self, package: &'a Package) { + fn visit_package(&mut self, package: &'a Package, _: &crate::fir::PackageStore) { package .items .iter() @@ -192,7 +199,7 @@ impl<'a> Visitor<'a> for Checker<'a> { impl<'a> Checker<'a> { pub fn check_all(mut self) -> Vec { - self.visit_package(self.package); + self.visit_package(self.package, self.store); self.generate_errors() } diff --git a/compiler/qsc_passes/src/capabilitiesck/tests_common.rs b/compiler/qsc_passes/src/capabilitiesck/tests_common.rs index ea711f7b44..93a5580af4 100644 --- a/compiler/qsc_passes/src/capabilitiesck/tests_common.rs +++ b/compiler/qsc_passes/src/capabilitiesck/tests_common.rs @@ -16,14 +16,24 @@ use qsc_rca::{Analyzer, PackageComputeProperties, PackageStoreComputeProperties} pub fn check(source: &str, expect: &Expect, capabilities: TargetCapabilityFlags) { let compilation_context = CompilationContext::new(source); let (package, compute_properties) = compilation_context.get_package_compute_properties_tuple(); - let errors = check_supported_capabilities(package, compute_properties, capabilities); + let errors = check_supported_capabilities( + package, + compute_properties, + capabilities, + &compilation_context.fir_store, + ); expect.assert_debug_eq(&errors); } pub fn check_for_exe(source: &str, expect: &Expect, capabilities: TargetCapabilityFlags) { let compilation_context = CompilationContext::new_for_exe(source); let (package, compute_properties) = compilation_context.get_package_compute_properties_tuple(); - let errors = check_supported_capabilities(package, compute_properties, capabilities); + let errors = check_supported_capabilities( + package, + compute_properties, + capabilities, + &compilation_context.fir_store, + ); expect.assert_debug_eq(&errors); } @@ -33,10 +43,8 @@ fn lower_hir_package_store( ) -> PackageStore { let mut fir_store = PackageStore::new(); for (id, unit) in hir_package_store { - fir_store.insert( - map_hir_package_to_fir(id), - lowerer.lower_package(&unit.package), - ); + let pkg = lowerer.lower_package(&unit.package, &fir_store); + fir_store.insert(map_hir_package_to_fir(id), pkg); } fir_store } @@ -49,7 +57,8 @@ struct CompilationContext { impl CompilationContext { fn new(source: &str) -> Self { - let (std_id, store) = qsc::compile::package_store_with_stdlib(TargetCapabilityFlags::all()); + let mut store = qsc::PackageStore::new(qsc::compile::core()); + let std_id = store.insert(qsc::compile::std(&store, TargetCapabilityFlags::all())); let mut compiler = Compiler::new( SourceMap::default(), PackageType::Lib, diff --git a/compiler/qsc_passes/src/lib.rs b/compiler/qsc_passes/src/lib.rs index a9d58e9c5d..7b168b3d4f 100644 --- a/compiler/qsc_passes/src/lib.rs +++ b/compiler/qsc_passes/src/lib.rs @@ -165,9 +165,10 @@ pub fn run_fir_passes( package: &fir::Package, compute_properties: &PackageComputeProperties, capabilities: TargetCapabilityFlags, + store: &fir::PackageStore, ) -> Vec { let capabilities_errors = - check_supported_capabilities(package, compute_properties, capabilities); + check_supported_capabilities(package, compute_properties, capabilities, store); capabilities_errors .into_iter() .map(Error::CapabilitiesCk) diff --git a/compiler/qsc_rca/src/core.rs b/compiler/qsc_rca/src/core.rs index 42f26fd348..b710d1f719 100644 --- a/compiler/qsc_rca/src/core.rs +++ b/compiler/qsc_rca/src/core.rs @@ -1927,10 +1927,28 @@ impl<'a> Visitor<'a> for Analyzer<'a> { ItemKind::Callable(decl) => { self.visit_callable_decl(decl); } + ItemKind::Export( + _, + qsc_fir::fir::ItemId { + package: Some(package), + item, + }, + ) => { + let package = self.package_store.get(*package); + let item = package + .items + .get(*item) + .expect("item should exist in package"); + self.visit_item(item); + } + ItemKind::Export(_, qsc_fir::fir::ItemId { package: None, .. }) => { + // if the package is none, then we know this item was defined in this package + // and therefore doesn't need to be analyzed -- the item itself will be analyzed. + } }; } - fn visit_package(&mut self, _: &'a Package) { + fn visit_package(&mut self, _: &'a Package, _: &PackageStore) { // Should never be called. unimplemented!("should never be called"); } diff --git a/compiler/qsc_rca/src/cycle_detection.rs b/compiler/qsc_rca/src/cycle_detection.rs index 7a61c75605..33b8c96fce 100644 --- a/compiler/qsc_rca/src/cycle_detection.rs +++ b/compiler/qsc_rca/src/cycle_detection.rs @@ -7,8 +7,8 @@ use crate::common::{ use qsc_fir::{ fir::{ Block, BlockId, CallableDecl, CallableImpl, Expr, ExprId, ExprKind, Item, ItemKind, - LocalVarId, Mutability, Package, PackageId, PackageLookup, Pat, PatId, PatKind, SpecDecl, - Stmt, StmtId, StmtKind, + LocalVarId, Mutability, Package, PackageId, PackageLookup, PackageStore, Pat, PatId, + PatKind, SpecDecl, Stmt, StmtId, StmtKind, }, ty::FunctorSetValue, visit::{walk_expr, Visitor}, @@ -22,21 +22,23 @@ pub struct CycleDetector<'a> { stack: CallStack, specializations_locals: FxHashMap>, specializations_with_cycles: FxHashSet, + store: &'a PackageStore, } impl<'a> CycleDetector<'a> { - pub fn new(package_id: PackageId, package: &'a Package) -> Self { + pub fn new(package_id: PackageId, package: &'a Package, store: &'a PackageStore) -> Self { Self { package_id, package, stack: CallStack::default(), specializations_locals: FxHashMap::default(), specializations_with_cycles: FxHashSet::::default(), + store, } } pub fn detect_specializations_with_cycles(mut self) -> Vec { - self.visit_package(self.package); + self.visit_package(self.package, self.store); self.specializations_with_cycles.drain().collect() } @@ -116,19 +118,34 @@ impl<'a> CycleDetector<'a> { return; } let item = self.package.get_item(callee.item.item); - match &item.kind { - ItemKind::Callable(callable_decl) => self.walk_callable_decl( - (callee.item.item, callee.functor_app.functor_set_value()).into(), - callable_decl, - ), - ItemKind::Namespace(_, _) => panic!("calls to namespaces are invalid"), - ItemKind::Ty(_, _) => { - // Ignore "calls" to types. - } + self.handle_item(item, &callee); + } + } + + fn handle_item(&mut self, item: &'a Item, callee: &crate::common::Callee) { + match &item.kind { + ItemKind::Callable(callable_decl) => self.walk_callable_decl( + (callee.item.item, callee.functor_app.functor_set_value()).into(), + callable_decl, + ), + ItemKind::Namespace(_, _) => panic!("calls to namespaces are invalid"), + ItemKind::Ty(_, _) => { + // Ignore "calls" to types. + } + ItemKind::Export(_, id) => { + // resolve the item, which may exist in another package + let item = self.resolve_item(*id); + self.handle_item(item, callee); } } } + fn resolve_item(&self, item: qsc_fir::fir::ItemId) -> &'a Item { + let package_id = item.package.unwrap_or(self.package_id); + let package = self.store.get(package_id); + package.get_item(item.item) + } + fn walk_spec_decl(&mut self, local_spec_id: LocalSpecId, spec_decl: &'a SpecDecl) { // If the specialization is already in the stack, it means the callable has a cycle. if self.stack.contains(&local_spec_id) { @@ -230,7 +247,7 @@ impl<'a> Visitor<'a> for CycleDetector<'a> { } } - fn visit_package(&mut self, package: &'a Package) { + fn visit_package(&mut self, package: &'a Package, _: &PackageStore) { // We are only interested in visiting items. package.items.values().for_each(|i| self.visit_item(i)); } diff --git a/compiler/qsc_rca/src/cyclic_callables.rs b/compiler/qsc_rca/src/cyclic_callables.rs index 184ac2d080..c4e762dd98 100644 --- a/compiler/qsc_rca/src/cyclic_callables.rs +++ b/compiler/qsc_rca/src/cyclic_callables.rs @@ -123,7 +123,7 @@ impl<'a> Analyzer<'a> { fn analyze_package_internal(&mut self, package_id: PackageId, package: &'a Package) { self.current_package = Some(package_id); - self.visit_package(package); + self.visit_package(package, self.package_store); self.current_package = None; } @@ -223,9 +223,9 @@ impl<'a> Visitor<'a> for Analyzer<'a> { unimplemented!(); } - fn visit_package(&mut self, package: &'a Package) { + fn visit_package(&mut self, package: &'a Package, store: &PackageStore) { let package_id = self.get_current_package(); - let cycle_detector = CycleDetector::new(package_id, package); + let cycle_detector = CycleDetector::new(package_id, package, store); let specializations_with_cycles = cycle_detector.detect_specializations_with_cycles(); for cyclic_specialization in specializations_with_cycles { self.analyze_cyclic_specialization(cyclic_specialization); diff --git a/compiler/qsc_rca/src/overrider.rs b/compiler/qsc_rca/src/overrider.rs index 7f9dc36ae6..afa946d32e 100644 --- a/compiler/qsc_rca/src/overrider.rs +++ b/compiler/qsc_rca/src/overrider.rs @@ -106,7 +106,7 @@ impl<'a> Overrider<'a> { fn populate_package_internal(&mut self, package_id: PackageId, package: &'a Package) { self.current_package = Some(package_id); - self.visit_package(package); + self.visit_package(package, self.package_store); self.current_package = None; } @@ -197,7 +197,7 @@ impl<'a> Visitor<'a> for Overrider<'a> { .insert_expr((package_id, id).into(), application_generator_set); } - fn visit_package(&mut self, package: &'a Package) { + fn visit_package(&mut self, package: &'a Package, _: &PackageStore) { // Go through each namespace, identifying the callables for which we have overrides. let namespaces = package .items diff --git a/compiler/qsc_rca/src/tests.rs b/compiler/qsc_rca/src/tests.rs index 722b1b22c2..419e6b2575 100644 --- a/compiler/qsc_rca/src/tests.rs +++ b/compiler/qsc_rca/src/tests.rs @@ -170,7 +170,7 @@ fn lower_hir_package_store(hir_package_store: &HirPackageStore) -> PackageStore let mut lowerer = Lowerer::new(); fir_store.insert( map_hir_package_to_fir(id), - lowerer.lower_package(&unit.package), + lowerer.lower_package(&unit.package, &fir_store), ); } fir_store diff --git a/compiler/qsc_rir/src/rir.rs b/compiler/qsc_rir/src/rir.rs index 7e0dbb632f..5d529042a6 100644 --- a/compiler/qsc_rir/src/rir.rs +++ b/compiler/qsc_rir/src/rir.rs @@ -617,6 +617,6 @@ fn set_indentation<'a, 'b>( 0 => indent.with_str(""), 1 => indent.with_str(" "), 2 => indent.with_str(" "), - _ => unimplemented!("intentation level not supported"), + _ => unimplemented!("indentation level not supported"), } } diff --git a/language_service/src/compilation.rs b/language_service/src/compilation.rs index 70e672a96c..552ae74b10 100644 --- a/language_service/src/compilation.rs +++ b/language_service/src/compilation.rs @@ -450,11 +450,38 @@ impl Lookup for Compilation { .get(package_id) .expect("package should exist in store") .package; - ( - package + + let mut item: &hir::Item = package + .items + .get(item_id.item) + .expect("item id should exist"); + + // follow chain of exports, if it is an aexport + while let hir::ItemKind::Export( + _, + hir::ItemId { + package: package_id, + item: local_item_id, + }, + ) = &item.kind + { + let package: &hir::Package = if let Some(id) = package_id { + &self + .package_store + .get(*id) + .expect("package should exist in store") + .package + } else { + package + }; + + item = package .items - .get(item_id.item) - .expect("item id should exist"), + .get(*local_item_id) + .expect("exported item should exist"); + } + ( + item, package, hir::ItemId { package: Some(package_id), diff --git a/language_service/src/completion.rs b/language_service/src/completion.rs index d3387a5b35..4a65207f4c 100644 --- a/language_service/src/completion.rs +++ b/language_service/src/completion.rs @@ -646,6 +646,8 @@ fn local_completion( CompletionItemKind::Interface, ) } + // We don't want completions for items exported from the local scope + ItemKind::Export(_, _) => return None, }; (kind, detail) } diff --git a/language_service/src/name_locator.rs b/language_service/src/name_locator.rs index de414d6558..4b486fa4a0 100644 --- a/language_service/src/name_locator.rs +++ b/language_service/src/name_locator.rs @@ -447,6 +447,9 @@ impl<'inner, 'package, T: Handler<'package>> Visitor<'package> for Locator<'inne path.id ) } + hir::ItemKind::Export(_, _) => { + unreachable!("handled in fn resolve_item_relative_to_user_package") + } } } Some(resolve::Res::Local(node_id)) => { diff --git a/language_service/src/references.rs b/language_service/src/references.rs index fb1c7f4160..46e4c31911 100644 --- a/language_service/src/references.rs +++ b/language_service/src/references.rs @@ -203,7 +203,7 @@ impl<'a> ReferenceFinder<'a> { let def_span = match &def.kind { hir::ItemKind::Callable(decl) => decl.name.span, hir::ItemKind::Namespace(name, _) => name.span(), - hir::ItemKind::Ty(name, _) => name.span, + hir::ItemKind::Ty(name, _) | hir::ItemKind::Export(name, _) => name.span, }; locations.push( self.location( diff --git a/library/qs_source/src/std/modern_api.qs b/library/qs_source/src/std/modern_api.qs new file mode 100644 index 0000000000..f647e11aad --- /dev/null +++ b/library/qs_source/src/std/modern_api.qs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + + +// This file re-exports the standard library under the name `Std`, which will be the preferred standard library API going forward. + +namespace Std { + export + Microsoft.Quantum.Arrays, + Microsoft.Quantum.Canon, + Microsoft.Quantum.Convert, + Microsoft.Quantum.Core, + Microsoft.Quantum.Diagnostics, + Microsoft.Quantum.Logical, + Microsoft.Quantum.Intrinsic, + Microsoft.Quantum.Math, + Microsoft.Quantum.Measurement, + Microsoft.Quantum.Random, + Microsoft.Quantum.ResourceEstimation, + Microsoft.Quantum.Unstable; +} diff --git a/library/src/lib.rs b/library/src/lib.rs index 15a60da35b..de58fef9ea 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -89,4 +89,8 @@ pub const STD_LIB: &[(&str, &str)] = &[ "qsharp-library-source:unstable_table_lookup.qs", include_str!("../qs_source/src/std/unstable_table_lookup.qs"), ), + ( + "qsharp-library-source:modern_api.qs", + include_str!("../qs_source/src/std/modern_api.qs"), + ), ]; diff --git a/library/src/tests.rs b/library/src/tests.rs index b51d5b8588..b01771035a 100644 --- a/library/src/tests.rs +++ b/library/src/tests.rs @@ -204,3 +204,15 @@ fn check_base_profile_measure_resets_aux_qubits() { &Value::RESULT_ONE, ); } + +// just tests a single case of the stdlib reexports for the modern api, +// to ensure that reexporting functionality doesn't break +#[test] +fn stdlib_reexport_single_case() { + test_expression( + r#" { + import Std.Arrays.Count; + }"#, + &Value::Tuple(vec![].into()), + ); +}