use strsim to suggest similar identifiers in UnknownIdentifier error

This commit is contained in:
Moritz Hölting 2025-08-27 17:05:21 +02:00
parent d9f2d99c3a
commit ef7bf95447
9 changed files with 140 additions and 52 deletions

View File

@ -456,9 +456,10 @@ impl Assignment {
return Err(err);
}
} else {
let err = error::Error::UnknownIdentifier(UnknownIdentifier {
identifier: self.destination().span(),
});
let err = error::Error::UnknownIdentifier(UnknownIdentifier::from_semantic_scope(
self.destination().span(),
scope,
));
handler.receive(err.clone());
return Err(err);
}
@ -574,9 +575,9 @@ impl Primary {
match self {
Self::Identifier(ident) => {
if scope.get_variable(ident.span.str()).is_none() {
let err = error::Error::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span.clone(),
});
let err = error::Error::UnknownIdentifier(
UnknownIdentifier::from_semantic_scope(ident.span.clone(), scope),
);
handler.receive(err.clone());
Err(err)
} else {
@ -589,9 +590,12 @@ impl Primary {
let var = scope.get_variable(call.identifier().span.str());
var.map_or_else(
|| {
let err = error::Error::UnknownIdentifier(UnknownIdentifier {
identifier: call.identifier().span.clone(),
});
let err = error::Error::UnknownIdentifier(
UnknownIdentifier::from_semantic_scope(
call.identifier().span.clone(),
scope,
),
);
handler.receive(err.clone());
Err(err)
},
@ -661,8 +665,8 @@ impl Primary {
Err(err)
}
}
Self::MemberAccess(_) => {
// TODO:
Self::MemberAccess(member_access) => {
member_access.parent().analyze_semantics(scope, handler)?;
Ok(())
}
Self::Parenthesized(expr) => expr.analyze_semantics(scope, handler),
@ -971,9 +975,12 @@ impl TemplateStringLiteral {
match expression.as_ref() {
Expression::Primary(Primary::Identifier(identifier)) => {
if scope.get_variable(identifier.span.str()).is_none() {
let err = error::Error::UnknownIdentifier(UnknownIdentifier {
identifier: identifier.span.clone(),
});
let err = error::Error::UnknownIdentifier(
UnknownIdentifier::from_semantic_scope(
identifier.span.clone(),
scope,
),
);
handler.receive(err.clone());
errs.push(err);
}
@ -999,9 +1006,12 @@ impl TemplateStringLiteral {
}
}
} else {
let err = error::Error::UnknownIdentifier(UnknownIdentifier {
identifier: identifier.span.clone(),
});
let err = error::Error::UnknownIdentifier(
UnknownIdentifier::from_semantic_scope(
identifier.span.clone(),
scope,
),
);
handler.receive(err.clone());
errs.push(err);
}

View File

@ -1,5 +1,7 @@
use std::{collections::HashMap, sync::RwLock};
use crate::{base::source_file::Span, transpile::error::UnknownIdentifier};
/// Type of variable
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub enum VariableType {
@ -95,3 +97,29 @@ impl<'a> SemanticScope<'a> {
self.parent
}
}
impl UnknownIdentifier {
pub(super) fn from_semantic_scope(identifier: Span, scope: &SemanticScope<'_>) -> Self {
use itertools::Itertools as _;
let own_name = identifier.str();
let alternatives = scope
.get_all_variables()
.iter()
.filter_map(|(name, _)| {
let normalized_distance = strsim::normalized_damerau_levenshtein(own_name, name);
(normalized_distance > 0.8 || strsim::damerau_levenshtein(own_name, name) < 3)
.then_some((normalized_distance, name))
})
.sorted_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal))
.map(|(_, data)| data)
.take(8)
.cloned()
.collect::<Vec<_>>();
Self {
identifier,
alternatives,
}
}
}

View File

@ -303,11 +303,46 @@ impl std::error::Error for AssignmentError {}
pub struct UnknownIdentifier {
/// The unknown identifier.
#[get = "pub"]
pub identifier: Span,
pub(crate) identifier: Span,
/// Alternatives to the current identifier
#[get = "pub"]
pub(crate) alternatives: Vec<String>,
}
impl UnknownIdentifier {
#[cfg(feature = "shulkerbox")]
pub(crate) fn from_scope(
identifier: Span,
scope: &std::sync::Arc<super::variables::Scope>,
) -> Self {
use itertools::Itertools as _;
let own_name = identifier.str();
let alternatives = scope
.get_all_variables()
.iter()
.filter_map(|(name, _)| {
let normalized_distance = strsim::normalized_damerau_levenshtein(own_name, name);
(normalized_distance > 0.8 || strsim::damerau_levenshtein(own_name, name) < 3)
.then_some((normalized_distance, name))
})
.sorted_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal))
.map(|(_, data)| data)
.take(8)
.cloned()
.collect::<Vec<_>>();
Self {
identifier,
alternatives,
}
}
}
impl Display for UnknownIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::fmt::Write as _;
write!(
f,
"{}",
@ -317,10 +352,23 @@ impl Display for UnknownIdentifier {
)
)?;
let help_message = if self.alternatives.is_empty() {
None
} else {
let mut message = String::from("did you mean ");
for (i, alternative) in self.alternatives.iter().enumerate() {
if i > 0 {
message.push_str(", ");
}
let _ = write!(message, "`{alternative}`");
}
Some(message + "?")
};
write!(
f,
"\n{}",
SourceCodeDisplay::new(&self.identifier, Option::<u8>::None)
SourceCodeDisplay::new(&self.identifier, help_message.as_ref())
)
}
}

View File

@ -1460,9 +1460,10 @@ impl Transpiler {
self.move_data(&from, target, primary, handler)
} else {
let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span.clone(),
});
let err = TranspileError::UnknownIdentifier(UnknownIdentifier::from_scope(
ident.span.clone(),
scope,
));
handler.receive(Box::new(err.clone()));
Err(err)
}
@ -1587,9 +1588,10 @@ impl Transpiler {
self.move_data(&from, target, primary, handler)
} else {
let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span.clone(),
});
let err = TranspileError::UnknownIdentifier(UnknownIdentifier::from_scope(
ident.span.clone(),
scope,
));
handler.receive(Box::new(err.clone()));
Err(err)
}
@ -1740,9 +1742,10 @@ impl Transpiler {
}
}
} else {
let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span.clone(),
});
let err = TranspileError::UnknownIdentifier(UnknownIdentifier::from_scope(
ident.span.clone(),
scope,
));
handler.receive(Box::new(err.clone()));
Err(err)
}
@ -1816,9 +1819,10 @@ impl Transpiler {
}
}
} else {
let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span.clone(),
});
let err = TranspileError::UnknownIdentifier(UnknownIdentifier::from_scope(
ident.span.clone(),
scope,
));
handler.receive(Box::new(err.clone()));
Err(err)
}

View File

@ -455,9 +455,7 @@ impl Transpiler {
let var =
scope.get_variable(ident.span.str()).ok_or_else(|| {
let err =
TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span(),
});
TranspileError::UnknownIdentifier(UnknownIdentifier::from_scope(ident.span(), scope));
handler.receive(Box::new(err.clone()));
err
})?;

View File

@ -156,9 +156,9 @@ fn print_function(
))),
}
} else {
Err(TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span(),
}))
Err(TranspileError::UnknownIdentifier(
UnknownIdentifier::from_scope(ident.span(), scope),
))
}
}
@ -289,9 +289,9 @@ fn print_function(
))),
}
} else {
Err(TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span(),
}))
Err(TranspileError::UnknownIdentifier(
UnknownIdentifier::from_scope(ident.span(), scope),
))
}
}

View File

@ -295,9 +295,9 @@ mod enabled {
}));
}
None => {
return Err(TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: identifier.span(),
}));
return Err(TranspileError::UnknownIdentifier(
UnknownIdentifier::from_scope(identifier.span(), scope),
));
}
};

View File

@ -552,9 +552,10 @@ impl Transpiler {
}
}
} else {
let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span.clone(),
});
let err = TranspileError::UnknownIdentifier(UnknownIdentifier::from_scope(
ident.span.clone(),
scope,
));
handler.receive(Box::new(err.clone()));
return Err(err);
}
@ -623,9 +624,10 @@ impl Transpiler {
Err(err)
}
None => {
let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span.clone(),
});
let err = TranspileError::UnknownIdentifier(UnknownIdentifier::from_scope(
ident.span.clone(),
scope,
));
handler.receive(Box::new(err.clone()));
Err(err)
}

View File

@ -326,9 +326,7 @@ impl TemplateStringLiteral {
}
} else {
let err =
TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: identifier.span(),
});
TranspileError::UnknownIdentifier(UnknownIdentifier::from_scope(identifier.span(), scope));
handler.receive(Box::new(err.clone()));
Err(err)
}