feat: implement better error messages with 'Did you mean?' suggestions
Add Levenshtein distance-based similarity matching for undefined variables, unknown types, unknown effects, and unknown traits. When a name is not found, the error now suggests similar names within edit distance 2. Changes: - Add levenshtein_distance() function to diagnostics module - Add find_similar_names() and format_did_you_mean() helpers - Update typechecker to suggest similar names for: - Undefined variables - Unknown types - Unknown effects - Unknown traits - Add 17 new tests for similarity matching Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ use crate::ast::{
|
||||
ImportDecl, LetDecl, Literal, LiteralKind, MatchArm, Parameter, Pattern, Program, Span,
|
||||
Statement, TraitDecl, TypeDecl, TypeExpr, UnaryOp, VariantFields,
|
||||
};
|
||||
use crate::diagnostics::{Diagnostic, Severity};
|
||||
use crate::diagnostics::{find_similar_names, format_did_you_mean, Diagnostic, Severity};
|
||||
use crate::exhaustiveness::{check_exhaustiveness, missing_patterns_hint};
|
||||
use crate::modules::ModuleLoader;
|
||||
use crate::types::{
|
||||
@@ -500,8 +500,19 @@ impl TypeChecker {
|
||||
// Validate that each trait in the bounds exists
|
||||
for bound in &constraint.bounds {
|
||||
if !self.env.traits.contains_key(&bound.trait_name.name) {
|
||||
// Find similar trait names for suggestion
|
||||
let available_traits: Vec<&str> = self.env.traits.keys()
|
||||
.map(|s| s.as_str())
|
||||
.collect();
|
||||
let suggestions = find_similar_names(&bound.trait_name.name, available_traits, 2);
|
||||
|
||||
let mut message = format!("Unknown trait: {}", bound.trait_name.name);
|
||||
if let Some(hint) = format_did_you_mean(&suggestions) {
|
||||
message.push_str(&format!(". {}", hint));
|
||||
}
|
||||
|
||||
self.errors.push(TypeError {
|
||||
message: format!("Unknown trait: {}", bound.trait_name.name),
|
||||
message,
|
||||
span: bound.span,
|
||||
});
|
||||
}
|
||||
@@ -537,8 +548,19 @@ impl TypeChecker {
|
||||
let effect = match self.env.lookup_effect(&handler.effect.name) {
|
||||
Some(e) => e.clone(),
|
||||
None => {
|
||||
// Find similar effect names for suggestion
|
||||
let available_effects: Vec<&str> = self.env.effects.keys()
|
||||
.map(|s| s.as_str())
|
||||
.collect();
|
||||
let suggestions = find_similar_names(&handler.effect.name, available_effects, 2);
|
||||
|
||||
let mut message = format!("Unknown effect: {}", handler.effect.name);
|
||||
if let Some(hint) = format_did_you_mean(&suggestions) {
|
||||
message.push_str(&format!(". {}", hint));
|
||||
}
|
||||
|
||||
self.errors.push(TypeError {
|
||||
message: format!("Unknown effect: {}", handler.effect.name),
|
||||
message,
|
||||
span: handler.effect.span,
|
||||
});
|
||||
return;
|
||||
@@ -595,8 +617,19 @@ impl TypeChecker {
|
||||
Expr::Var(ident) => match self.env.lookup(&ident.name) {
|
||||
Some(scheme) => scheme.instantiate(),
|
||||
None => {
|
||||
// Find similar variable names for "Did you mean?" suggestion
|
||||
let available_names: Vec<&str> = self.env.bindings.keys()
|
||||
.map(|s| s.as_str())
|
||||
.collect();
|
||||
let suggestions = find_similar_names(&ident.name, available_names, 2);
|
||||
|
||||
let mut message = format!("Undefined variable: {}", ident.name);
|
||||
if let Some(hint) = format_did_you_mean(&suggestions) {
|
||||
message.push_str(&format!(". {}", hint));
|
||||
}
|
||||
|
||||
self.errors.push(TypeError {
|
||||
message: format!("Undefined variable: {}", ident.name),
|
||||
message,
|
||||
span: ident.span,
|
||||
});
|
||||
Type::Error
|
||||
@@ -1423,8 +1456,19 @@ impl TypeChecker {
|
||||
|
||||
// Verify the effect exists
|
||||
if self.env.lookup_effect(&effect_name.name).is_none() {
|
||||
// Find similar effect names for suggestion
|
||||
let available_effects: Vec<&str> = self.env.effects.keys()
|
||||
.map(|s| s.as_str())
|
||||
.collect();
|
||||
let suggestions = find_similar_names(&effect_name.name, available_effects, 2);
|
||||
|
||||
let mut message = format!("Unknown effect: {}", effect_name.name);
|
||||
if let Some(hint) = format_did_you_mean(&suggestions) {
|
||||
message.push_str(&format!(". {}", hint));
|
||||
}
|
||||
|
||||
self.errors.push(TypeError {
|
||||
message: format!("Unknown effect: {}", effect_name.name),
|
||||
message,
|
||||
span: effect_name.span,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user