diff --git a/src/semantic/mod.rs b/src/semantic/mod.rs index 8368a42..71e3369 100644 --- a/src/semantic/mod.rs +++ b/src/semantic/mod.rs @@ -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); } diff --git a/src/semantic/scope.rs b/src/semantic/scope.rs index 6c9b4e3..94d9037 100644 --- a/src/semantic/scope.rs +++ b/src/semantic/scope.rs @@ -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::>(); + + Self { + identifier, + alternatives, + } + } +} diff --git a/src/transpile/error.rs b/src/transpile/error.rs index 4021e0c..b232b49 100644 --- a/src/transpile/error.rs +++ b/src/transpile/error.rs @@ -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, +} + +impl UnknownIdentifier { + #[cfg(feature = "shulkerbox")] + pub(crate) fn from_scope( + identifier: Span, + scope: &std::sync::Arc, + ) -> 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::>(); + + 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::::None) + SourceCodeDisplay::new(&self.identifier, help_message.as_ref()) ) } } diff --git a/src/transpile/expression.rs b/src/transpile/expression.rs index 2ad772c..ff72698 100644 --- a/src/transpile/expression.rs +++ b/src/transpile/expression.rs @@ -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) } diff --git a/src/transpile/function.rs b/src/transpile/function.rs index e32677c..70cd4b3 100644 --- a/src/transpile/function.rs +++ b/src/transpile/function.rs @@ -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 })?; diff --git a/src/transpile/internal_functions.rs b/src/transpile/internal_functions.rs index 075acf0..449259c 100644 --- a/src/transpile/internal_functions.rs +++ b/src/transpile/internal_functions.rs @@ -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), + )) } } diff --git a/src/transpile/lua.rs b/src/transpile/lua.rs index e60ffe0..81bf181 100644 --- a/src/transpile/lua.rs +++ b/src/transpile/lua.rs @@ -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), + )); } }; diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 1a1eca3..6aa5a36 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -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) } diff --git a/src/transpile/util.rs b/src/transpile/util.rs index 87ff769..2318e76 100644 --- a/src/transpile/util.rs +++ b/src/transpile/util.rs @@ -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) }