From: Brendan Hansen Date: Wed, 24 Nov 2021 03:14:57 +0000 (-0600) Subject: more helpful error messages for misspelled symbols X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=ff7487a4817409b581f957b9703ecf65d45a39bb;p=onyx.git more helpful error messages for misspelled symbols --- diff --git a/include/utils.h b/include/utils.h index 48c58f5f..b209ed81 100644 --- a/include/utils.h +++ b/include/utils.h @@ -39,4 +39,7 @@ i32 string_process_escape_seqs(char* dest, char* src, i32 len); // :RelativeFiles This should lookup for the file relative to "relative_to" char* lookup_included_file(char* filename, char* relative_to, b32 add_onyx_suffix, b32 search_included_folders); +u32 levenshtein_distance(const char *str1, const char *str2); +char *find_closest_symbol_in_node(AstNode *node, char *sym); + extern AstTyped node_that_signals_a_yield; diff --git a/misc/vscode/onyx-0.0.1.vsix b/misc/vscode/onyx-0.0.1.vsix index 17a8a2e0..1e76b0e1 100644 Binary files a/misc/vscode/onyx-0.0.1.vsix and b/misc/vscode/onyx-0.0.1.vsix differ diff --git a/misc/vscode/package.json b/misc/vscode/package.json index afd7aae3..35a53b89 100644 --- a/misc/vscode/package.json +++ b/misc/vscode/package.json @@ -27,11 +27,12 @@ "owner": "onyx", "fileLocation": "absolute", "pattern": { - "regexp": "^\\(([^:]+):([0-9]+),([0-9]+)\\) (.*)", + "regexp": "\\((/.*|[a-zA-Z]:[^:]+):([0-9]+),([0-9]+)\\) (.*)", "file": 1, "line": 2, "column": 3, - "message": 4 + "message": 4, + "loop": true } }] } diff --git a/src/astnodes.c b/src/astnodes.c index e56f21d6..eafc64dd 100644 --- a/src/astnodes.c +++ b/src/astnodes.c @@ -573,7 +573,23 @@ TypeMatch unify_node_and_type_(AstTyped** pnode, Type* type, b32 permanent) { if (node->kind == Ast_Kind_Unary_Field_Access) { AstType* ast_type = type->ast_type; AstNode* resolved = try_symbol_resolve_from_node((AstNode *) ast_type, node->token); - if (resolved == NULL) return TYPE_MATCH_YIELD; + if (resolved == NULL) { + if (context.cycle_detected) { + token_toggle_end(node->token); + char *closest = find_closest_symbol_in_node((AstNode *) ast_type, node->token->text); + token_toggle_end(node->token); + + if (closest) { + onyx_report_error(node->token->pos, "'%b' does not exist in '%s'. Did you mean '%s'?", + node->token->text, node->token->length, + type_get_name(type), + closest); + return TYPE_MATCH_FAILED; + } + } + + return TYPE_MATCH_YIELD; + } if (permanent) *pnode = (AstTyped *) resolved; return TYPE_MATCH_SUCCESS; diff --git a/src/checker.c b/src/checker.c index 1276a6af..02f32c1b 100644 --- a/src/checker.c +++ b/src/checker.c @@ -1529,10 +1529,19 @@ CheckStatus check_field_access(AstFieldAccess** pfield) { return Check_Success; } - ERROR_(field->token->pos, - "Field '%s' does not exists on '%s'.", - field->field, - node_get_type_name(field->expr)); + char* closest = find_closest_symbol_in_node((AstNode *) type_node, field->field); + if (closest) { + ERROR_(field->token->pos, + "Field '%s' does not exists on '%s'. Did you mean '%s'?", + field->field, + node_get_type_name(field->expr), + closest); + } else { + ERROR_(field->token->pos, + "Field '%s' does not exists on '%s'.", + field->field, + node_get_type_name(field->expr)); + } } field->offset = smem.offset; diff --git a/src/symres.c b/src/symres.c index d37f7b18..069b9cd1 100644 --- a/src/symres.c +++ b/src/symres.c @@ -282,11 +282,24 @@ static SymresStatus symres_field_access(AstFieldAccess** fa) { if (resolution) *((AstNode **) fa) = resolution; else if (expr->kind == Ast_Kind_Package) { if (report_unresolved_symbols) { - onyx_report_error((*fa)->token->pos, "'%b' was not found in package '%s'. Perhaps it is defined in a file that wasn't loaded?", - (*fa)->token->text, - (*fa)->token->length, - ((AstPackage *) (*fa)->expr)->package->name); + token_toggle_end((*fa)->token); + char *closest = find_closest_symbol_in_node((AstNode *) expr, (*fa)->token->text); + token_toggle_end((*fa)->token); + + if (closest) { + onyx_report_error((*fa)->token->pos, "'%b' was not found in package '%s'. Did you mean '%s'?", + (*fa)->token->text, + (*fa)->token->length, + ((AstPackage *) (*fa)->expr)->package->name, + closest); + } else { + onyx_report_error((*fa)->token->pos, "'%b' was not found in package '%s'. Perhaps it is defined in a file that wasn't loaded?", + (*fa)->token->text, + (*fa)->token->length, + ((AstPackage *) (*fa)->expr)->package->name); + } return Symres_Error; + } else { return Symres_Yield_Macro; } diff --git a/src/utils.c b/src/utils.c index ed541dfb..4b185f24 100644 --- a/src/utils.c +++ b/src/utils.c @@ -988,3 +988,134 @@ char* lookup_included_file(char* filename, char* relative_to, b32 add_onyx_suffi #undef DIR_SEPARATOR } +u32 levenshtein_distance(const char *str1, const char *str2) { + i32 m = strlen(str1); + i32 n = strlen(str2); + + i32 *d = bh_alloc_array(global_scratch_allocator, i32, m * n); + fori (i, 0, m * n) d[i] = 0; + + fori (i, 0, m) d[i * n + 0] = i; + fori (j, 0, n) d[0 * n + j] = j; + + fori (j, 0, n) { + fori (i, 0, m) { + i32 subst_cost = str1[i] == str2[j] ? 0 : 1; + + i32 a = d[(i - 1) * n + j] + 1; + i32 b = d[i * n + (j - 1)] + 1; + i32 c = d[(i - 1) * n + (j - 1)] + subst_cost; + + d[i * n + j] = min(min(a, b), c); + } + } + + return d[m * n - 1]; +} + +char *find_closest_symbol_in_scope(Scope *scope, char *sym, u32 *out_distance) { + *out_distance = 0x7fffffff; + + if (scope == NULL) return NULL; + + char* closest = NULL; + bh_table_each_start(AstNode *, scope->symbols); + u32 d = levenshtein_distance(key, sym); + if (d < *out_distance) { + *out_distance = d; + closest = (char *) key; + } + bh_table_each_end; + + return closest; +} + +char *find_closest_symbol_in_node(AstNode* node, char *sym) { + b32 used_pointer = 0; + + while (1) { + if (!node) return NULL; + + switch (node->kind) { + case Ast_Kind_Type_Raw_Alias: node = (AstNode *) ((AstTypeRawAlias *) node)->to->ast_type; break; + case Ast_Kind_Type_Alias: node = (AstNode *) ((AstTypeAlias *) node)->to; break; + case Ast_Kind_Alias: node = (AstNode *) ((AstAlias *) node)->alias; break; + case Ast_Kind_Pointer_Type: { + if (used_pointer) goto all_types_peeled_off; + used_pointer = 1; + + node = (AstNode *) ((AstPointerType *) node)->elem; + break; + } + + default: goto all_types_peeled_off; + } + } + +all_types_peeled_off: + if (!node) return NULL; + + switch (node->kind) { + case Ast_Kind_Package: { + AstPackage* package = (AstPackage *) node; + if (package->package == NULL) return NULL; + + u32 dist; + return find_closest_symbol_in_scope(package->package->scope, sym, &dist); + } + + case Ast_Kind_Enum_Type: { + AstEnumType* etype = (AstEnumType *) node; + u32 dist; + return find_closest_symbol_in_scope(etype->scope, sym, &dist); + } + + case Ast_Kind_Struct_Type: { + AstStructType* stype = (AstStructType *) node; + + u32 dist; + char *closest = find_closest_symbol_in_scope(stype->scope, sym, &dist); + + Type *type = type_build_from_ast(context.ast_alloc, (AstType *) stype); + assert(type); + bh_arr_each(StructMember *, mem, type->Struct.memarr) { + u32 d = levenshtein_distance((*mem)->name, sym); + if (d < dist) { + dist = d; + closest = (*mem)->name; + } + } + + return closest; + } + + case Ast_Kind_Poly_Struct_Type: { + AstStructType* stype = ((AstPolyStructType *) node)->base_struct; + u32 dist; + return find_closest_symbol_in_scope(stype->scope, sym, &dist); + } + + case Ast_Kind_Poly_Call_Type: { + AstPolyCallType* pcall = (AstPolyCallType *) node; + + u32 dist = 0x7fffffff; + char *closest = NULL; + + Type *type = type_build_from_ast(context.ast_alloc, (AstType *) pcall); + assert(type); + + bh_arr_each(StructMember *, mem, type->Struct.memarr) { + i32 d = levenshtein_distance((*mem)->name, sym); + if (d < dist) { + dist = d; + closest = (*mem)->name; + } + } + + return closest; + } + } + + return NULL; +} +