Resolve alias targets (#548)
During analysis, resolve alias targets from `Name`s to `Rc<Recipe>`, giving us type-level assurance that alias resolution was performed, and avoiding the need to look up alias targets in a separate table when running.
This commit is contained in:
parent
30c77f8d03
commit
4f08bb4d77
38
src/alias.rs
38
src/alias.rs
@ -2,28 +2,39 @@ use crate::common::*;
|
||||
|
||||
/// An alias, e.g. `name := target`
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct Alias<'src> {
|
||||
pub(crate) struct Alias<'src, T = Rc<Recipe<'src>>> {
|
||||
pub(crate) name: Name<'src>,
|
||||
pub(crate) target: Name<'src>,
|
||||
pub(crate) target: T,
|
||||
}
|
||||
|
||||
impl<'src> Alias<'src, Name<'src>> {
|
||||
pub(crate) fn line_number(&self) -> usize {
|
||||
self.name.line
|
||||
}
|
||||
|
||||
pub(crate) fn resolve(self, target: Rc<Recipe<'src>>) -> Alias<'src> {
|
||||
assert_eq!(self.target.lexeme(), target.name.lexeme());
|
||||
|
||||
Alias {
|
||||
name: self.name,
|
||||
target,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Alias<'_> {
|
||||
pub(crate) fn is_private(&self) -> bool {
|
||||
self.name.lexeme().starts_with('_')
|
||||
}
|
||||
|
||||
pub(crate) fn line_number(&self) -> usize {
|
||||
self.name.line
|
||||
}
|
||||
}
|
||||
|
||||
impl<'src> Keyed<'src> for Alias<'src> {
|
||||
impl<'src, T> Keyed<'src> for Alias<'src, T> {
|
||||
fn key(&self) -> &'src str {
|
||||
self.name.lexeme()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Alias<'a> {
|
||||
impl<'src> Display for Alias<'src, Name<'src>> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
@ -33,3 +44,14 @@ impl<'a> Display for Alias<'a> {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'src> Display for Alias<'src> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"alias {} := {}",
|
||||
self.name.lexeme(),
|
||||
self.target.name.lexeme()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,52 +0,0 @@
|
||||
use crate::common::*;
|
||||
use CompilationErrorKind::*;
|
||||
|
||||
pub(crate) struct AliasResolver<'a, 'b>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
aliases: &'b Table<'a, Alias<'a>>,
|
||||
recipes: &'b Table<'a, Rc<Recipe<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a: 'b, 'b> AliasResolver<'a, 'b> {
|
||||
pub(crate) fn resolve_aliases(
|
||||
aliases: &Table<'a, Alias<'a>>,
|
||||
recipes: &Table<'a, Rc<Recipe<'a>>>,
|
||||
) -> CompilationResult<'a, ()> {
|
||||
let resolver = AliasResolver { aliases, recipes };
|
||||
|
||||
resolver.resolve()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve(&self) -> CompilationResult<'a, ()> {
|
||||
for alias in self.aliases.values() {
|
||||
self.resolve_alias(alias)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_alias(&self, alias: &Alias<'a>) -> CompilationResult<'a, ()> {
|
||||
let token = alias.name.token();
|
||||
// Make sure the alias doesn't conflict with any recipe
|
||||
if let Some(recipe) = self.recipes.get(alias.name.lexeme()) {
|
||||
return Err(token.error(AliasShadowsRecipe {
|
||||
alias: alias.name.lexeme(),
|
||||
recipe_line: recipe.line_number(),
|
||||
}));
|
||||
}
|
||||
|
||||
// Make sure the target recipe exists
|
||||
if self.recipes.get(alias.target.lexeme()).is_none() {
|
||||
return Err(token.error(UnknownAliasTarget {
|
||||
alias: alias.name.lexeme(),
|
||||
target: alias.target.lexeme(),
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ use CompilationErrorKind::*;
|
||||
pub(crate) struct Analyzer<'src> {
|
||||
recipes: Table<'src, Recipe<'src, Name<'src>>>,
|
||||
assignments: Table<'src, Assignment<'src>>,
|
||||
aliases: Table<'src, Alias<'src>>,
|
||||
aliases: Table<'src, Alias<'src, Name<'src>>>,
|
||||
sets: Table<'src, Set<'src>>,
|
||||
}
|
||||
|
||||
@ -51,7 +51,6 @@ impl<'src> Analyzer<'src> {
|
||||
}
|
||||
|
||||
let assignments = self.assignments;
|
||||
let aliases = self.aliases;
|
||||
|
||||
AssignmentResolver::resolve_assignments(&assignments)?;
|
||||
|
||||
@ -67,7 +66,10 @@ impl<'src> Analyzer<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
AliasResolver::resolve_aliases(&aliases, &recipes)?;
|
||||
let mut aliases = Table::new();
|
||||
while let Some(alias) = self.aliases.pop() {
|
||||
aliases.insert(Self::resolve_alias(&recipes, alias)?);
|
||||
}
|
||||
|
||||
let mut settings = Settings::new();
|
||||
|
||||
@ -161,7 +163,7 @@ impl<'src> Analyzer<'src> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn analyze_alias(&self, alias: &Alias<'src>) -> CompilationResult<'src, ()> {
|
||||
fn analyze_alias(&self, alias: &Alias<'src, Name<'src>>) -> CompilationResult<'src, ()> {
|
||||
let name = alias.name.lexeme();
|
||||
|
||||
if let Some(original) = self.aliases.get(name) {
|
||||
@ -184,6 +186,29 @@ impl<'src> Analyzer<'src> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_alias(
|
||||
recipes: &Table<'src, Rc<Recipe<'src>>>,
|
||||
alias: Alias<'src, Name<'src>>,
|
||||
) -> CompilationResult<'src, Alias<'src>> {
|
||||
let token = alias.name.token();
|
||||
// Make sure the alias doesn't conflict with any recipe
|
||||
if let Some(recipe) = recipes.get(alias.name.lexeme()) {
|
||||
return Err(token.error(AliasShadowsRecipe {
|
||||
alias: alias.name.lexeme(),
|
||||
recipe_line: recipe.line_number(),
|
||||
}));
|
||||
}
|
||||
|
||||
// Make sure the target recipe exists
|
||||
match recipes.get(alias.target.lexeme()) {
|
||||
Some(target) => Ok(alias.resolve(target.clone())),
|
||||
None => Err(token.error(UnknownAliasTarget {
|
||||
alias: alias.name.lexeme(),
|
||||
target: alias.target.lexeme(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -47,7 +47,7 @@ pub(crate) use crate::{
|
||||
|
||||
// structs and enums
|
||||
pub(crate) use crate::{
|
||||
alias::Alias, alias_resolver::AliasResolver, analyzer::Analyzer, assignment::Assignment,
|
||||
alias::Alias, analyzer::Analyzer, assignment::Assignment,
|
||||
assignment_evaluator::AssignmentEvaluator, assignment_resolver::AssignmentResolver, color::Color,
|
||||
compilation_error::CompilationError, compilation_error_kind::CompilationErrorKind,
|
||||
compiler::Compiler, config::Config, config_error::ConfigError, count::Count,
|
||||
|
@ -441,10 +441,10 @@ impl Config {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !recipe_aliases.contains_key(alias.target.lexeme()) {
|
||||
recipe_aliases.insert(alias.target.lexeme(), vec![alias.name.lexeme()]);
|
||||
if !recipe_aliases.contains_key(alias.target.name.lexeme()) {
|
||||
recipe_aliases.insert(alias.target.name.lexeme(), vec![alias.name.lexeme()]);
|
||||
} else {
|
||||
let aliases = recipe_aliases.get_mut(alias.target.lexeme()).unwrap();
|
||||
let aliases = recipe_aliases.get_mut(alias.target.name.lexeme()).unwrap();
|
||||
aliases.push(alias.name.lexeme());
|
||||
}
|
||||
}
|
||||
@ -542,7 +542,7 @@ impl Config {
|
||||
|
||||
fn show(&self, name: &str, justfile: Justfile) -> Result<(), i32> {
|
||||
if let Some(alias) = justfile.get_alias(name) {
|
||||
let recipe = justfile.get_recipe(alias.target.lexeme()).unwrap();
|
||||
let recipe = justfile.get_recipe(alias.target.name.lexeme()).unwrap();
|
||||
println!("{}", alias);
|
||||
println!("{}", recipe);
|
||||
Ok(())
|
||||
|
@ -3,7 +3,7 @@ use crate::common::*;
|
||||
/// A single top-level item
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Item<'src> {
|
||||
Alias(Alias<'src>),
|
||||
Alias(Alias<'src, Name<'src>>),
|
||||
Assignment(Assignment<'src>),
|
||||
Recipe(Recipe<'src, Name<'src>>),
|
||||
Set(Set<'src>),
|
||||
|
@ -4,7 +4,7 @@ use crate::common::*;
|
||||
pub(crate) struct Justfile<'src> {
|
||||
pub(crate) recipes: Table<'src, Rc<Recipe<'src>>>,
|
||||
pub(crate) assignments: Table<'src, Assignment<'src>>,
|
||||
pub(crate) aliases: Table<'src, Alias<'src>>,
|
||||
pub(crate) aliases: Table<'src, Alias<'src, Rc<Recipe<'src>>>>,
|
||||
pub(crate) settings: Settings<'src>,
|
||||
pub(crate) warnings: Vec<Warning<'src>>,
|
||||
}
|
||||
@ -166,7 +166,7 @@ impl<'src> Justfile<'src> {
|
||||
if let Some(recipe) = self.recipes.get(name) {
|
||||
Some(recipe)
|
||||
} else if let Some(alias) = self.aliases.get(name) {
|
||||
self.recipes.get(alias.target.lexeme()).map(Rc::as_ref)
|
||||
Some(alias.target.as_ref())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ pub mod node;
|
||||
pub(crate) mod fuzzing;
|
||||
|
||||
mod alias;
|
||||
mod alias_resolver;
|
||||
mod analyzer;
|
||||
mod assignment;
|
||||
mod assignment_evaluator;
|
||||
|
@ -26,7 +26,7 @@ impl<'src> Node<'src> for Item<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'src> Node<'src> for Alias<'src> {
|
||||
impl<'src> Node<'src> for Alias<'src, Name<'src>> {
|
||||
fn tree(&self) -> Tree<'src> {
|
||||
Tree::atom(keyword::ALIAS)
|
||||
.push(self.name.lexeme())
|
||||
|
@ -325,7 +325,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
||||
}
|
||||
|
||||
/// Parse an alias, e.g `alias name := target`
|
||||
fn parse_alias(&mut self) -> CompilationResult<'src, Alias<'src>> {
|
||||
fn parse_alias(&mut self) -> CompilationResult<'src, Alias<'src, Name<'src>>> {
|
||||
self.presume_name(keyword::ALIAS)?;
|
||||
let name = self.parse_name()?;
|
||||
self.presume_any(&[Equals, ColonEquals])?;
|
||||
|
@ -8,6 +8,12 @@ pub(crate) struct Table<'key, V: Keyed<'key>> {
|
||||
}
|
||||
|
||||
impl<'key, V: Keyed<'key>> Table<'key, V> {
|
||||
pub(crate) fn new() -> Table<'key, V> {
|
||||
Table {
|
||||
map: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&mut self, value: V) {
|
||||
self.map.insert(value.key(), value);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user