more helpful error messages for misspelled symbols
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 24 Nov 2021 03:14:57 +0000 (21:14 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 24 Nov 2021 03:14:57 +0000 (21:14 -0600)
include/utils.h
misc/vscode/onyx-0.0.1.vsix
misc/vscode/package.json
src/astnodes.c
src/checker.c
src/symres.c
src/utils.c

index 48c58f5fd003c480a23781e16365f1b14bdd40e7..b209ed81c96aee6c10e3054510d17a89eb051398 100644 (file)
@@ -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;
index 17a8a2e04d625beb495cc9bd35776b460c5a213f..1e76b0e142d4a076a1e60ff083d80a00c6dfa679 100644 (file)
Binary files a/misc/vscode/onyx-0.0.1.vsix and b/misc/vscode/onyx-0.0.1.vsix differ
index afd7aae3040b483e535640f273dc1ef6f6108984..35a53b89080a08cc24d52390871a8edc7bdcfa05 100644 (file)
             "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
             }
         }]
     }
index e56f21d6cc9e496317fc132059d331d071b996aa..eafc64ddd270bf80b14676e316f8be4f06e4a939 100644 (file)
@@ -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;
index 1276a6afa4b572c753dfc618537d16785c6c5ae2..02f32c1bbccc59d170c0ef7d8e66305e5f04b2d7 100644 (file)
@@ -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;
index d37f7b182ea196167028e1d72dd5522ba577ad9c..069b9cd1790e760015ccb853be1e7191f8ccf9bf 100644 (file)
@@ -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;
         }
index ed541dfbd88ffa4994b36ca172b0a92eab434097..4b185f24a6926910782175b3d7b39763dedf22a8 100644 (file)
@@ -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;
+}
+