Skip to content

Commit

Permalink
Merge pull request #290 from baszalmstra/feature/use_statement
Browse files Browse the repository at this point in the history
feature: use statements language support
  • Loading branch information
baszalmstra authored Feb 28, 2021
2 parents c0185b7 + 0fd6ae6 commit b83586a
Show file tree
Hide file tree
Showing 36 changed files with 2,098 additions and 86 deletions.
4 changes: 3 additions & 1 deletion crates/mun_hir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
63 changes: 62 additions & 1 deletion crates/mun_hir/src/code_model/module.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<Module> {
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<Name> {
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<Module> {
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<FileId> {
db.module_tree(self.id.package).modules[self.id.local_id].file
Expand All @@ -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),
Expand All @@ -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<Module> {
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)]
Expand Down
38 changes: 38 additions & 0 deletions crates/mun_hir/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,3 +746,41 @@ impl Diagnostic for FreeTypeAliasWithoutTypeRef {
self
}
}

#[derive(Debug)]
pub struct UnresolvedImport {
pub use_tree: InFile<AstPtr<ast::UseTree>>,
}

impl Diagnostic for UnresolvedImport {
fn message(&self) -> String {
"unresolved import".to_string()
}

fn source(&self) -> InFile<SyntaxNodePtr> {
self.use_tree.map(Into::into)
}

fn as_any(&self) -> &(dyn Any + Send) {
self
}
}

#[derive(Debug)]
pub struct ImportDuplicateDefinition {
pub use_tree: InFile<AstPtr<ast::UseTree>>,
}

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<SyntaxNodePtr> {
self.use_tree.map(Into::into)
}

fn as_any(&self) -> &(dyn Any + Send) {
self
}
}
145 changes: 140 additions & 5 deletions crates/mun_hir/src/item_scope.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -17,6 +42,16 @@ pub struct ItemScope {
defs: Vec<ItemDefinitionId>,
}

/// 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<FxHashMap<Name, PerNs<(ItemDefinitionId, Visibility)>>> =
Lazy::new(|| {
PrimitiveType::ALL
Expand All @@ -31,6 +66,14 @@ pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs<(ItemDefinitionId, V
});

impl ItemScope {
/// Returns all the entries in the scope
pub fn entries(
&self,
) -> impl Iterator<Item = (&'_ Name, PerNs<(ItemDefinitionId, Visibility)>)> + '_ {
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<Item = ItemDefinitionId> + '_ {
self.defs.iter().copied()
Expand All @@ -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
Expand Down
Loading

0 comments on commit b83586a

Please sign in to comment.