From 35230b1a11940117ee218066ef5cd11243a23456 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 8 Jul 2021 23:15:07 +0100 Subject: Add "Suggestion: Did you mean?" for selector expression typos --- src/check_expr.cpp | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) (limited to 'src/check_expr.cpp') diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 20e54ab3d..9b2d17206 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3576,6 +3576,39 @@ ExactValue get_constant_field(CheckerContext *c, Operand const *operand, Selecti if (success_) *success_ = true; return empty_exact_value; } +void check_did_you_mean_print(DidYouMeanAnswers *d) { + auto results = did_you_mean_results(d); + if (results.count != 0) { + error_line("\tSuggestion: Did you mean?\n"); + for_array(i, results) { + String const &target = results[i].target; + error_line("\t\t%.*s\n", LIT(target)); + } + } +} + +void check_did_you_mean_type(String const &name, Array const &fields) { + DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), fields.count, name); + defer (did_you_mean_destroy(&d)); + + for_array(i, fields) { + did_you_mean_append(&d, fields[i]->token.string); + } + check_did_you_mean_print(&d); +} + +void check_did_you_mean_scope(String const &name, Scope *scope) { + DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), scope->elements.entries.count, name); + defer (did_you_mean_destroy(&d)); + + for_array(i, scope->elements.entries) { + Entity *e = scope->elements.entries[i].value; + did_you_mean_append(&d, e->token.string); + } + check_did_you_mean_print(&d); +} + + Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *type_hint) { ast_node(se, SelectorExpr, node); @@ -3641,6 +3674,8 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ error(op_expr, "'%.*s' is not declared by '%.*s'", LIT(entity_name), LIT(import_name)); operand->mode = Addressing_Invalid; operand->expr = node; + + check_did_you_mean_scope(entity_name, import_scope); return nullptr; } @@ -3818,6 +3853,17 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ gbString type_str = type_to_string(operand->type); gbString sel_str = expr_to_string(selector); error(op_expr, "'%s' of type '%s' has no field '%s'", op_str, type_str, sel_str); + + if (operand->type != nullptr && selector->kind == Ast_Ident) { + String const &name = selector->Ident.token.string; + Type *bt = base_type(operand->type); + if (bt->kind == Type_Struct) { + check_did_you_mean_type(name, bt->Struct.fields); + } else if (bt->kind == Type_Enum) { + check_did_you_mean_type(name, bt->Enum.fields); + } + } + gb_string_free(sel_str); gb_string_free(type_str); gb_string_free(op_str); @@ -6180,9 +6226,14 @@ ExprKind check_implicit_selector_expr(CheckerContext *c, Operand *o, Ast *node, String name = ise->selector->Ident.token.string; if (is_type_enum(th)) { + Type *bt = base_type(th); + GB_ASSERT(bt->kind == Type_Enum); + gbString typ = type_to_string(th); - error(node, "Undeclared name %.*s for type '%s'", LIT(name), typ); - gb_string_free(typ); + defer (gb_string_free(typ)); + error(node, "Undeclared name '%.*s' for type '%s'", LIT(name), typ); + + check_did_you_mean_type(name, bt->Enum.fields); } else { gbString typ = type_to_string(th); gbString str = expr_to_string(node); -- cgit v1.2.3