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

feat: module path aliases #2632

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

corvusrabus
Copy link

Implements #2629 .

The usage of NamePath feels clunky. Maybe I could make it so that it's guaranteed that NamePath is not empty and that all tokens sit after each other separated by ColonColon. Then, we could generate a &str and avoid the usage of String in UnknownAliasTarget

Copy link
Owner

@casey casey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments! I think that this can be made simpler by making the analysis function recursive. Also, I landed another PR which guarantees that a namepath is non-empty. Although the addition of new in this PR breaks that. Although I think the actual answer is using a recursive function and erroring when the path is empty. See comments.

src/analyzer.rs Outdated
})),
}
}

fn traverse_nonempty_module_name_path<'modules>(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be made simpler if it's recursive. Maybe with this signature:

fn foo(
  path: &[Name],
  modules: &Table<Justfile>,
  recipes: &Table<Rc<Recipe>>,
) -> Result<Option<Rc<Recipe>>, ()>;

If the path is empty, then resolution has failed, and you can return Err(()).

If path is length one, then look in recipes for the target recipe.

Otherwise, try to find the next module to look in using the first item in path. If not found, then return an error. If found, then make the recursive call with path[1..], and the submodules recipes and modules.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I didn't see added value in having an Err(()) and a Ok(None) so I made it just an Option with None indicating that the recipe was not found

src/analyzer.rs Outdated
.pop()
.expect("Internal error: Alias target path can't be empty");

let alias = Alias {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that resolve can be changed so that it's implemented for the correct type, so we don't need to take apart and put the alias back together again.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@corvusrabus
Copy link
Author

Sorry I've been a bit busy. Will follow up on the weekend

@corvusrabus corvusrabus force-pushed the module_alias branch 2 times, most recently from 9de5027 to 31d6e83 Compare March 5, 2025 22:52
@corvusrabus
Copy link
Author

@casey I

  1. removed the NamePath::new so that len >=1 is again guaranteed
  2. Needed to add new PartialEq for NamePath because namepaths should be considered equal if they lex to the same path

Once we merge this I'd be happy to implement dependencies on recipes in other modules, which I think is 60% done with this MR already (that's the feature that I came here for in the first place :) )

@corvusrabus corvusrabus requested a review from casey March 6, 2025 21:25
Copy link
Owner

@casey casey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, much cleaner! See comments.

src/namepath.rs Outdated
@@ -42,3 +63,11 @@ impl Serialize for Namepath<'_> {
serializer.serialize_str(&format!("{self}"))
}
}

impl<'src> Deref for Namepath<'src> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't add deref to Namepath. It means that any function on Vec (I actually changed it to slice before I decided we shouldn't provide it at all.) is valid for namepath, which I think is not ideal. See comment about last later on.

src/analyzer.rs Outdated
mut modules: &'a Table<'src, Justfile<'src>>,
mut recipes: &'a Table<'src, Rc<Recipe<'src>>>,
) -> Option<Rc<Recipe<'src>>> {
let name = path.last()?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if .last() return None? This will be treated as an unknown alias target, but is in fact a bug, since namepath should be non-empty, and thus if .last() returns None it should be an internal error. This is why we shouldn't iplement Deref for Namepath, because the function signature fn last(&self) -> Option<Name> doesn't make sense. It should be fn last(&self) -> Name.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will expect() panic now saying that Namepath can't be empty

src/namepath.rs Outdated
pub(crate) struct Namepath<'src>(Vec<Name<'src>>);

impl PartialEq for Namepath<'_> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should implement a PartialEq implementation which ignores everything but the lexeme.

I think we should do fn is_same_path(&self, other: &Self) -> bool which compares the lexemes.

src/namepath.rs Outdated
self.0.push(name);
}

fn lexeme_iter(&self) -> impl ExactSizeIterator<Item = &str> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think a helper function which is only used in one place adds much. .all(|(a, b)| a.lexeme() == b.lexeme()) is fine.

corvusrabus and others added 2 commits March 8, 2025 20:25
@corvusrabus corvusrabus requested a review from casey March 8, 2025 20:37
/// a::b::c -> (a::b, c)
/// a -> (empty, a)
pub fn split_last(&self) -> (&[Name<'src>], &Name<'src>) {
let name = self.last();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assigning this variable first instead of making this a one liner because last() will give a more meaningful panic message than &self.0[..self.0.len() - 1] panicking due to wrong indexing if len()==0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants