-
Notifications
You must be signed in to change notification settings - Fork 12
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
ModuleSet
: store a set rather than a map
#292
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
use std::borrow::Borrow; | ||
use std::fmt::Display; | ||
use std::hash::Hash; | ||
use std::hash::Hasher; | ||
|
||
use camino::Utf8Path; | ||
|
||
use crate::normal_path::NormalPath; | ||
|
||
/// Information about a module loaded into a `ghci` session. | ||
/// | ||
/// Hashing and equality are determined by the module's path alone. | ||
#[derive(Debug, Clone, Eq)] | ||
pub struct LoadedModule { | ||
/// The module's source file, like `src/My/Cool/Module.hs`. | ||
path: NormalPath, | ||
|
||
/// The module's dotted name, like `My.Cool.Module`. | ||
/// | ||
/// This is present if and only if the module is loaded by name. | ||
/// | ||
/// Entries in `:show targets` can be one of two types: module paths or module names (with `.` in | ||
/// place of path separators). Due to a `ghci` bug, the module can only be referred to as whichever | ||
/// form it was originally added as (see below), so we use this to track how we refer to modules. | ||
/// | ||
/// See: <https://gitlab.haskell.org/ghc/ghc/-/issues/13254#note_525037> | ||
name: Option<String>, | ||
} | ||
|
||
impl LoadedModule { | ||
/// Create a new module, loaded by path. | ||
pub fn new(path: NormalPath) -> Self { | ||
Self { path, name: None } | ||
} | ||
|
||
/// Create a new module, loaded by name. | ||
pub fn with_name(path: NormalPath, name: String) -> Self { | ||
Self { | ||
path, | ||
name: Some(name), | ||
} | ||
} | ||
|
||
/// Get the module's source path. | ||
pub fn path(&self) -> &NormalPath { | ||
&self.path | ||
Comment on lines
+45
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar question here: what's the advantage of doing |
||
} | ||
} | ||
|
||
impl Display for LoadedModule { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!( | ||
f, | ||
"{}", | ||
self.name | ||
.as_deref() | ||
.unwrap_or_else(|| self.path.relative().as_str()) | ||
) | ||
} | ||
} | ||
|
||
impl Hash for LoadedModule { | ||
fn hash<H: Hasher>(&self, state: &mut H) { | ||
self.path.hash(state) | ||
} | ||
} | ||
|
||
impl PartialEq for LoadedModule { | ||
fn eq(&self, other: &Self) -> bool { | ||
self.path.eq(&other.path) | ||
} | ||
} | ||
|
||
impl PartialOrd for LoadedModule { | ||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { | ||
self.path.partial_cmp(&other.path) | ||
} | ||
} | ||
|
||
impl Ord for LoadedModule { | ||
fn cmp(&self, other: &Self) -> std::cmp::Ordering { | ||
self.path.cmp(&other.path) | ||
} | ||
} | ||
|
||
impl Borrow<NormalPath> for LoadedModule { | ||
fn borrow(&self) -> &NormalPath { | ||
&self.path | ||
} | ||
} | ||
|
||
impl Borrow<Utf8Path> for LoadedModule { | ||
fn borrow(&self) -> &Utf8Path { | ||
&self.path | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
use std::borrow::Borrow; | ||
use std::borrow::Cow; | ||
use std::cmp::Eq; | ||
use std::collections::HashSet; | ||
use std::hash::Hash; | ||
|
||
use crate::normal_path::NormalPath; | ||
|
||
use super::loaded_module::LoadedModule; | ||
|
||
/// A collection of source paths, retaining information about loaded modules in a `ghci` | ||
/// session. | ||
#[derive(Debug, Clone, Default, PartialEq, Eq)] | ||
pub struct ModuleSet { | ||
modules: HashSet<LoadedModule>, | ||
} | ||
Comment on lines
+14
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking, but I feel like this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that's true. I like the type safety this gives, though, and I like being able to give semantically descriptive names to the methods. I think I'll keep this for now. |
||
|
||
impl ModuleSet { | ||
/// Iterate over the modules in this set. | ||
pub fn iter(&self) -> std::collections::hash_set::Iter<'_, LoadedModule> { | ||
self.modules.iter() | ||
} | ||
|
||
/// Iterate over the modules in this set. | ||
#[cfg(test)] | ||
pub fn into_iter(self) -> std::collections::hash_set::IntoIter<LoadedModule> { | ||
self.modules.into_iter() | ||
} | ||
|
||
/// Get the number of modules in this set. | ||
pub fn len(&self) -> usize { | ||
self.modules.len() | ||
} | ||
|
||
/// Determine if a module with the given source path is contained in this module set. | ||
pub fn contains_source_path<P>(&self, path: &P) -> bool | ||
where | ||
LoadedModule: Borrow<P>, | ||
P: Hash + Eq + ?Sized, | ||
{ | ||
self.modules.contains(path) | ||
} | ||
|
||
/// Add a module to this set. | ||
/// | ||
/// Returns whether the module was newly inserted. | ||
pub fn insert_module(&mut self, module: LoadedModule) -> bool { | ||
self.modules.insert(module) | ||
} | ||
|
||
/// Remove a source path from this module set. | ||
/// | ||
/// Returns whether the path was present in the set. | ||
pub fn remove_source_path<P>(&mut self, path: &P) -> bool | ||
where | ||
LoadedModule: Borrow<P>, | ||
P: Hash + Eq + ?Sized, | ||
{ | ||
self.modules.remove(path) | ||
} | ||
|
||
/// Get a module in this set. | ||
pub fn get_module<P>(&self, path: &P) -> Option<&LoadedModule> | ||
where | ||
LoadedModule: Borrow<P>, | ||
P: Hash + Eq + ?Sized, | ||
{ | ||
self.modules.get(path) | ||
} | ||
|
||
/// Get the import name for a module. | ||
/// | ||
/// The path parameter should be relative to the GHCi session's working directory. | ||
pub fn get_import_name(&self, path: &NormalPath) -> Cow<'_, LoadedModule> { | ||
match self.get_module(path) { | ||
Some(module) => Cow::Borrowed(module), | ||
None => Cow::Owned(LoadedModule::new(path.clone())), | ||
} | ||
} | ||
} | ||
|
||
impl FromIterator<LoadedModule> for ModuleSet { | ||
fn from_iter<T: IntoIterator<Item = LoadedModule>>(iter: T) -> Self { | ||
Self { | ||
modules: iter.into_iter().collect(), | ||
} | ||
} | ||
} | ||
|
||
impl Extend<LoadedModule> for ModuleSet { | ||
fn extend<T: IntoIterator<Item = LoadedModule>>(&mut self, iter: T) { | ||
self.modules.extend(iter) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I might have asked this before, but what's the motivation for using these methods instead of using the
LoadedModule
constructor directly?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the usual encapsulation reason. If you want to be able to write
LoadedModule { path, name: None }
then all of the struct's fields have to be public (and theoretically callers could use this to break invariants).In reality,
LoadedModule
isn't exported publicly by the library and it would be basically fine to have the fields be public, but this is considered "good practice" so I lean towards it.(Also constructor names can be a little more informative than setting struct fields directly.)