From ff7487a4817409b581f957b9703ecf65d45a39bb Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Tue, 23 Nov 2021 21:14:57 -0600 Subject: [PATCH] more helpful error messages for misspelled symbols --- include/utils.h | 3 + misc/vscode/onyx-0.0.1.vsix | Bin 3665 -> 3687 bytes misc/vscode/package.json | 5 +- src/astnodes.c | 18 ++++- src/checker.c | 17 +++-- src/symres.c | 21 ++++-- src/utils.c | 131 ++++++++++++++++++++++++++++++++++++ 7 files changed, 184 insertions(+), 11 deletions(-) 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 17a8a2e04d625beb495cc9bd35776b460c5a213f..1e76b0e142d4a076a1e60ff083d80a00c6dfa679 100644 GIT binary patch delta 606 zcmca8^IV2Ez?+#xgn@yBgTZK7`9|ItCT1W#Ifv;5^Omld&Ed@Z7@75f0urp6^}fYT z1_EvG|8ccVj_Q^F8kE2Hc7Ji>IhGh_E5*b~7qWE!?`;$aQ8PNsv-$k)+2*@XSBlI~ zf3l;YK)R)Ae&540$G(M3<(VI6{iUqeA$8KUSzPY|7WlW z7M!##^P=k8ST3c)K#A~!e8oYDNh-SjF{>U#UD|T#uyW*jb5;G1(=Ps9kZXIeCn>df z*S*qRDGDu%+3syQo;5K%<&3PxN|iEOckcA;y2BTy8#USO+@~e7P4>X0a{ll)_i}x% zdl#%;;8L$@>|ht$S(^9uW-Y_kj1&*?b+60!ObuGX(#X_U zAo<4S+IGI>UIxZ#{pGxq?(^(#7vhjPt!CSI{4wjSf36k(e%t)_*mm)`S>=qJ;}?tm zrP!stQ|?ngDB-K#TwLY9?8d3)8Ie`Z*W{*1-giy@aFLnYB;b@^!Q<6hE|23*bLAz} zn)3Zt?|pUAKERuuEHRN9jEtM=-6#YbOhg z+TbPgMf3B3S{5@iFsL#h!2ij$yk>mBI1fe`I{6^4nF7$X;MM+DayGCsFmS0eFeoAP XF#K-E`RCGX6yzPRK-#4b(U%+q?uKD>+8a8;e!vgBoXnuV?Gy3Ge{ z6pyR6>{bx@bE$=U_w?XU!=w$nNqHs9yyQAP8CE|H_S73&>+f$^8>05<-i6HGZPFs`w%P5oc(N6o zF710$p#6JVlSj<7W1$<4&$%)~!t;Fpx{w0h%&6Bd)o-pa*UkTJyZEQdwuuRD8#kFc zmo>k$5OiAHSpG3W_fuETPGi-rNA!xf6m6Tv&+}J9S)%*)Znwnw4Ab@%??^lEmmIYI zKyQ=K)-w$CKTAZfwQfy+&Uj_jWX0HBSLRo&Ug9A+MLr@uO|C}F*4WDEB@_gpl68CKK zy~X^O6W%(W;o0RV&3~%r`X=MA&l*)VQuA|vGu+-i;q8CJ>~y*R0p9E!HO$jB8yFcF z-Y_#TK;n)A7lPB|< w@d0Bu7-8t->%3;nK+`4*@JX^PX*|z1nUP&ZEWn$U4Wy412y=iYo#z4Z0G!hBD*ylh 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; +} + -- 2.25.1