diff --git a/crates/mun_hir/Cargo.toml b/crates/mun_hir/Cargo.toml index 2d7343555..ff87da21c 100644 --- a/crates/mun_hir/Cargo.toml +++ b/crates/mun_hir/Cargo.toml @@ -23,9 +23,11 @@ once_cell = "1.4.0" ena = "0.14" drop_bomb = "0.1.4" either = "1.5.3" -itertools = "0.9.0" +itertools = "0.10.0" +smallvec = "1.4.2" [dev-dependencies] insta = "0.16" +text_trees = "0.1.2" parking_lot = "0.10" mun_test = { version = "=0.1.0", path = "../mun_test" } diff --git a/crates/mun_hir/src/code_model/module.rs b/crates/mun_hir/src/code_model/module.rs index 88e7c1a4d..6f76eb798 100644 --- a/crates/mun_hir/src/code_model/module.rs +++ b/crates/mun_hir/src/code_model/module.rs @@ -1,7 +1,7 @@ use super::{Function, Package, Struct, TypeAlias}; use crate::ids::{ItemDefinitionId, ModuleId}; use crate::primitive_type::PrimitiveType; -use crate::{DiagnosticSink, FileId, HirDatabase}; +use crate::{DiagnosticSink, FileId, HirDatabase, Name}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Module { @@ -23,6 +23,49 @@ impl Module { .find(|m| m.file_id(db) == Some(file)) } + /// Returns the parent module of this module. + pub fn parent(self, db: &dyn HirDatabase) -> Option { + let module_tree = db.module_tree(self.id.package); + let parent_id = module_tree[self.id.local_id].parent?; + Some(Module { + id: ModuleId { + package: self.id.package, + local_id: parent_id, + }, + }) + } + + /// Returns the name of this module or None if this is the root module + pub fn name(self, db: &dyn HirDatabase) -> Option { + let module_tree = db.module_tree(self.id.package); + let parent = module_tree[self.id.local_id].parent?; + module_tree[parent] + .children + .iter() + .find_map(|(name, module_id)| { + if *module_id == self.id.local_id { + Some(name.clone()) + } else { + None + } + }) + } + + /// Returns all the child modules of this module + pub fn children(self, db: &dyn HirDatabase) -> Vec { + let module_tree = db.module_tree(self.id.package); + module_tree[self.id.local_id] + .children + .iter() + .map(|(_, local_id)| Module { + id: ModuleId { + package: self.id.package, + local_id: *local_id, + }, + }) + .collect() + } + /// Returns the file that defines the module pub fn file_id(self, db: &dyn HirDatabase) -> Option { db.module_tree(self.id.package).modules[self.id.local_id].file @@ -39,12 +82,19 @@ impl Module { /// Iterate over all diagnostics from this `Module` by placing them in the `sink` pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { + // Add diagnostics from the package definitions + let package_defs = db.package_defs(self.id.package); + package_defs.add_diagnostics(db.upcast(), self.id.local_id, sink); + + // Add diagnostics from the item tree if let Some(file_id) = self.file_id(db) { let item_tree = db.item_tree(file_id); for diagnostics in item_tree.diagnostics.iter() { diagnostics.add_to(db, &*item_tree, sink); } } + + // Add diagnostics from the items for decl in self.declarations(db) { match decl { ModuleDef::Function(f) => f.diagnostics(db, sink), @@ -54,6 +104,17 @@ impl Module { } } } + + /// Returns the path from this module to the root module + pub fn path_to_root(self, db: &dyn HirDatabase) -> Vec { + let mut res = vec![self]; + let mut curr = self; + while let Some(next) = curr.parent(db) { + res.push(next); + curr = next + } + res + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] diff --git a/crates/mun_hir/src/diagnostics.rs b/crates/mun_hir/src/diagnostics.rs index 70e787b73..89c929083 100644 --- a/crates/mun_hir/src/diagnostics.rs +++ b/crates/mun_hir/src/diagnostics.rs @@ -746,3 +746,41 @@ impl Diagnostic for FreeTypeAliasWithoutTypeRef { self } } + +#[derive(Debug)] +pub struct UnresolvedImport { + pub use_tree: InFile>, +} + +impl Diagnostic for UnresolvedImport { + fn message(&self) -> String { + "unresolved import".to_string() + } + + fn source(&self) -> InFile { + self.use_tree.map(Into::into) + } + + fn as_any(&self) -> &(dyn Any + Send) { + self + } +} + +#[derive(Debug)] +pub struct ImportDuplicateDefinition { + pub use_tree: InFile>, +} + +impl Diagnostic for ImportDuplicateDefinition { + fn message(&self) -> String { + "a second item with the same name imported. Try to use an alias.".to_string() + } + + fn source(&self) -> InFile { + self.use_tree.map(Into::into) + } + + fn as_any(&self) -> &(dyn Any + Send) { + self + } +} diff --git a/crates/mun_hir/src/item_scope.rs b/crates/mun_hir/src/item_scope.rs index f3dbe6bf4..a25bde428 100644 --- a/crates/mun_hir/src/item_scope.rs +++ b/crates/mun_hir/src/item_scope.rs @@ -1,7 +1,32 @@ +use crate::module_tree::LocalModuleId; use crate::primitive_type::PrimitiveType; use crate::{ids::ItemDefinitionId, visibility::Visibility, Name, PerNs}; use once_cell::sync::Lazy; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::collections::hash_map::Entry; + +/// Defines the type of import. An import can either be a named import (e.g. `use foo::Bar`) or a +/// wildcard import (e.g. `use foo::*`) +#[derive(Copy, Clone)] +pub(crate) enum ImportType { + /// A wildcard import statement (`use foo::*`) + Glob, + + /// A named import statement (`use foo::Bar`) + Named, +} + +/// A struct that holds information on which name was imported via a glob import. This information +/// is used by the `PackageDef` collector to keep track of duplicates so that this doesn't result in +/// a duplicate name error; e.g. : +/// ```mun +/// use foo::{Foo, *}; +/// ``` +#[derive(Debug, Default)] +pub struct PerNsGlobImports { + types: FxHashSet<(LocalModuleId, Name)>, + values: FxHashSet<(LocalModuleId, Name)>, +} /// Holds all items that are visible from an item as well as by which name and under which /// visibility they are accessible. @@ -17,6 +42,16 @@ pub struct ItemScope { defs: Vec, } +/// A struct that is returned from `add_resolution_from_import`. +#[derive(Debug)] +pub(crate) struct AddResolutionFromImportResult { + /// Whether or not adding the resolution changed the item scope + pub changed: bool, + + /// Whether or not adding the resolution will overwrite an existing entry + pub duplicate: bool, +} + pub(crate) static BUILTIN_SCOPE: Lazy>> = Lazy::new(|| { PrimitiveType::ALL @@ -31,6 +66,14 @@ pub(crate) static BUILTIN_SCOPE: Lazy impl Iterator)> + '_ { + let keys: FxHashSet<_> = self.types.keys().chain(self.values.keys()).collect(); + keys.into_iter().map(move |name| (name, self.get(name))) + } + /// Returns an iterator over all declarations with this scope pub fn declarations(&self) -> impl Iterator + '_ { self.defs.iter().copied() @@ -41,18 +84,110 @@ impl ItemScope { self.defs.push(def) } - /// Adds a named item resolution into the scope + /// Adds a named item resolution into the scope. Returns true if adding the resolution changes + /// the scope. pub(crate) fn add_resolution( &mut self, name: Name, def: PerNs<(ItemDefinitionId, Visibility)>, - ) { + ) -> bool { + let mut changed = false; if let Some((types, visibility)) = def.types { - self.types.insert(name.clone(), (types, visibility)); + self.types.entry(name.clone()).or_insert_with(|| { + changed = true; + (types, visibility) + }); } if let Some((values, visibility)) = def.values { - self.values.insert(name, (values, visibility)); + self.values.entry(name).or_insert_with(|| { + changed = true; + (values, visibility) + }); } + + changed + } + + /// Adds a named item resolution into the scope which is the result of a `use` statement. + /// Returns true if adding the resolution changes the scope. + pub(crate) fn add_resolution_from_import( + &mut self, + glob_imports: &mut PerNsGlobImports, + lookup: (LocalModuleId, Name), + def: PerNs<(ItemDefinitionId, Visibility)>, + def_import_type: ImportType, + ) -> AddResolutionFromImportResult { + let mut changed = false; + let mut duplicate = false; + + macro_rules! check_changed { + ( + $changed:ident, + ( $this:ident / $def:ident ) . $field:ident, + $glob_imports:ident [ $lookup:ident ], + $def_import_type:ident + ) => {{ + let existing = $this.$field.entry($lookup.1.clone()); + match (existing, $def.$field) { + // The name doesnt exist yet in the scope + (Entry::Vacant(entry), Some(_)) => { + match $def_import_type { + // If this is a wildcard import, add it to the list of items we imported + // via a glob. This information is stored so if we later explicitly + // import this type or value, it doesn't cause a conflict. + ImportType::Glob => { + $glob_imports.$field.insert($lookup.clone()); + } + // If this is *not* a wildcard import, remove it from the list of items + // imported via a glob. + ImportType::Named => { + $glob_imports.$field.remove(&$lookup); + } + } + + if let Some(fld) = $def.$field { + entry.insert(fld); + } + $changed = true; + } + // If there is already an entry for this resolution, but it came from a glob + // pattern, overwrite it and mark it as not included from the glob pattern. + (Entry::Occupied(mut entry), Some(_)) + if $glob_imports.$field.contains(&$lookup) + && matches!($def_import_type, ImportType::Named) => + { + $glob_imports.$field.remove(&$lookup); + if let Some(fld) = $def.$field { + entry.insert(fld); + } + $changed = true; + } + (Entry::Occupied(_), Some(_)) => { + let is_previous_from_glob = $glob_imports.$field.contains(&$lookup); + let is_explicit_import = matches!($def_import_type, ImportType::Named); + if is_explicit_import && !is_previous_from_glob { + duplicate = true; + } + } + _ => {} + } + }}; + } + + check_changed!( + changed, + (self / def).types, + glob_imports[lookup], + def_import_type + ); + check_changed!( + changed, + (self / def).values, + glob_imports[lookup], + def_import_type + ); + + AddResolutionFromImportResult { changed, duplicate } } /// Gets a name from the current module scope diff --git a/crates/mun_hir/src/item_tree.rs b/crates/mun_hir/src/item_tree.rs index 31faed7e5..433334ee4 100644 --- a/crates/mun_hir/src/item_tree.rs +++ b/crates/mun_hir/src/item_tree.rs @@ -2,12 +2,13 @@ mod lower; #[cfg(test)] mod tests; +use crate::path::ImportAlias; use crate::{ arena::{Arena, Idx}, source_id::FileAstId, type_ref::TypeRef, visibility::RawVisibility, - DefDatabase, FileId, InFile, Name, + DefDatabase, FileId, InFile, Name, Path, }; use mun_syntax::{ast, AstNode}; use std::{ @@ -45,6 +46,8 @@ impl fmt::Debug for RawVisibilityId { } /// An `ItemTree` is a derivative of an AST that only contains the items defined in the AST. +/// +/// Examples of items are: functions, structs, use statements. #[derive(Debug, Eq, PartialEq)] pub struct ItemTree { file_id: FileId, @@ -102,6 +105,7 @@ impl ItemVisibilities { #[derive(Default, Debug, Eq, PartialEq)] struct ItemTreeData { + imports: Arena, functions: Arena, structs: Arena, fields: Arena, @@ -222,6 +226,7 @@ mod_items! { Function in functions -> ast::FunctionDef, Struct in structs -> ast::StructDef, TypeAlias in type_aliases -> ast::TypeAliasDef, + Import in imports -> ast::Use, } macro_rules! impl_index { @@ -265,6 +270,30 @@ impl Index> for ItemTree { } } +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Import { + /// The path of the import (e.g. foo::Bar). Note that group imports have been desugared, each + /// item in the import tree is a seperate import. + pub path: Path, + + /// An optional alias for this import statement (e.g. `use foo as bar`) + pub alias: Option, + + /// The visibility of the import statement as seen from the file that contains the import + /// statement. + pub visibility: RawVisibilityId, + + /// Whether or not this is a wildcard import. + pub is_glob: bool, + + /// AST Id of the `use` item this import was derived from. Note that multiple `Import`s can map + /// to the same `use` item. + pub ast_id: FileAstId, + + /// Index of this `Import` when the containing `Use` is visited with `Path::expand_use_item`. + pub index: usize, +} + #[derive(Debug, Clone, Eq, PartialEq)] pub struct Function { pub name: Name, @@ -414,6 +443,9 @@ mod diagnostics { ModItem::TypeAlias(item) => { SyntaxNodePtr::new(item_tree.source(db, item).syntax()) } + ModItem::Import(item) => { + SyntaxNodePtr::new(item_tree.source(db, item).syntax()) + } } } } diff --git a/crates/mun_hir/src/item_tree/lower.rs b/crates/mun_hir/src/item_tree/lower.rs index 2b54a7464..b1198883c 100644 --- a/crates/mun_hir/src/item_tree/lower.rs +++ b/crates/mun_hir/src/item_tree/lower.rs @@ -4,20 +4,33 @@ use super::{ diagnostics, Field, Fields, Function, IdRange, ItemTree, ItemTreeData, ItemTreeNode, LocalItemTreeId, ModItem, RawVisibilityId, Struct, StructDefKind, TypeAlias, }; +use crate::item_tree::Import; use crate::{ arena::{Idx, RawId}, name::AsName, source_id::AstIdMap, type_ref::TypeRef, visibility::RawVisibility, - DefDatabase, FileId, Name, + DefDatabase, FileId, InFile, Name, Path, }; use mun_syntax::{ ast, ast::{ExternOwner, ModuleItemOwner, NameOwner, StructKind, TypeAscriptionOwner}, }; +use smallvec::SmallVec; use std::{collections::HashMap, convert::TryInto, marker::PhantomData, sync::Arc}; +struct ModItems(SmallVec<[ModItem; 1]>); + +impl From for ModItems +where + T: Into, +{ + fn from(t: T) -> Self { + ModItems(SmallVec::from_buf([t.into(); 1])) + } +} + impl From> for LocalItemTreeId { fn from(index: Idx) -> Self { LocalItemTreeId { @@ -50,25 +63,29 @@ impl Context { let top_level = item_owner .items() .flat_map(|item| self.lower_mod_item(&item)) + .flat_map(|items| items.0) .collect::>(); // Check duplicates let mut set = HashMap::::new(); for item in top_level.iter() { let name = match item { - ModItem::Function(item) => &self.data.functions[item.index].name, - ModItem::Struct(item) => &self.data.structs[item.index].name, - ModItem::TypeAlias(item) => &self.data.type_aliases[item.index].name, + ModItem::Function(item) => Some(&self.data.functions[item.index].name), + ModItem::Struct(item) => Some(&self.data.structs[item.index].name), + ModItem::TypeAlias(item) => Some(&self.data.type_aliases[item.index].name), + ModItem::Import(_) => None, }; - if let Some(first_item) = set.get(&name) { - self.diagnostics - .push(diagnostics::ItemTreeDiagnostic::DuplicateDefinition { - name: name.clone(), - first: **first_item, - second: *item, - }) - } else { - set.insert(name.clone(), item); + if let Some(name) = name { + if let Some(first_item) = set.get(&name) { + self.diagnostics + .push(diagnostics::ItemTreeDiagnostic::DuplicateDefinition { + name: name.clone(), + first: **first_item, + second: *item, + }) + } else { + set.insert(name.clone(), item); + } } } @@ -81,14 +98,49 @@ impl Context { } /// Lowers a single module item - fn lower_mod_item(&mut self, item: &ast::ModuleItem) -> Option { + fn lower_mod_item(&mut self, item: &ast::ModuleItem) -> Option { match item.kind() { ast::ModuleItemKind::FunctionDef(ast) => self.lower_function(&ast).map(Into::into), ast::ModuleItemKind::StructDef(ast) => self.lower_struct(&ast).map(Into::into), ast::ModuleItemKind::TypeAliasDef(ast) => self.lower_type_alias(&ast).map(Into::into), + ast::ModuleItemKind::Use(ast) => Some(ModItems( + self.lower_use(&ast) + .into_iter() + .map(Into::into) + .collect::>(), + )), } } + /// Lowers a `use` statement + fn lower_use(&mut self, use_item: &ast::Use) -> Vec> { + let visibility = self.lower_visibility(use_item); + let ast_id = self.source_ast_id_map.ast_id(use_item); + + // Every use item can expand to many `Import`s. + let mut imports = Vec::new(); + let tree = &mut self.data; + Path::expand_use_item( + InFile::new(self.file, use_item.clone()), + |path, _use_tree, is_glob, alias| { + imports.push( + tree.imports + .alloc(Import { + path, + alias, + visibility, + is_glob, + ast_id, + index: imports.len(), + }) + .into(), + ); + }, + ); + + imports + } + /// Lowers a function fn lower_function(&mut self, func: &ast::FunctionDef) -> Option> { let name = func.name()?.as_name(); diff --git a/crates/mun_hir/src/item_tree/tests.rs b/crates/mun_hir/src/item_tree/tests.rs index 1b72d48d6..6a4bd76a1 100644 --- a/crates/mun_hir/src/item_tree/tests.rs +++ b/crates/mun_hir/src/item_tree/tests.rs @@ -44,6 +44,9 @@ fn format_mod_item(out: &mut String, tree: &ItemTree, item: ModItem) -> fmt::Res ModItem::TypeAlias(item) => { write!(out, "{:?}", tree[item])?; } + ModItem::Import(item) => { + write!(out, "{:?}", tree[item])?; + } } for line in children.lines() { diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs index b440c3fb0..20a64ad86 100644 --- a/crates/mun_hir/src/lib.rs +++ b/crates/mun_hir/src/lib.rs @@ -22,7 +22,7 @@ mod item_tree; pub mod line_index; mod module_tree; mod name; -mod name_resolution; +pub(crate) mod name_resolution; mod path; mod primitive_type; mod resolve; diff --git a/crates/mun_hir/src/module_tree.rs b/crates/mun_hir/src/module_tree.rs index a18bec0f4..480ca50f3 100644 --- a/crates/mun_hir/src/module_tree.rs +++ b/crates/mun_hir/src/module_tree.rs @@ -10,7 +10,16 @@ use paths::RelativePath; use rustc_hash::FxHashMap; use std::sync::Arc; -/// Represents the tree of modules in a package. +/// Represents the tree of modules of a package. +/// +/// The `ModuleTree` is built by looking at all the source files of the source root of a package and +/// creating a tree based on their relative paths. See the [`ModuleTree::module_tree_query`] method. +/// When constructing the `ModuleTree` extra empty modules may be added for missing files. For +/// instance for the relative path `foo/bar/baz.mun`, besides the module `foo::bar::baz` the modules +/// `foo`, `foo::bar` get created along the way. +/// +/// A `ModuleTree` represent the inner connections between files. It can be used to query the +/// shortest path for use declarations #[derive(Debug, PartialEq, Eq)] pub struct ModuleTree { pub root: LocalModuleId, @@ -108,8 +117,8 @@ impl ModuleTree { }) } - /// Given a `RawVisibility` which describes the visibility of an item relative to a module into - /// a `Visibility` which describes the absolute visibility within the module tree. + /// Converts a `RawVisibility` which describes the visibility of an item relative to a module + /// into a `Visibility` which describes the absolute visibility within the module tree. pub(crate) fn resolve_visibility( &self, _db: &dyn DefDatabase, diff --git a/crates/mun_hir/src/name_resolution.rs b/crates/mun_hir/src/name_resolution.rs index 9399b58f9..74cef1508 100644 --- a/crates/mun_hir/src/name_resolution.rs +++ b/crates/mun_hir/src/name_resolution.rs @@ -2,3 +2,4 @@ mod path_resolution; mod per_ns; pub use self::per_ns::{Namespace, PerNs}; +pub use path_resolution::ReachedFixedPoint; diff --git a/crates/mun_hir/src/name_resolution/path_resolution.rs b/crates/mun_hir/src/name_resolution/path_resolution.rs index 5c9932351..c01d9937c 100644 --- a/crates/mun_hir/src/name_resolution/path_resolution.rs +++ b/crates/mun_hir/src/name_resolution/path_resolution.rs @@ -7,8 +7,11 @@ use crate::{ }; use std::iter::successors; +/// Indicates whether or not any newly resolved import statements will actually change the outcome +/// of an operation. This is useful to know if more iterations of an algorithm might be required, or +/// whether its hopeless. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(super) enum ReachedFixedPoint { +pub enum ReachedFixedPoint { Yes, No, } @@ -16,11 +19,11 @@ pub(super) enum ReachedFixedPoint { /// Contains the result of resolving a path. It contains how far the path was able to be resolved /// as well as the resolved values or types so far. #[derive(Debug, Clone)] -pub(super) struct ResolvePathResult { - pub(super) resolved_def: PerNs<(ItemDefinitionId, Visibility)>, - pub(super) segment_index: Option, - pub(super) reached_fixedpoint: ReachedFixedPoint, - pub(super) package: Option, +pub(crate) struct ResolvePathResult { + pub(crate) resolved_def: PerNs<(ItemDefinitionId, Visibility)>, + pub(crate) segment_index: Option, + pub(crate) reached_fixedpoint: ReachedFixedPoint, + pub(crate) package: Option, } impl ResolvePathResult { @@ -74,7 +77,7 @@ impl PackageDefs { /// Resolves the specified `path` from within the specified `module`. Also returns whether or /// not additions to the `PackageDef` would change the result or whether a fixed point has been /// reached. This is useful when resolving all imports. - pub(super) fn resolve_path_with_fixedpoint( + pub(crate) fn resolve_path_with_fixedpoint( &self, db: &dyn DefDatabase, original_module: LocalModuleId, diff --git a/crates/mun_hir/src/package_defs.rs b/crates/mun_hir/src/package_defs.rs index c45effca4..ee9efddfd 100644 --- a/crates/mun_hir/src/package_defs.rs +++ b/crates/mun_hir/src/package_defs.rs @@ -1,8 +1,10 @@ mod collector; +#[cfg(test)] +mod tests; use crate::{ arena::map::ArenaMap, item_scope::ItemScope, module_tree::LocalModuleId, - module_tree::ModuleTree, DefDatabase, PackageId, + module_tree::ModuleTree, DefDatabase, DiagnosticSink, PackageId, }; use std::{ops::Index, sync::Arc}; @@ -11,15 +13,29 @@ use std::{ops::Index, sync::Arc}; pub struct PackageDefs { pub modules: ArenaMap, pub module_tree: Arc, + diagnostics: Vec, } impl PackageDefs { + /// Constructs a `PackageDefs` for the specified `package` with the data from the `db`. pub(crate) fn package_def_map_query( db: &dyn DefDatabase, package: PackageId, ) -> Arc { Arc::new(collector::collect(db, package)) } + + /// Adds all the diagnostics for the specified `module` to the `sink`. + pub fn add_diagnostics( + &self, + db: &dyn DefDatabase, + module: LocalModuleId, + sink: &mut DiagnosticSink, + ) { + for diagnostic in self.diagnostics.iter() { + diagnostic.add_to(db, module, sink) + } + } } impl Index for PackageDefs { @@ -29,3 +45,100 @@ impl Index for PackageDefs { &self.modules[index] } } + +mod diagnostics { + use crate::diagnostics::{ImportDuplicateDefinition, UnresolvedImport}; + use crate::{ + module_tree::LocalModuleId, source_id::AstId, AstDatabase, DefDatabase, DiagnosticSink, + InFile, Path, + }; + use mun_syntax::ast::Use; + use mun_syntax::{ast, AstPtr}; + + /// A type of diagnostic that may be emitted during resolving all package definitions. + #[derive(Debug, PartialEq, Eq)] + enum DiagnosticKind { + UnresolvedImport { ast: AstId, index: usize }, + DuplicateImport { ast: AstId, index: usize }, + } + + /// A diagnostic that may be emitted during resolving all package definitions. + #[derive(Debug, PartialEq, Eq)] + pub(super) struct DefDiagnostic { + /// The module that contains the diagnostic + in_module: LocalModuleId, + + /// The type of diagnostic + kind: DiagnosticKind, + } + + impl DefDiagnostic { + /// Constructs a new `DefDiagnostic` which indicates that an import could not be resolved. + pub(super) fn unresolved_import( + container: LocalModuleId, + ast: AstId, + index: usize, + ) -> Self { + Self { + in_module: container, + kind: DiagnosticKind::UnresolvedImport { ast, index }, + } + } + + /// Constructs a new `DefDiagnostic` which indicates that an import names a duplication. + pub(super) fn duplicate_import( + container: LocalModuleId, + ast: AstId, + index: usize, + ) -> Self { + Self { + in_module: container, + kind: DiagnosticKind::DuplicateImport { ast, index }, + } + } + + pub(super) fn add_to( + &self, + db: &dyn DefDatabase, + target_module: LocalModuleId, + sink: &mut DiagnosticSink, + ) { + if self.in_module != target_module { + return; + } + + match &self.kind { + DiagnosticKind::UnresolvedImport { ast, index } => { + if let Some(use_tree) = use_tree_ptr_from_ast(db.upcast(), ast, *index) { + sink.push(UnresolvedImport { use_tree }); + } + } + DiagnosticKind::DuplicateImport { ast, index } => { + if let Some(use_tree) = use_tree_ptr_from_ast(db.upcast(), ast, *index) { + sink.push(ImportDuplicateDefinition { use_tree }); + } + } + } + + fn use_tree_ptr_from_ast( + db: &dyn AstDatabase, + ast: &AstId, + index: usize, + ) -> Option>> { + let use_item = ast.to_node(db); + let mut cur = 0; + let mut tree = None; + Path::expand_use_item( + InFile::new(ast.file_id, use_item), + |_path, use_tree, _is_glob, _alias| { + if cur == index { + tree = Some(use_tree.clone()) + } + cur += 1; + }, + ); + tree.map(|t| InFile::new(ast.file_id, AstPtr::new(&t))) + } + } + } +} diff --git a/crates/mun_hir/src/package_defs/collector.rs b/crates/mun_hir/src/package_defs/collector.rs index dad03ad4a..7d136b4a0 100644 --- a/crates/mun_hir/src/package_defs/collector.rs +++ b/crates/mun_hir/src/package_defs/collector.rs @@ -1,23 +1,114 @@ use super::PackageDefs; use crate::{ - arena::map::ArenaMap, - ids::{FunctionLoc, Intern, ItemDefinitionId, ModuleId, StructLoc, TypeAliasLoc}, - item_scope::ItemScope, + ids::ItemDefinitionId, + ids::{FunctionLoc, Intern, StructLoc, TypeAliasLoc}, + item_scope::ImportType, + item_scope::{ItemScope, PerNsGlobImports}, item_tree::{ - Function, ItemTree, ItemTreeId, LocalItemTreeId, ModItem, Struct, StructDefKind, TypeAlias, + self, Function, ItemTree, ItemTreeId, LocalItemTreeId, ModItem, Struct, StructDefKind, + TypeAlias, }, - module_tree::{LocalModuleId, ModuleTree}, + module_tree::LocalModuleId, + name_resolution::ReachedFixedPoint, + package_defs::diagnostics::DefDiagnostic, + path::ImportAlias, visibility::RawVisibility, - DefDatabase, FileId, Name, PackageId, PerNs, Visibility, + DefDatabase, FileId, InFile, ModuleId, Name, PackageId, Path, PerNs, Visibility, }; -use std::sync::Arc; +use rustc_hash::FxHashMap; + +/// Result of resolving an import statement +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum PartiallyResolvedImport { + /// None of the namespaces are resolved + Unresolved, + /// One of namespaces is resolved. + Indeterminate(PerNs<(ItemDefinitionId, Visibility)>), + /// All namespaces are resolved, OR it came from another crate + Resolved(PerNs<(ItemDefinitionId, Visibility)>), +} + +/// The result of an import directive +#[derive(Clone, Debug, Eq, PartialEq)] +struct ImportResolution { + /// The name to expose the resolution as + name: Option, + + /// The resolution itself + resolution: PerNs<(ItemDefinitionId, Visibility)>, +} + +impl PartiallyResolvedImport { + fn namespaces(&self) -> PerNs<(ItemDefinitionId, Visibility)> { + match self { + PartiallyResolvedImport::Unresolved => PerNs::none(), + PartiallyResolvedImport::Indeterminate(ns) => *ns, + PartiallyResolvedImport::Resolved(ns) => *ns, + } + } +} + +/// Definition of a single import statement +#[derive(Clone, Debug, Eq, PartialEq)] +struct Import { + /// The path of the import (e.g. foo::Bar). Note that group imports have been desugared, each + /// item in the import tree is a seperate import. + pub path: Path, + + /// The alias for this import statement + pub alias: Option, + + /// The visibility of the import in the file the import statement resides in + pub visibility: RawVisibility, + + /// Whether or not this is a * import. + pub is_glob: bool, + + /// The original location of the import + source: ItemTreeId, +} + +impl Import { + /// Constructs an `Import` from a `use` statement in an `ItemTree`. + fn from_use(tree: &ItemTree, id: ItemTreeId) -> Self { + let it = &tree[id.value]; + let visibility = &tree[it.visibility]; + Self { + path: it.path.clone(), + alias: it.alias.clone(), + visibility: visibility.clone(), + is_glob: it.is_glob, + source: id, + } + } +} + +/// A struct that keeps track of the state of an import directive. +#[derive(Clone, Debug, Eq, PartialEq)] +struct ImportDirective { + /// The module that defines the import statement + module_id: LocalModuleId, + + /// Information about the import statement. + import: Import, + + /// The current status of the import. + status: PartiallyResolvedImport, +} pub(super) fn collect(db: &dyn DefDatabase, package_id: PackageId) -> PackageDefs { let mut collector = DefCollector { db, package_id, - modules: Default::default(), - module_tree: db.module_tree(package_id), + package_defs: PackageDefs { + modules: Default::default(), + module_tree: db.module_tree(package_id), + diagnostics: Default::default(), + }, + unresolved_imports: Default::default(), + resolved_imports: Default::default(), + glob_imports: Default::default(), + from_glob_import: Default::default(), }; collector.collect(); collector.finish() @@ -27,26 +118,86 @@ pub(super) fn collect(db: &dyn DefDatabase, package_id: PackageId) -> PackageDef struct DefCollector<'db> { db: &'db dyn DefDatabase, package_id: PackageId, - modules: ArenaMap, - module_tree: Arc, + package_defs: PackageDefs, + unresolved_imports: Vec, + resolved_imports: Vec, + + /// A mapping from local module to wildcard imports of other modules + glob_imports: + FxHashMap)>>, + + /// A list of all items that have been imported via a wildcard + from_glob_import: PerNsGlobImports, } impl<'db> DefCollector<'db> { /// Collects all information and stores it in the instance fn collect(&mut self) { // Collect all definitions in each module - let module_tree = self.module_tree.clone(); + let module_tree = self.package_defs.module_tree.clone(); + + // Start by collecting the definitions from all modules. This ensures that, for every module, + // all local definitions are accessible. This is the starting point for the import + // resolution. + collect_modules_recursive(self, module_tree.root, None); + + // Now, as long as we have unresolved imports, try to resolve them, or part of them. + while !self.unresolved_imports.is_empty() { + // Keep track of whether we were able to resolve anything + let mut resolved_something = false; + + // Get all the current unresolved import directives + let imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); + + // For each import, try to resolve it with the current state. + for mut directive in imports { + // Resolve the import + directive.status = self.resolve_import(directive.module_id, &directive.import); + // Check the status of the import, if the import is still considered unresolved, try + // again in the next round. + match directive.status { + PartiallyResolvedImport::Indeterminate(_) => { + self.record_resolved_import(&directive); + // FIXME: To avoid performance regression, we consider an import resolved + // if it is indeterminate (i.e not all namespace resolved). This might not + // completely resolve correctly in the future if we can have values and + // types with the same name. + self.resolved_imports.push(directive); + resolved_something = true; + } + PartiallyResolvedImport::Resolved(_) => { + self.record_resolved_import(&directive); + self.resolved_imports.push(directive); + resolved_something = true; + } + PartiallyResolvedImport::Unresolved => { + self.unresolved_imports.push(directive); + } + } + } + + // If nothing actually changed up to this point, stop resolving. + if !resolved_something { + break; + } + } + + /// Recursively iterate over all modules in the `ModuleTree` and add them and their + /// definitions to their corresponding `ItemScope`. fn collect_modules_recursive( collector: &mut DefCollector, module_id: LocalModuleId, parent: Option<(Name, LocalModuleId)>, ) { // Insert an empty item scope for this module, this will be filled in. - collector.modules.insert(module_id, ItemScope::default()); + collector + .package_defs + .modules + .insert(module_id, ItemScope::default()); // If there is a file associated with the module, collect all definitions from it - let module_data = &collector.module_tree[module_id]; + let module_data = &collector.package_defs.module_tree[module_id]; if let Some(file_id) = module_data.file { let item_tree = collector.db.item_tree(file_id); let mut mod_collector = ModCollectorContext { @@ -61,7 +212,7 @@ impl<'db> DefCollector<'db> { // Insert this module into the scope of the parent if let Some((name, parent)) = parent { - collector.modules[parent].add_resolution( + collector.package_defs.modules[parent].add_resolution( name, PerNs::from_definition( ModuleId { @@ -76,7 +227,7 @@ impl<'db> DefCollector<'db> { } // Iterate over all children - let child_module_ids = collector.module_tree[module_id] + let child_module_ids = collector.package_defs.module_tree[module_id] .children .iter() .map(|(name, local_id)| (name.clone(), *local_id)) @@ -85,16 +236,250 @@ impl<'db> DefCollector<'db> { collect_modules_recursive(collector, child_module_id, Some((name, module_id))); } }; + } - collect_modules_recursive(self, module_tree.root, None); + /// Given an import, try to resolve it. + fn resolve_import(&self, module_id: LocalModuleId, import: &Import) -> PartiallyResolvedImport { + let res = self + .package_defs + .resolve_path_with_fixedpoint(self.db, module_id, &import.path); + + let def = res.resolved_def; + if res.reached_fixedpoint == ReachedFixedPoint::No || def.is_none() { + return PartiallyResolvedImport::Unresolved; + } + + if let Some(package) = res.package { + if package != self.package_defs.module_tree.package { + return PartiallyResolvedImport::Resolved(def); + } + } + + // Check whether all namespaces have been resolved + if def.take_types().is_some() && def.take_values().is_some() { + PartiallyResolvedImport::Resolved(def) + } else { + PartiallyResolvedImport::Indeterminate(def) + } + } + + /// Records ands propagates the resolution of an import directive. + fn record_resolved_import(&mut self, directive: &ImportDirective) { + let import_module_id = directive.module_id; + let import = &directive.import; + + // Get the resolved definition of the use statement + let resolution = directive.status.namespaces(); + + // Get the visibility of the import statement + let import_visibility = self.package_defs.module_tree.resolve_visibility( + self.db, + import_module_id, + &directive.import.visibility, + ); + + if import.is_glob { + match resolution.take_types() { + Some((ItemDefinitionId::ModuleId(m), _)) => { + let scope = &self.package_defs[m.local_id]; + + // Get all the items that are visible from this module + let resolutions = scope + .entries() + .map(|(n, res)| ImportResolution { + name: Some(n.clone()), + resolution: res.and_then(|(item, vis)| { + if vis.is_visible_from_module_tree( + &self.package_defs.module_tree, + import_module_id, + ) { + Some((item, vis)) + } else { + None + } + }), + }) + .filter(|res| !res.resolution.is_none()) + .collect::>(); + + self.update( + import_module_id, + import_visibility, + ImportType::Glob, + import.source, + &resolutions, + ); + + // Record the wildcard import in case new items are added to the module we are importing + let glob = self.glob_imports.entry(m.local_id).or_default(); + if !glob.iter().any(|(m, _, _)| *m == import_module_id) { + glob.push((import_module_id, import_visibility, import.source)); + } + } + Some((_, _)) => { + // Happens when wildcard importing something other than a module. I guess it's ok to do nothing here? + } + None => { + // Happens if a wildcard import refers to something other than a type? + } + } + } else { + match import.path.segments.last() { + Some(last_segment) => { + let name = match &import.alias { + Some(ImportAlias::Alias(name)) => Some(name.clone()), + Some(ImportAlias::Underscore) => None, + None => Some(last_segment.clone()), + }; + + self.update( + import_module_id, + import_visibility, + ImportType::Named, + import.source, + &[ImportResolution { name, resolution }], + ); + } + None => unreachable!(), + } + } + } + + /// Updates the current state with the resolutions of an import statement. + fn update( + &mut self, + import_module_id: LocalModuleId, + import_visibility: Visibility, + import_type: ImportType, + import_source: ItemTreeId, + resolutions: &[ImportResolution], + ) { + self.update_recursive( + import_module_id, + import_visibility, + import_type, + import_source, + resolutions, + 0, + ); + } + + /// Updates the current state with the resolutions of an import statement. Also recursively + /// updates any wildcard imports. + fn update_recursive( + &mut self, + import_module_id: LocalModuleId, + import_visibility: Visibility, + import_type: ImportType, + import_source: ItemTreeId, + resolutions: &[ImportResolution], + depth: usize, + ) { + if depth > 100 { + // prevent stack overflows (but this shouldn't be possible) + panic!("infinite recursion in glob imports!"); + } + + let scope = &mut self.package_defs.modules[import_module_id]; + + let mut changed = false; + for ImportResolution { name, resolution } in resolutions { + // TODO(#309): Add an error if the visibility of the item does not allow exposing with the + // import visibility. e.g.: + // ```mun + // //- foo.mun + // pub(package) struct Foo; + // + // //- main.mun + // pub foo::Foo; // This is not allowed because Foo is only public within the package. + // ``` + + match name { + Some(name) => { + let add_result = scope.add_resolution_from_import( + &mut self.from_glob_import, + (import_module_id, name.clone()), + resolution.map(|(item, _)| (item, import_visibility)), + import_type, + ); + + if add_result.changed { + changed = true; + } + if add_result.duplicate { + let item_tree = self.db.item_tree(import_source.file_id); + let import_data = &item_tree[import_source.value]; + self.package_defs + .diagnostics + .push(DefDiagnostic::duplicate_import( + import_module_id, + InFile::new(import_source.file_id, import_data.ast_id), + import_data.index, + )) + } + } + None => { + // This is not yet implemented (bringing in types into scope without a name). + // This might be useful for traits. e.g.: + // ```mun + // use foo::SomeTrait as _; // Should be able to call methods added by SomeTrait. + // ``` + continue; + } + } + } + + // If nothing changed, there is also no point in updating the wildcard imports + if !changed { + return; + } + + let glob_imports = self + .glob_imports + .get(&import_module_id) + .into_iter() + .flat_map(|v| v.iter()) + .filter(|(glob_importing_module, _, _)| { + import_visibility.is_visible_from_module_tree( + &self.package_defs.module_tree, + *glob_importing_module, + ) + }) + .cloned() + .collect::>(); + + for (glob_importing_module, glob_import_vis, glob_import_source) in glob_imports { + self.update_recursive( + glob_importing_module, + glob_import_vis, + ImportType::Glob, + glob_import_source, + resolutions, + depth + 1, + ) + } } /// Create the `PackageDefs` struct that holds all the items fn finish(self) -> PackageDefs { - PackageDefs { - modules: self.modules, - module_tree: self.module_tree, + let mut package_defs = self.package_defs; + + // Create diagnostics for all unresolved imports + for directive in self.unresolved_imports.iter() { + let import = &directive.import; + let item_tree = self.db.item_tree(import.source.file_id); + let import_data = &item_tree[import.source.value]; + + package_defs + .diagnostics + .push(DefDiagnostic::unresolved_import( + directive.module_id, + InFile::new(import.source.file_id, import_data.ast_id), + import_data.index, + )) } + + package_defs } } @@ -113,6 +498,7 @@ impl<'a> ModCollectorContext<'a, '_> { ModItem::Function(id) => self.collect_function(id), ModItem::Struct(id) => self.collect_struct(id), ModItem::TypeAlias(id) => self.collect_type_alias(id), + ModItem::Import(id) => self.collect_import(id), }; if let Some(DefData { @@ -122,20 +508,30 @@ impl<'a> ModCollectorContext<'a, '_> { has_constructor, }) = definition { - self.def_collector.modules[self.module_id].add_definition(id); - let visibility = self.def_collector.module_tree.resolve_visibility( - self.def_collector.db, - self.module_id, - visibility, - ); - self.def_collector.modules[self.module_id].add_resolution( + self.def_collector.package_defs.modules[self.module_id].add_definition(id); + let visibility = self + .def_collector + .package_defs + .module_tree + .resolve_visibility(self.def_collector.db, self.module_id, visibility); + self.def_collector.package_defs.modules[self.module_id].add_resolution( name.clone(), PerNs::from_definition(id, visibility, has_constructor), - ) + ); } } } + /// Collects the definition data from an import statement. + fn collect_import(&mut self, id: LocalItemTreeId) -> Option> { + self.def_collector.unresolved_imports.push(ImportDirective { + module_id: self.module_id, + import: Import::from_use(&self.item_tree, InFile::new(self.file_id, id)), + status: PartiallyResolvedImport::Unresolved, + }); + None + } + /// Collects the definition data from a `Function` fn collect_function(&self, id: LocalItemTreeId) -> Option> { let func = &self.item_tree[id]; diff --git a/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_.snap b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_.snap new file mode 100644 index 000000000..0aadc8aae --- /dev/null +++ b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_.snap @@ -0,0 +1,15 @@ +--- +source: crates/mun_hir/src/package_defs/tests.rs +expression: "//- /bar.mun\nuse package::Foo;\npub struct Bar(Foo);\n\n//- /mod.mun\npub use foo::Foo; // Re-export a child's definition\n\nstruct Baz;\n\n//- /foo.mun\nuse package::{bar::Bar, Baz};\n\npub struct Foo {\n baz: Baz, // Can use private definitions from any of its ancestors\n}\n\npub fn foo_from_bar(bar: Bar) -> Foo {\n bar.0\n}" +--- +mod mod ++-- struct Baz ++-- use struct package::foo::Foo ++-- mod bar +| +-- struct Bar +| '-- use struct package::foo::Foo +'-- mod foo + +-- fn foo_from_bar + +-- struct Foo + +-- use struct package::Baz + '-- use struct package::bar::Bar diff --git a/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_alias.snap b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_alias.snap new file mode 100644 index 000000000..dc924e051 --- /dev/null +++ b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_alias.snap @@ -0,0 +1,12 @@ +--- +source: crates/mun_hir/src/package_defs/tests.rs +expression: "//- /foo.mun\npub struct Ok;\n\n//- /bar.mun\npub use package::foo::Ok as ReallyOk;\n\npub struct Ok;\n\n//- /baz.mun\nuse package::bar::ReallyOk;" +--- +mod mod ++-- mod bar +| +-- struct Ok +| '-- use struct package::foo::Ok ++-- mod baz +| '-- use struct package::foo::Ok +'-- mod foo + '-- struct Ok diff --git a/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_cyclic.snap b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_cyclic.snap new file mode 100644 index 000000000..91999dc4f --- /dev/null +++ b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_cyclic.snap @@ -0,0 +1,14 @@ +--- +source: crates/mun_hir/src/package_defs/tests.rs +expression: "//- /foo.mun\nuse super::baz::Cyclic;\n\npub struct Ok;\n\n//- /bar.mun\nuse super::foo::{Cyclic, Ok};\n\n//- /baz.mun\nuse super::bar::{Cyclic, Ok};" +--- +mod mod ++-- mod bar +| +-- ERROR: [17; 23): unresolved import +| '-- use struct package::foo::Ok ++-- mod baz +| +-- ERROR: [17; 23): unresolved import +| '-- use struct package::foo::Ok +'-- mod foo + +-- ERROR: [4; 22): unresolved import + '-- struct Ok diff --git a/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_cyclic_wildcard.snap b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_cyclic_wildcard.snap new file mode 100644 index 000000000..cb15df62d --- /dev/null +++ b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_cyclic_wildcard.snap @@ -0,0 +1,11 @@ +--- +source: crates/mun_hir/src/package_defs/tests.rs +expression: "//- /foo.mun\npub use super::baz::*;\n\npub struct Ok;\n\n//- /baz.mun\npub use super::foo::{self, *};" +--- +mod mod ++-- mod baz +| +-- use struct package::foo::Ok +| '-- use mod package::foo +'-- mod foo + +-- struct Ok + '-- use mod package::foo diff --git a/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_duplicate_name.snap b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_duplicate_name.snap new file mode 100644 index 000000000..f4fee7f1e --- /dev/null +++ b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_duplicate_name.snap @@ -0,0 +1,10 @@ +--- +source: crates/mun_hir/src/package_defs/tests.rs +expression: "//- /foo.mun\npub struct Ok;\n\n//- /bar.mun\nuse package::foo::Ok;\n\npub struct Ok;" +--- +mod mod ++-- mod bar +| +-- ERROR: [4; 20): a second item with the same name imported. Try to use an alias. +| '-- struct Ok +'-- mod foo + '-- struct Ok diff --git a/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_self.snap b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_self.snap new file mode 100644 index 000000000..571b8cde5 --- /dev/null +++ b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_self.snap @@ -0,0 +1,10 @@ +--- +source: crates/mun_hir/src/package_defs/tests.rs +expression: "//- /foo.mun\npub struct Ok;\n\n//- /bar.mun\nuse super::foo::{self};\nuse foo::Ok;" +--- +mod mod ++-- mod bar +| +-- use struct package::foo::Ok +| '-- use mod package::foo +'-- mod foo + '-- struct Ok diff --git a/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_unresolved.snap b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_unresolved.snap new file mode 100644 index 000000000..61f6ac0e7 --- /dev/null +++ b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_unresolved.snap @@ -0,0 +1,10 @@ +--- +source: crates/mun_hir/src/package_defs/tests.rs +expression: "//- /foo.mun\npub struct Foo;\n\n//- /mod.mun\nuse foo::Foo; // works\nuse foo::Bar; // doesnt work (Bar does not exist)\nuse baz::Baz; // doesnt work (baz does not exist)" +--- +mod mod ++-- ERROR: [29; 37): unresolved import ++-- ERROR: [81; 89): unresolved import ++-- use struct package::foo::Foo +'-- mod foo + '-- struct Foo diff --git a/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_wildcard.snap b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_wildcard.snap new file mode 100644 index 000000000..a793b22fd --- /dev/null +++ b/crates/mun_hir/src/package_defs/snapshots/mun_hir__package_defs__tests__use_wildcard.snap @@ -0,0 +1,13 @@ +--- +source: crates/mun_hir/src/package_defs/tests.rs +expression: "//- /foo.mun\npub struct Foo;\n\n//- /foo/bar.mun\npub use super::Foo;\npub struct FooBar;\n\n//- /bar.mun\nuse package::foo::bar::*; // Should reference two structs (Foo and FooBar)" +--- +mod mod ++-- mod bar +| +-- use struct package::foo::Foo +| '-- use struct package::foo::bar::FooBar +'-- mod foo + +-- struct Foo + '-- mod bar + +-- struct FooBar + '-- use struct package::foo::Foo diff --git a/crates/mun_hir/src/package_defs/tests.rs b/crates/mun_hir/src/package_defs/tests.rs new file mode 100644 index 000000000..11453d332 --- /dev/null +++ b/crates/mun_hir/src/package_defs/tests.rs @@ -0,0 +1,288 @@ +use crate::{ + db::DefDatabase, fixture::WithFixture, ids::ItemDefinitionId, mock::MockDatabase, + package_defs::PackageDefs, DiagnosticSink, Function, HirDatabase, Module, Package, Struct, + TypeAlias, +}; +use itertools::Itertools; +use rustc_hash::FxHashSet; + +#[test] +fn use_alias() { + resolve_snapshot( + r#" + //- /foo.mun + pub struct Ok; + + //- /bar.mun + pub use package::foo::Ok as ReallyOk; + + pub struct Ok; + + //- /baz.mun + use package::bar::ReallyOk; + "#, + ) +} + +#[test] +fn use_duplicate_name() { + resolve_snapshot( + r#" + //- /foo.mun + pub struct Ok; + + //- /bar.mun + use package::foo::Ok; + + pub struct Ok; + "#, + ) +} + +#[test] +fn use_cyclic_wildcard() { + resolve_snapshot( + r#" + //- /foo.mun + pub use super::baz::*; + + pub struct Ok; + + //- /baz.mun + pub use super::foo::{self, *}; + "#, + ) +} + +#[test] +fn use_wildcard() { + resolve_snapshot( + r#" + //- /foo.mun + pub struct Foo; + + //- /foo/bar.mun + pub use super::Foo; + pub struct FooBar; + + //- /bar.mun + use package::foo::bar::*; // Should reference two structs (Foo and FooBar) + "#, + ) +} + +#[test] +fn use_self() { + resolve_snapshot( + r#" + //- /foo.mun + pub struct Ok; + + //- /bar.mun + use super::foo::{self}; + use foo::Ok; + "#, + ) +} + +#[test] +fn use_cyclic() { + resolve_snapshot( + r#" + //- /foo.mun + use super::baz::Cyclic; + + pub struct Ok; + + //- /bar.mun + use super::foo::{Cyclic, Ok}; + + //- /baz.mun + use super::bar::{Cyclic, Ok}; + "#, + ) +} + +#[test] +fn use_unresolved() { + resolve_snapshot( + r#" + //- /foo.mun + pub struct Foo; + + //- /mod.mun + use foo::Foo; // works + use foo::Bar; // doesnt work (Bar does not exist) + use baz::Baz; // doesnt work (baz does not exist) + "#, + ) +} + +#[test] +fn use_() { + resolve_snapshot( + r#" + //- /bar.mun + use package::Foo; + pub struct Bar(Foo); + + //- /mod.mun + pub use foo::Foo; // Re-export a child's definition + + struct Baz; + + //- /foo.mun + use package::{bar::Bar, Baz}; + + pub struct Foo { + baz: Baz, // Can use private definitions from any of its ancestors + } + + pub fn foo_from_bar(bar: Bar) -> Foo { + bar.0 + } + "#, + ) +} + +fn resolve_snapshot(text: &str) { + let text = text.trim().replace("\n ", "\n"); + let resolved = resolve(&text); + insta::assert_snapshot!(insta::_macro_support::AutoName, resolved.trim(), &text); +} + +fn resolve(content: &str) -> String { + let db = MockDatabase::with_files(content); + + Package::all(&db) + .iter() + .map(|package| { + let package_defs = db.package_defs(package.id); + tree_for_module(&db, &package_defs, package.root_module(&db)).to_string() + }) + .intersperse("\n".to_owned()) + .collect() +} + +fn tree_for_module( + db: &dyn HirDatabase, + package_defs: &PackageDefs, + module: Module, +) -> text_trees::StringTreeNode { + // Construct a tree node + let mut node = text_trees::StringTreeNode::new(format!( + "mod {}", + module + .name(db) + .map(|name| name.to_string()) + .unwrap_or_else(|| "mod".to_owned()) + )); + + // Add module level diagnostics + let mut diag_sink = DiagnosticSink::new(|diag| { + node.push(format!( + "ERROR: {}: {}", + diag.highlight_range(), + diag.message() + )); + }); + module.diagnostics(db, &mut diag_sink); + drop(diag_sink); + + // Iterate over all declarations and add them as nodes + let scope = &package_defs[module.id.local_id]; + let local_declarations = scope.declarations().collect::>(); + let used_declarations = scope + .entries() + .filter_map(|entry| entry.1.take_types().map(|(def, _)| def)) + .collect::>(); + for def in local_declarations.iter().chain( + used_declarations + .iter() + .filter(|decl| !local_declarations.contains(*decl)), + ) { + let is_local = local_declarations.contains(&def); + match def { + ItemDefinitionId::ModuleId(m) => { + if m.package == module.id.package + && module + .children(db) + .into_iter() + .find(|child_id| child_id.id == *m) + .is_none() + { + let module: Module = (*m).into(); + node.push(format!( + "use mod {}", + fully_qualified_module_path(db, module) + )); + } + } + ItemDefinitionId::FunctionId(f) => { + let func: Function = (*f).into(); + let name = func.name(db); + if is_local { + node.push(format!("fn {}", name)); + } else { + let fully_qualified_name = format!( + "{}::{}", + fully_qualified_module_path(db, func.module(db)), + name + ); + node.push(format!("use fn {}", fully_qualified_name)); + } + } + ItemDefinitionId::StructId(s) => { + let strukt: Struct = (*s).into(); + let name = strukt.name(db); + if is_local { + node.push(format!("struct {}", name)); + } else { + let fully_qualified_name = format!( + "{}::{}", + fully_qualified_module_path(db, strukt.module(db)), + name + ); + node.push(format!("use struct {}", fully_qualified_name)); + } + } + ItemDefinitionId::TypeAliasId(alias) => { + let alias: TypeAlias = (*alias).into(); + let name = alias.name(db); + if is_local { + node.push(format!("type {}", name)); + } else { + let fully_qualified_name = format!( + "{}::{}", + fully_qualified_module_path(db, alias.module(db)), + name + ); + node.push(format!("use type {}", fully_qualified_name)); + } + } + ItemDefinitionId::PrimitiveType(_) => {} + } + } + + // Iterate over all children of this module + for child_module in module.children(db) { + node.push_node(tree_for_module(db, package_defs, child_module)) + } + + node +} + +/// Returns a fully qualified path of a module e.g. `package::foo::bar::baz` +fn fully_qualified_module_path(db: &dyn HirDatabase, module: Module) -> String { + module + .path_to_root(db) + .into_iter() + .map(|m| { + m.name(db) + .map(|name| name.to_string()) + .unwrap_or_else(|| "package".to_owned()) + }) + .rev() + .intersperse("::".to_string()) + .collect::() +} diff --git a/crates/mun_hir/src/path.rs b/crates/mun_hir/src/path.rs index c7f79ed60..b09f5dcab 100644 --- a/crates/mun_hir/src/path.rs +++ b/crates/mun_hir/src/path.rs @@ -1,5 +1,6 @@ -use crate::{AsName, Name}; +use crate::{AsName, InFile, Name}; use mun_syntax::ast; +use mun_syntax::ast::{NameOwner, PathSegmentKind}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Path { @@ -15,6 +16,15 @@ pub enum PathKind { Package, } +/// A possible import alias e.g. `Foo as Bar` or `Foo as _`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ImportAlias { + /// Unnamed alias, as in `use Foo as _;` + Underscore, + /// Named alias + Alias(Name), +} + impl Path { /// Converts an `ast::Path` to `Path`. pub fn from_ast(mut path: ast::Path) -> Option { @@ -67,6 +77,118 @@ impl Path { } None } + + /// Constructs a path from its segments. + pub fn from_segments(kind: PathKind, segments: impl IntoIterator) -> Path { + let segments = segments.into_iter().collect::>(); + Path { kind, segments } + } + + /// Calls `cb` with all paths, represented by this use item. For the use statement: + /// ```mun + /// use foo::{self, Bar}; + /// ``` + /// the function will call the callback twice. Once for `foo` and once for `foo::Bar`. + pub(crate) fn expand_use_item( + item_src: InFile, + mut cb: impl FnMut(Path, &ast::UseTree, /* is_glob */ bool, Option), + ) { + if let Some(tree) = item_src.value.use_tree() { + lower_use_tree(None, &tree, &mut cb); + } + } +} + +/// Given an `ast::UseTree` and an optional prefix, call a callback function for every item that is +/// contained in the import tree. +/// +/// For the use statement: +/// ```mun +/// use foo::{self, Bar}; +/// ``` +/// the function will call the callback twice. Once for `foo` and once for `foo::Bar`. +fn lower_use_tree( + prefix: Option, + tree: &ast::UseTree, + cb: &mut dyn FnMut(Path, &ast::UseTree, bool, Option), +) { + if let Some(use_tree_list) = tree.use_tree_list() { + let prefix = match tree.path() { + None => prefix, + Some(path) => convert_path(prefix, &path), + }; + for child_tree in use_tree_list.use_trees() { + lower_use_tree(prefix.clone(), &child_tree, cb); + } + } else { + let alias = tree.rename().map(|a| { + a.name() + .map(|it| it.as_name()) + .map_or(ImportAlias::Underscore, ImportAlias::Alias) + }); + + let is_glob = tree.has_star_token(); + if let Some(ast_path) = tree.path() { + // Handle self in a path. + if ast_path.qualifier().is_none() { + if let Some(segment) = ast_path.segment() { + if segment.kind() == Some(ast::PathSegmentKind::SelfKw) { + if let Some(prefix) = prefix { + cb(prefix, &tree, false, alias); + return; + } + } + } + } + if let Some(path) = convert_path(prefix, &ast_path) { + cb(path, &tree, is_glob, alias) + } + } else if is_glob { + if let Some(prefix) = prefix { + cb(prefix, &tree, is_glob, None) + } + } + } +} + +/// Constructs a `hir::Path` from an `ast::Path` and an optional prefix. +fn convert_path(prefix: Option, path: &ast::Path) -> Option { + let prefix = if let Some(qualifier) = path.qualifier() { + Some(convert_path(prefix, &qualifier)?) + } else { + prefix + }; + + let segment = path.segment()?; + let res = match segment.kind()? { + ast::PathSegmentKind::Name(name_ref) => { + let mut res = prefix.unwrap_or_else(|| Path { + kind: PathKind::Plain, + segments: Vec::with_capacity(1), + }); + res.segments.push(name_ref.as_name()); + res + } + ast::PathSegmentKind::PackageKw => { + if prefix.is_some() { + return None; + } + Path::from_segments(PathKind::Package, std::iter::empty()) + } + PathSegmentKind::SelfKw => { + if prefix.is_some() { + return None; + } + Path::from_segments(PathKind::Super(0), std::iter::empty()) + } + PathSegmentKind::SuperKw => { + if prefix.is_some() { + return None; + } + Path::from_segments(PathKind::Super(1), std::iter::empty()) + } + }; + Some(res) } impl From for Path { diff --git a/crates/mun_hir/src/source_id.rs b/crates/mun_hir/src/source_id.rs index 7bc7a3128..cf0fa449a 100644 --- a/crates/mun_hir/src/source_id.rs +++ b/crates/mun_hir/src/source_id.rs @@ -1,7 +1,6 @@ use crate::{ arena::{Arena, Idx}, db::AstDatabase, - db::DefDatabase, in_file::InFile, FileId, }; @@ -18,7 +17,7 @@ use std::{ pub(crate) type AstId = InFile>; impl AstId { - pub fn to_node(&self, db: &dyn DefDatabase) -> N { + pub fn to_node(&self, db: &dyn AstDatabase) -> N { let root = db.parse(self.file_id); db.ast_id_map(self.file_id) .get(self.value) diff --git a/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__recursive_alias.snap b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__recursive_alias.snap index 2e68fdb9a..a14bdb02b 100644 --- a/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__recursive_alias.snap +++ b/crates/mun_hir/src/ty/snapshots/mun_hir__ty__tests__recursive_alias.snap @@ -2,13 +2,12 @@ source: crates/mun_hir/src/ty/tests.rs expression: "struct Foo {}\ntype Foo = Foo;\n\ntype A = B;\ntype B = A;\n\nfn main() {\n let a: Foo; // error: unknown type\n let b: A; // error: unknown type\n let c: B; // error: unknown type\n}" --- -[25; 28): cyclic type +[14; 29): the name `Foo` is defined multiple times [40; 41): cyclic type [52; 53): cyclic type -[79; 82): cyclic type [119; 120): cyclic type [159; 160): cyclic type [66; 189) '{ ...type }': nothing -[76; 77) 'a': {unknown} +[76; 77) 'a': Foo [116; 117) 'b': {unknown} [156; 157) 'c': {unknown} diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs index 5d8dacf74..06222e6da 100644 --- a/crates/mun_hir/src/ty/tests.rs +++ b/crates/mun_hir/src/ty/tests.rs @@ -652,6 +652,12 @@ fn infer(content: &str) -> String { write!(diags, "{}: {}\n", diag.highlight_range(), diag.message()).unwrap(); }); + for package in Package::all(&db).iter() { + for module in package.modules(&db).iter() { + module.diagnostics(&db, &mut diag_sink); + } + } + for item in Package::all(&db) .iter() .flat_map(|pkg| pkg.modules(&db)) @@ -662,13 +668,8 @@ fn infer(content: &str) -> String { let source_map = fun.body_source_map(&db); let infer_result = fun.infer(&db); - fun.diagnostics(&db, &mut diag_sink); - infer_def(infer_result, source_map); } - ModuleDef::TypeAlias(item) => { - item.diagnostics(&db, &mut diag_sink); - } _ => {} } } diff --git a/crates/mun_hir/src/visibility.rs b/crates/mun_hir/src/visibility.rs index de2742c0a..e11ded978 100644 --- a/crates/mun_hir/src/visibility.rs +++ b/crates/mun_hir/src/visibility.rs @@ -1,3 +1,4 @@ +use crate::module_tree::{LocalModuleId, ModuleTree}; use crate::{ids::ModuleId, DefDatabase, HirDatabase, Resolver}; use mun_syntax::ast; use std::iter::successors; @@ -57,6 +58,23 @@ pub enum Visibility { } impl Visibility { + /// Returns true if an item with this visibility is accessible from the module of the + /// specified `PackageDefs`. + pub(crate) fn is_visible_from_module_tree( + self, + module_tree: &ModuleTree, + from_module: LocalModuleId, + ) -> bool { + let to_module = match self { + Visibility::Module(m) => m, + Visibility::Public => return true, + }; + + let mut ancestors = successors(Some(from_module), |m| module_tree[*m].parent); + + ancestors.any(|m| m == to_module.local_id) + } + /// Returns true if an item with this visibility is accessible from the given module. pub fn is_visible_from(self, db: &dyn HirDatabase, from_module: ModuleId) -> bool { let to_module = match self { diff --git a/crates/mun_syntax/src/ast/extensions.rs b/crates/mun_syntax/src/ast/extensions.rs index a7033e4d6..6aea3929f 100644 --- a/crates/mun_syntax/src/ast/extensions.rs +++ b/crates/mun_syntax/src/ast/extensions.rs @@ -199,3 +199,11 @@ impl ast::Visibility { .any(|it| it.kind() == T![super]) } } + +impl ast::UseTree { + pub fn has_star_token(&self) -> bool { + self.syntax() + .children_with_tokens() + .any(|it| it.kind() == T![*]) + } +} diff --git a/crates/mun_syntax/src/ast/generated.rs b/crates/mun_syntax/src/ast/generated.rs index b9d6a0eeb..7a673d4f3 100644 --- a/crates/mun_syntax/src/ast/generated.rs +++ b/crates/mun_syntax/src/ast/generated.rs @@ -610,7 +610,7 @@ pub struct ModuleItem { impl AstNode for ModuleItem { fn can_cast(kind: SyntaxKind) -> bool { - matches!(kind, FUNCTION_DEF | STRUCT_DEF | TYPE_ALIAS_DEF) + matches!(kind, USE | FUNCTION_DEF | STRUCT_DEF | TYPE_ALIAS_DEF) } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -625,10 +625,16 @@ impl AstNode for ModuleItem { } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ModuleItemKind { + Use(Use), FunctionDef(FunctionDef), StructDef(StructDef), TypeAliasDef(TypeAliasDef), } +impl From for ModuleItem { + fn from(n: Use) -> ModuleItem { + ModuleItem { syntax: n.syntax } + } +} impl From for ModuleItem { fn from(n: FunctionDef) -> ModuleItem { ModuleItem { syntax: n.syntax } @@ -648,6 +654,7 @@ impl From for ModuleItem { impl ModuleItem { pub fn kind(&self) -> ModuleItemKind { match self.syntax.kind() { + USE => ModuleItemKind::Use(Use::cast(self.syntax.clone()).unwrap()), FUNCTION_DEF => { ModuleItemKind::FunctionDef(FunctionDef::cast(self.syntax.clone()).unwrap()) } @@ -1191,6 +1198,31 @@ impl RecordLit { } } +// Rename + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Rename { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for Rename { + fn can_cast(kind: SyntaxKind) -> bool { + matches!(kind, RENAME) + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Rename { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl ast::NameOwner for Rename {} +impl Rename {} + // RetType #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -1492,6 +1524,99 @@ impl TypeRef { impl TypeRef {} +// Use + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Use { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for Use { + fn can_cast(kind: SyntaxKind) -> bool { + matches!(kind, USE) + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Use { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl ast::VisibilityOwner for Use {} +impl Use { + pub fn use_tree(&self) -> Option { + super::child_opt(self) + } +} + +// UseTree + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct UseTree { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for UseTree { + fn can_cast(kind: SyntaxKind) -> bool { + matches!(kind, USE_TREE) + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(UseTree { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl UseTree { + pub fn use_tree_list(&self) -> Option { + super::child_opt(self) + } + + pub fn path(&self) -> Option { + super::child_opt(self) + } + + pub fn rename(&self) -> Option { + super::child_opt(self) + } +} + +// UseTreeList + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct UseTreeList { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for UseTreeList { + fn can_cast(kind: SyntaxKind) -> bool { + matches!(kind, USE_TREE_LIST) + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(UseTreeList { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl UseTreeList { + pub fn use_trees(&self) -> impl Iterator { + super::children(self) + } +} + // Visibility #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/mun_syntax/src/grammar.ron b/crates/mun_syntax/src/grammar.ron index b7c628890..bb71927d3 100644 --- a/crates/mun_syntax/src/grammar.ron +++ b/crates/mun_syntax/src/grammar.ron @@ -72,6 +72,8 @@ Grammar( "fn", "if", "in", + "as", + "use", // "local", // We use let "nil", // "not", // We use ! @@ -169,13 +171,18 @@ Grammar( "RECORD_LIT", "RECORD_FIELD_LIST", "RECORD_FIELD", + + "USE", + "USE_TREE", + "USE_TREE_LIST", + "RENAME" ], ast: { "SourceFile": ( traits: [ "ModuleItemOwner", "FunctionDefOwner" ], ), "ModuleItem": ( - enum: ["FunctionDef", "StructDef", "TypeAliasDef"] + enum: ["Use", "FunctionDef", "StructDef", "TypeAliasDef"] ), "Visibility": (), "FunctionDef": ( @@ -349,5 +356,25 @@ Grammar( options: [["spread", "Expr"]] ), "RecordField": (options: ["NameRef", "Expr"]), + + "Use": ( + options: [["use_tree", "UseTree"]], + traits: ("VisibilityOwner") + ), + + "UseTree": ( + options: [ + ["use_tree_list", "UseTreeList"], + ["path", "Path"], + ["rename", "Rename"] + ] + ), + "UseTreeList": ( + collections: [ ("use_trees", "UseTree") ] + ), + + "Rename": ( + traits: ("NameOwner") + ) } ) diff --git a/crates/mun_syntax/src/lib.rs b/crates/mun_syntax/src/lib.rs index 7d1b7f036..cb26985ea 100644 --- a/crates/mun_syntax/src/lib.rs +++ b/crates/mun_syntax/src/lib.rs @@ -189,6 +189,7 @@ fn api_walkthrough() { ast::ModuleItemKind::FunctionDef(f) => func = Some(f), ast::ModuleItemKind::StructDef(_) => (), ast::ModuleItemKind::TypeAliasDef(_) => (), + ast::ModuleItemKind::Use(_) => (), } } diff --git a/crates/mun_syntax/src/parsing/grammar/declarations.rs b/crates/mun_syntax/src/parsing/grammar/declarations.rs index 2b20acc0d..b06ef9f31 100644 --- a/crates/mun_syntax/src/parsing/grammar/declarations.rs +++ b/crates/mun_syntax/src/parsing/grammar/declarations.rs @@ -1,7 +1,8 @@ use super::*; -use crate::T; +use crate::{parsing::grammar::paths::is_use_path_start, T}; -pub(super) const DECLARATION_RECOVERY_SET: TokenSet = TokenSet::new(&[T![fn], T![pub], T![struct]]); +pub(super) const DECLARATION_RECOVERY_SET: TokenSet = + TokenSet::new(&[T![fn], T![pub], T![struct], T![use]]); pub(super) fn mod_contents(p: &mut Parser) { while !p.at(EOF) { @@ -62,6 +63,9 @@ fn abi(p: &mut Parser) { fn declarations_without_modifiers(p: &mut Parser, m: Marker) -> Result<(), Marker> { match p.current() { + T![use] => { + use_(p, m); + } T![struct] => { adt::struct_def(p, m); } @@ -105,3 +109,79 @@ fn opt_fn_ret_type(p: &mut Parser) -> bool { false } } + +fn use_(p: &mut Parser, m: Marker) { + assert!(p.at(T![use])); + p.bump(T![use]); + use_tree(p, true); + p.expect(T![;]); + m.complete(p, USE); +} + +/// Parses a use "tree", such as `foo::bar` in `use foo::bar;`. +fn use_tree(p: &mut Parser, top_level: bool) { + let m = p.start(); + + match p.current() { + T![*] if !top_level => p.bump(T![*]), + _ if is_use_path_start(p, top_level) => { + paths::use_path(p, top_level); + match p.current() { + T![as] => { + opt_rename(p); + } + T![:] if p.at(T![::]) => { + p.bump(T![::]); + match p.current() { + T![*] => { + p.bump(T![*]); + } + T!['{'] => use_tree_list(p), + _ => { + p.error("expected `{` or `*`"); + } + } + } + _ => (), + } + } + _ => { + m.abandon(p); + let msg = "expected one of `self`, `super`, `package` or an identifier"; + if top_level { + p.error_recover(msg, DECLARATION_RECOVERY_SET); + } else { + // if we are parsing a nested tree, we have to eat a token to remain balanced `{}` + p.error_and_bump(msg); + } + return; + } + } + + m.complete(p, USE_TREE); +} + +fn use_tree_list(p: &mut Parser) { + assert!(p.at(T!['{'])); + let m = p.start(); + p.bump(T!['{']); + while !p.at(EOF) && !p.at(T!['}']) { + use_tree(p, false); + if !p.at(T!['}']) { + p.expect(T![,]); + } + } + p.expect(T!['}']); + m.complete(p, USE_TREE_LIST); +} + +fn opt_rename(p: &mut Parser) { + if p.at(T![as]) { + let m = p.start(); + p.bump(T![as]); + if !p.eat(T![_]) { + name(p); + } + m.complete(p, RENAME); + } +} diff --git a/crates/mun_syntax/src/parsing/grammar/paths.rs b/crates/mun_syntax/src/parsing/grammar/paths.rs index a7c1fe49b..83288da4c 100644 --- a/crates/mun_syntax/src/parsing/grammar/paths.rs +++ b/crates/mun_syntax/src/parsing/grammar/paths.rs @@ -7,29 +7,41 @@ pub(super) fn is_path_start(p: &Parser) -> bool { matches!(p.current(), IDENT | T![self] | T![super] | T![package]) } +pub(super) fn is_use_path_start(p: &Parser, top_level: bool) -> bool { + if top_level { + matches!(p.current(), IDENT | T![self] | T![super] | T![package]) + } else { + matches!(p.current(), IDENT | T![self]) + } +} + pub(super) fn type_path(p: &mut Parser) { - path(p, Mode::Type) + path(p, Mode::Type, true) } pub(super) fn expr_path(p: &mut Parser) { - path(p, Mode::Expr) + path(p, Mode::Expr, true) +} +pub(super) fn use_path(p: &mut Parser, top_level: bool) { + path(p, Mode::Use, top_level) } #[derive(Clone, Copy, Eq, PartialEq)] enum Mode { Type, Expr, + Use, } -fn path(p: &mut Parser, mode: Mode) { +fn path(p: &mut Parser, mode: Mode, top_level: bool) { let path = p.start(); - path_segment(p, mode); + path_segment(p, mode, top_level); let mut qualifier = path.complete(p, PATH); loop { - let import_tree = matches!(p.nth(1), T![*] | T!['{']); - if p.at(T![::]) && !import_tree { + let use_tree = matches!(p.nth(2), T![*] | T!['{']); + if p.at(T![::]) && !use_tree { let path = qualifier.precede(p); p.bump(T![::]); - path_segment(p, mode); + path_segment(p, mode, false); let path = path.complete(p, PATH); qualifier = path; } else { @@ -38,13 +50,14 @@ fn path(p: &mut Parser, mode: Mode) { } } -fn path_segment(p: &mut Parser, _mode: Mode) { +fn path_segment(p: &mut Parser, _mode: Mode, top_level: bool) { let m = p.start(); match p.current() { IDENT => { name_ref(p); } - T![self] | T![super] | T![package] => p.bump_any(), + T![super] | T![package] if top_level => p.bump_any(), + T![self] => p.bump(T![self]), _ => p.error_recover( "expected identifier", declarations::DECLARATION_RECOVERY_SET, diff --git a/crates/mun_syntax/src/syntax_kind/generated.rs b/crates/mun_syntax/src/syntax_kind/generated.rs index 5f512f872..43ed5cad9 100644 --- a/crates/mun_syntax/src/syntax_kind/generated.rs +++ b/crates/mun_syntax/src/syntax_kind/generated.rs @@ -73,6 +73,8 @@ pub enum SyntaxKind { FN_KW, IF_KW, IN_KW, + AS_KW, + USE_KW, NIL_KW, RETURN_KW, TRUE_KW, @@ -141,6 +143,10 @@ pub enum SyntaxKind { RECORD_LIT, RECORD_FIELD_LIST, RECORD_FIELD, + USE, + USE_TREE, + USE_TREE_LIST, + RENAME, // Technical kind so that we can cast from u16 safely #[doc(hidden)] __LAST, @@ -314,6 +320,12 @@ macro_rules! T { (in) => { $crate::SyntaxKind::IN_KW }; + (as) => { + $crate::SyntaxKind::AS_KW + }; + (use) => { + $crate::SyntaxKind::USE_KW + }; (nil) => { $crate::SyntaxKind::NIL_KW }; @@ -389,6 +401,8 @@ impl SyntaxKind { | FN_KW | IF_KW | IN_KW + | AS_KW + | USE_KW | NIL_KW | RETURN_KW | TRUE_KW @@ -528,6 +542,8 @@ impl SyntaxKind { FN_KW => &SyntaxInfo { name: "FN_KW" }, IF_KW => &SyntaxInfo { name: "IF_KW" }, IN_KW => &SyntaxInfo { name: "IN_KW" }, + AS_KW => &SyntaxInfo { name: "AS_KW" }, + USE_KW => &SyntaxInfo { name: "USE_KW" }, NIL_KW => &SyntaxInfo { name: "NIL_KW" }, RETURN_KW => &SyntaxInfo { name: "RETURN_KW" }, TRUE_KW => &SyntaxInfo { name: "TRUE_KW" }, @@ -596,6 +612,10 @@ impl SyntaxKind { RECORD_LIT => &SyntaxInfo { name: "RECORD_LIT" }, RECORD_FIELD_LIST => &SyntaxInfo { name: "RECORD_FIELD_LIST" }, RECORD_FIELD => &SyntaxInfo { name: "RECORD_FIELD" }, + USE => &SyntaxInfo { name: "USE" }, + USE_TREE => &SyntaxInfo { name: "USE_TREE" }, + USE_TREE_LIST => &SyntaxInfo { name: "USE_TREE_LIST" }, + RENAME => &SyntaxInfo { name: "RENAME" }, TOMBSTONE => &SyntaxInfo { name: "TOMBSTONE" }, EOF => &SyntaxInfo { name: "EOF" }, __LAST => &SyntaxInfo { name: "__LAST" }, @@ -612,6 +632,8 @@ impl SyntaxKind { "fn" => FN_KW, "if" => IF_KW, "in" => IN_KW, + "as" => AS_KW, + "use" => USE_KW, "nil" => NIL_KW, "return" => RETURN_KW, "true" => TRUE_KW, diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs index 9b51f092f..ac30d2b3d 100644 --- a/crates/mun_syntax/src/tests/parser.rs +++ b/crates/mun_syntax/src/tests/parser.rs @@ -376,3 +376,38 @@ fn function_return_path() { "#, ); } + +#[test] +fn use_() { + snapshot_test( + r#" + // Simple paths + use package_name; + use self::item_in_scope_or_package_name; + use self::module::Item; + use package::Item; + use self::some::Struct; + use package::some_item; + + // Use tree list + use crate::{Item}; + use self::{Item}; + + // Wildcard import + use *; // Error + use ::*; // Error + use crate::*; + use crate::{*}; + + // Renames + use some::path as some_name; + use some::{ + other::path as some_other_name, + different::path as different_name, + yet::another::path, + running::out::of::synonyms::for_::different::* + }; + use Foo as _; + "#, + ) +} diff --git a/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__use_.snap b/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__use_.snap new file mode 100644 index 000000000..0c6cfe388 --- /dev/null +++ b/crates/mun_syntax/src/tests/snapshots/mun_syntax__tests__parser__use_.snap @@ -0,0 +1,334 @@ +--- +source: crates/mun_syntax/src/tests/parser.rs +expression: "// Simple paths\n use package_name;\n use self::item_in_scope_or_package_name;\n use self::module::Item;\n use package::Item;\n use self::some::Struct;\n use package::some_item;\n\n // Use tree list\n use crate::{Item};\n use self::{Item};\n\n // Wildcard import\n use *; // Error\n use ::*; // Error\n use crate::*;\n use crate::{*};\n\n // Renames\n use some::path as some_name;\n use some::{\n other::path as some_other_name,\n different::path as different_name,\n yet::another::path,\n running::out::of::synonyms::for_::different::*\n };\n use Foo as _;" +--- +SOURCE_FILE@[0; 616) + COMMENT@[0; 15) "// Simple paths" + WHITESPACE@[15; 20) "\n " + USE@[20; 37) + USE_KW@[20; 23) "use" + WHITESPACE@[23; 24) " " + USE_TREE@[24; 36) + PATH@[24; 36) + PATH_SEGMENT@[24; 36) + NAME_REF@[24; 36) + IDENT@[24; 36) "package_name" + SEMI@[36; 37) ";" + WHITESPACE@[37; 42) "\n " + USE@[42; 82) + USE_KW@[42; 45) "use" + WHITESPACE@[45; 46) " " + USE_TREE@[46; 81) + PATH@[46; 81) + PATH@[46; 50) + PATH_SEGMENT@[46; 50) + SELF_KW@[46; 50) "self" + COLONCOLON@[50; 52) "::" + PATH_SEGMENT@[52; 81) + NAME_REF@[52; 81) + IDENT@[52; 81) "item_in_scope_or_pack ..." + SEMI@[81; 82) ";" + WHITESPACE@[82; 87) "\n " + USE@[87; 110) + USE_KW@[87; 90) "use" + WHITESPACE@[90; 91) " " + USE_TREE@[91; 109) + PATH@[91; 109) + PATH@[91; 103) + PATH@[91; 95) + PATH_SEGMENT@[91; 95) + SELF_KW@[91; 95) "self" + COLONCOLON@[95; 97) "::" + PATH_SEGMENT@[97; 103) + NAME_REF@[97; 103) + IDENT@[97; 103) "module" + COLONCOLON@[103; 105) "::" + PATH_SEGMENT@[105; 109) + NAME_REF@[105; 109) + IDENT@[105; 109) "Item" + SEMI@[109; 110) ";" + WHITESPACE@[110; 115) "\n " + USE@[115; 133) + USE_KW@[115; 118) "use" + WHITESPACE@[118; 119) " " + USE_TREE@[119; 132) + PATH@[119; 132) + PATH@[119; 126) + PATH_SEGMENT@[119; 126) + PACKAGE_KW@[119; 126) "package" + COLONCOLON@[126; 128) "::" + PATH_SEGMENT@[128; 132) + NAME_REF@[128; 132) + IDENT@[128; 132) "Item" + SEMI@[132; 133) ";" + WHITESPACE@[133; 138) "\n " + USE@[138; 161) + USE_KW@[138; 141) "use" + WHITESPACE@[141; 142) " " + USE_TREE@[142; 160) + PATH@[142; 160) + PATH@[142; 152) + PATH@[142; 146) + PATH_SEGMENT@[142; 146) + SELF_KW@[142; 146) "self" + COLONCOLON@[146; 148) "::" + PATH_SEGMENT@[148; 152) + NAME_REF@[148; 152) + IDENT@[148; 152) "some" + COLONCOLON@[152; 154) "::" + PATH_SEGMENT@[154; 160) + NAME_REF@[154; 160) + IDENT@[154; 160) "Struct" + SEMI@[160; 161) ";" + WHITESPACE@[161; 166) "\n " + USE@[166; 189) + USE_KW@[166; 169) "use" + WHITESPACE@[169; 170) " " + USE_TREE@[170; 188) + PATH@[170; 188) + PATH@[170; 177) + PATH_SEGMENT@[170; 177) + PACKAGE_KW@[170; 177) "package" + COLONCOLON@[177; 179) "::" + PATH_SEGMENT@[179; 188) + NAME_REF@[179; 188) + IDENT@[179; 188) "some_item" + SEMI@[188; 189) ";" + WHITESPACE@[189; 195) "\n\n " + COMMENT@[195; 211) "// Use tree list" + WHITESPACE@[211; 216) "\n " + USE@[216; 234) + USE_KW@[216; 219) "use" + WHITESPACE@[219; 220) " " + USE_TREE@[220; 233) + PATH@[220; 225) + PATH_SEGMENT@[220; 225) + NAME_REF@[220; 225) + IDENT@[220; 225) "crate" + COLONCOLON@[225; 227) "::" + USE_TREE_LIST@[227; 233) + L_CURLY@[227; 228) "{" + USE_TREE@[228; 232) + PATH@[228; 232) + PATH_SEGMENT@[228; 232) + NAME_REF@[228; 232) + IDENT@[228; 232) "Item" + R_CURLY@[232; 233) "}" + SEMI@[233; 234) ";" + WHITESPACE@[234; 239) "\n " + USE@[239; 256) + USE_KW@[239; 242) "use" + WHITESPACE@[242; 243) " " + USE_TREE@[243; 255) + PATH@[243; 247) + PATH_SEGMENT@[243; 247) + SELF_KW@[243; 247) "self" + COLONCOLON@[247; 249) "::" + USE_TREE_LIST@[249; 255) + L_CURLY@[249; 250) "{" + USE_TREE@[250; 254) + PATH@[250; 254) + PATH_SEGMENT@[250; 254) + NAME_REF@[250; 254) + IDENT@[250; 254) "Item" + R_CURLY@[254; 255) "}" + SEMI@[255; 256) ";" + WHITESPACE@[256; 262) "\n\n " + COMMENT@[262; 280) "// Wildcard import" + WHITESPACE@[280; 285) "\n " + USE@[285; 291) + USE_KW@[285; 288) "use" + WHITESPACE@[288; 289) " " + ERROR@[289; 290) + STAR@[289; 290) "*" + SEMI@[290; 291) ";" + WHITESPACE@[291; 292) " " + COMMENT@[292; 300) "// Error" + WHITESPACE@[300; 305) "\n " + USE@[305; 310) + USE_KW@[305; 308) "use" + WHITESPACE@[308; 309) " " + ERROR@[309; 310) + COLON@[309; 310) ":" + ERROR@[310; 311) + COLON@[310; 311) ":" + ERROR@[311; 312) + STAR@[311; 312) "*" + ERROR@[312; 313) + SEMI@[312; 313) ";" + WHITESPACE@[313; 314) " " + COMMENT@[314; 322) "// Error" + WHITESPACE@[322; 327) "\n " + USE@[327; 340) + USE_KW@[327; 330) "use" + WHITESPACE@[330; 331) " " + USE_TREE@[331; 339) + PATH@[331; 336) + PATH_SEGMENT@[331; 336) + NAME_REF@[331; 336) + IDENT@[331; 336) "crate" + COLONCOLON@[336; 338) "::" + STAR@[338; 339) "*" + SEMI@[339; 340) ";" + WHITESPACE@[340; 345) "\n " + USE@[345; 360) + USE_KW@[345; 348) "use" + WHITESPACE@[348; 349) " " + USE_TREE@[349; 359) + PATH@[349; 354) + PATH_SEGMENT@[349; 354) + NAME_REF@[349; 354) + IDENT@[349; 354) "crate" + COLONCOLON@[354; 356) "::" + USE_TREE_LIST@[356; 359) + L_CURLY@[356; 357) "{" + USE_TREE@[357; 358) + STAR@[357; 358) "*" + R_CURLY@[358; 359) "}" + SEMI@[359; 360) ";" + WHITESPACE@[360; 366) "\n\n " + COMMENT@[366; 376) "// Renames" + WHITESPACE@[376; 381) "\n " + USE@[381; 409) + USE_KW@[381; 384) "use" + WHITESPACE@[384; 385) " " + USE_TREE@[385; 408) + PATH@[385; 395) + PATH@[385; 389) + PATH_SEGMENT@[385; 389) + NAME_REF@[385; 389) + IDENT@[385; 389) "some" + COLONCOLON@[389; 391) "::" + PATH_SEGMENT@[391; 395) + NAME_REF@[391; 395) + IDENT@[391; 395) "path" + WHITESPACE@[395; 396) " " + RENAME@[396; 408) + AS_KW@[396; 398) "as" + WHITESPACE@[398; 399) " " + NAME@[399; 408) + IDENT@[399; 408) "some_name" + SEMI@[408; 409) ";" + WHITESPACE@[409; 414) "\n " + USE@[414; 598) + USE_KW@[414; 417) "use" + WHITESPACE@[417; 418) " " + USE_TREE@[418; 597) + PATH@[418; 422) + PATH_SEGMENT@[418; 422) + NAME_REF@[418; 422) + IDENT@[418; 422) "some" + COLONCOLON@[422; 424) "::" + USE_TREE_LIST@[424; 597) + L_CURLY@[424; 425) "{" + WHITESPACE@[425; 434) "\n " + USE_TREE@[434; 464) + PATH@[434; 445) + PATH@[434; 439) + PATH_SEGMENT@[434; 439) + NAME_REF@[434; 439) + IDENT@[434; 439) "other" + COLONCOLON@[439; 441) "::" + PATH_SEGMENT@[441; 445) + NAME_REF@[441; 445) + IDENT@[441; 445) "path" + WHITESPACE@[445; 446) " " + RENAME@[446; 464) + AS_KW@[446; 448) "as" + WHITESPACE@[448; 449) " " + NAME@[449; 464) + IDENT@[449; 464) "some_other_name" + COMMA@[464; 465) "," + WHITESPACE@[465; 474) "\n " + USE_TREE@[474; 507) + PATH@[474; 489) + PATH@[474; 483) + PATH_SEGMENT@[474; 483) + NAME_REF@[474; 483) + IDENT@[474; 483) "different" + COLONCOLON@[483; 485) "::" + PATH_SEGMENT@[485; 489) + NAME_REF@[485; 489) + IDENT@[485; 489) "path" + WHITESPACE@[489; 490) " " + RENAME@[490; 507) + AS_KW@[490; 492) "as" + WHITESPACE@[492; 493) " " + NAME@[493; 507) + IDENT@[493; 507) "different_name" + COMMA@[507; 508) "," + WHITESPACE@[508; 517) "\n " + USE_TREE@[517; 535) + PATH@[517; 535) + PATH@[517; 529) + PATH@[517; 520) + PATH_SEGMENT@[517; 520) + NAME_REF@[517; 520) + IDENT@[517; 520) "yet" + COLONCOLON@[520; 522) "::" + PATH_SEGMENT@[522; 529) + NAME_REF@[522; 529) + IDENT@[522; 529) "another" + COLONCOLON@[529; 531) "::" + PATH_SEGMENT@[531; 535) + NAME_REF@[531; 535) + IDENT@[531; 535) "path" + COMMA@[535; 536) "," + WHITESPACE@[536; 545) "\n " + USE_TREE@[545; 591) + PATH@[545; 588) + PATH@[545; 577) + PATH@[545; 571) + PATH@[545; 561) + PATH@[545; 557) + PATH@[545; 552) + PATH_SEGMENT@[545; 552) + NAME_REF@[545; 552) + IDENT@[545; 552) "running" + COLONCOLON@[552; 554) "::" + PATH_SEGMENT@[554; 557) + NAME_REF@[554; 557) + IDENT@[554; 557) "out" + COLONCOLON@[557; 559) "::" + PATH_SEGMENT@[559; 561) + NAME_REF@[559; 561) + IDENT@[559; 561) "of" + COLONCOLON@[561; 563) "::" + PATH_SEGMENT@[563; 571) + NAME_REF@[563; 571) + IDENT@[563; 571) "synonyms" + COLONCOLON@[571; 573) "::" + PATH_SEGMENT@[573; 577) + NAME_REF@[573; 577) + IDENT@[573; 577) "for_" + COLONCOLON@[577; 579) "::" + PATH_SEGMENT@[579; 588) + NAME_REF@[579; 588) + IDENT@[579; 588) "different" + COLONCOLON@[588; 590) "::" + STAR@[590; 591) "*" + WHITESPACE@[591; 596) "\n " + R_CURLY@[596; 597) "}" + SEMI@[597; 598) ";" + WHITESPACE@[598; 603) "\n " + USE@[603; 616) + USE_KW@[603; 606) "use" + WHITESPACE@[606; 607) " " + USE_TREE@[607; 615) + PATH@[607; 610) + PATH_SEGMENT@[607; 610) + NAME_REF@[607; 610) + IDENT@[607; 610) "Foo" + WHITESPACE@[610; 611) " " + RENAME@[611; 615) + AS_KW@[611; 613) "as" + WHITESPACE@[613; 614) " " + UNDERSCORE@[614; 615) "_" + SEMI@[615; 616) ";" +error Offset(289): expected one of `self`, `super`, `package` or an identifier +error Offset(309): expected one of `self`, `super`, `package` or an identifier +error Offset(310): expected SEMI +error Offset(310): expected a declaration +error Offset(311): expected a declaration +error Offset(312): expected a declaration +