Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: use statements language support #290

Merged
merged 9 commits into from
Feb 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
Wodann marked this conversation as resolved.
Show resolved Hide resolved
.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, *};
Wodann marked this conversation as resolved.
Show resolved Hide resolved
/// ```
#[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