mirror of
https://github.com/odin-lang/Odin.git
synced 2026-04-19 13:00:28 +00:00
Add "Suggestion: Did you mean?" for selector expression typos
This commit is contained in:
@@ -89,6 +89,19 @@ template <typename T>
|
||||
Slice<T> slice_from_array(Array<T> const &a) {
|
||||
return {a.data, a.count};
|
||||
}
|
||||
template <typename T>
|
||||
Slice<T> slice_array(Array<T> const &array, isize lo, isize hi) {
|
||||
GB_ASSERT(0 <= lo && lo <= hi && hi <= array.count);
|
||||
Slice<T> out = {};
|
||||
isize len = hi-lo;
|
||||
if (len > 0) {
|
||||
out.data = array.data+lo;
|
||||
out.count = len;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
Slice<T> slice_clone(gbAllocator const &allocator, Slice<T> const &a) {
|
||||
T *data = cast(T *)gb_alloc_copy_align(allocator, a.data, a.count*gb_size_of(T), gb_align_of(T));
|
||||
|
||||
@@ -580,6 +580,11 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
||||
error(ce->args[0],
|
||||
"'%s' has no field named '%.*s'", type_str, LIT(arg->token.string));
|
||||
gb_string_free(type_str);
|
||||
|
||||
Type *bt = base_type(type);
|
||||
if (bt->kind == Type_Struct) {
|
||||
check_did_you_mean_type(arg->token.string, bt->Struct.fields);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (sel.indirect) {
|
||||
@@ -3082,6 +3087,10 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
|
||||
error(ce->args[0],
|
||||
"'%s' has no field named '%.*s'", type_str, LIT(field_name));
|
||||
gb_string_free(type_str);
|
||||
|
||||
if (bt->kind == Type_Struct) {
|
||||
check_did_you_mean_type(field_name, bt->Struct.fields);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (sel.indirect) {
|
||||
|
||||
@@ -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<Entity *> 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);
|
||||
|
||||
@@ -325,13 +325,13 @@ gb_global u64 const unsigned_integer_maxs[] = {
|
||||
|
||||
|
||||
bool add_overflow_u64(u64 x, u64 y, u64 *result) {
|
||||
*result = x + y;
|
||||
return *result < x || *result < y;
|
||||
*result = x + y;
|
||||
return *result < x || *result < y;
|
||||
}
|
||||
|
||||
bool sub_overflow_u64(u64 x, u64 y, u64 *result) {
|
||||
*result = x - y;
|
||||
return *result > x;
|
||||
*result = x - y;
|
||||
return *result > x;
|
||||
}
|
||||
|
||||
void mul_overflow_u64(u64 x, u64 y, u64 *lo, u64 *hi) {
|
||||
@@ -1174,3 +1174,86 @@ ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
|
||||
#else
|
||||
#error Implement read_directory
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
isize levenstein_distance_case_insensitive(String const &a, String const &b) {
|
||||
isize w = a.len+1;
|
||||
isize h = b.len+1;
|
||||
isize *matrix = gb_alloc_array(temporary_allocator(), isize, w*h);
|
||||
for (isize i = 0; i <= a.len; i++) {
|
||||
matrix[i*w + 0] = i;
|
||||
}
|
||||
for (isize i = 0; i <= b.len; i++) {
|
||||
matrix[0*w + i] = i;
|
||||
}
|
||||
|
||||
for (isize i = 1; i <= a.len; i++) {
|
||||
char a_c = gb_char_to_lower(cast(char)a.text[i-1]);
|
||||
for (isize j = 1; j <= b.len; j++) {
|
||||
char b_c = gb_char_to_lower(cast(char)b.text[j-1]);
|
||||
if (a_c == b_c) {
|
||||
matrix[i*w + j] = matrix[(i-1)*w + j-1];
|
||||
} else {
|
||||
isize remove = matrix[(i-1)*w + j] + 1;
|
||||
isize insert = matrix[i*w + j-1] + 1;
|
||||
isize substitute = matrix[(i-1)*w + j-1] + 1;
|
||||
isize minimum = remove;
|
||||
if (insert < minimum) {
|
||||
minimum = insert;
|
||||
}
|
||||
if (substitute < minimum) {
|
||||
minimum = substitute;
|
||||
}
|
||||
matrix[i*w + j] = minimum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matrix[a.len*w + b.len];
|
||||
}
|
||||
|
||||
|
||||
struct DistanceAndTarget {
|
||||
isize distance;
|
||||
String target;
|
||||
};
|
||||
|
||||
struct DidYouMeanAnswers {
|
||||
Array<DistanceAndTarget> distances;
|
||||
String key;
|
||||
};
|
||||
|
||||
enum {MAX_SMALLEST_DID_YOU_MEAN_DISTANCE = 3};
|
||||
|
||||
DidYouMeanAnswers did_you_mean_make(gbAllocator allocator, isize cap, String const &key) {
|
||||
DidYouMeanAnswers d = {};
|
||||
array_init(&d.distances, allocator, 0, cap);
|
||||
d.key = key;
|
||||
return d;
|
||||
}
|
||||
void did_you_mean_destroy(DidYouMeanAnswers *d) {
|
||||
array_free(&d->distances);
|
||||
}
|
||||
void did_you_mean_append(DidYouMeanAnswers *d, String const &target) {
|
||||
if (target.len == 0 || target == "_") {
|
||||
return;
|
||||
}
|
||||
DistanceAndTarget dat = {};
|
||||
dat.target = target;
|
||||
dat.distance = levenstein_distance_case_insensitive(d->key, target);
|
||||
array_add(&d->distances, dat);
|
||||
}
|
||||
Slice<DistanceAndTarget> did_you_mean_results(DidYouMeanAnswers *d) {
|
||||
gb_sort_array(d->distances.data, d->distances.count, gb_isize_cmp(gb_offset_of(DistanceAndTarget, distance)));
|
||||
isize count = 0;
|
||||
for (isize i = 0; i < d->distances.count; i++) {
|
||||
isize distance = d->distances[i].distance;
|
||||
if (distance > MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) {
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
return slice_array(d->distances, 0, count);
|
||||
}
|
||||
|
||||
12
src/main.cpp
12
src/main.cpp
@@ -1106,24 +1106,24 @@ bool parse_build_flags(Array<String> args) {
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
struct DistanceAndTarget {
|
||||
struct DistanceAndTargetIndex {
|
||||
isize distance;
|
||||
isize target_index;
|
||||
};
|
||||
DistanceAndTarget distances[gb_count_of(named_targets)] = {};
|
||||
|
||||
DistanceAndTargetIndex distances[gb_count_of(named_targets)] = {};
|
||||
for (isize i = 0; i < gb_count_of(named_targets); i++) {
|
||||
distances[i].target_index = i;
|
||||
distances[i].distance = levenstein_distance_case_insensitive(str, named_targets[i].name);
|
||||
}
|
||||
gb_sort_array(distances, gb_count_of(distances), gb_isize_cmp(gb_offset_of(DistanceAndTarget, distance)));
|
||||
gb_sort_array(distances, gb_count_of(distances), gb_isize_cmp(gb_offset_of(DistanceAndTargetIndex, distance)));
|
||||
|
||||
gb_printf_err("Unknown target '%.*s'\n", LIT(str));
|
||||
|
||||
enum {MAX_SMALLEST_DISTANCE = 3};
|
||||
if (distances[0].distance <= MAX_SMALLEST_DISTANCE) {
|
||||
if (distances[0].distance <= MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) {
|
||||
gb_printf_err("Did you mean:\n");
|
||||
for (isize i = 0; i < gb_count_of(named_targets); i++) {
|
||||
if (distances[i].distance > MAX_SMALLEST_DISTANCE) {
|
||||
if (distances[i].distance > MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) {
|
||||
break;
|
||||
}
|
||||
gb_printf_err("\t%.*s\n", LIT(named_targets[distances[i].target_index].name));
|
||||
|
||||
@@ -779,41 +779,3 @@ i32 unquote_string(gbAllocator a, String *s_, u8 quote=0, bool has_carriage_retu
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
isize levenstein_distance_case_insensitive(String const &a, String const &b) {
|
||||
isize w = a.len+1;
|
||||
isize h = b.len+1;
|
||||
isize *matrix = gb_alloc_array(heap_allocator(), isize, w*h);
|
||||
for (isize i = 0; i <= a.len; i++) {
|
||||
matrix[i*w + 0] = i;
|
||||
}
|
||||
for (isize i = 0; i <= b.len; i++) {
|
||||
matrix[0*w + i] = i;
|
||||
}
|
||||
|
||||
for (isize i = 1; i <= a.len; i++) {
|
||||
char a_c = gb_char_to_lower(cast(char)a.text[i-1]);
|
||||
for (isize j = 1; j <= b.len; j++) {
|
||||
char b_c = gb_char_to_lower(cast(char)b.text[j-1]);
|
||||
if (a_c == b_c) {
|
||||
matrix[i*w + j] = matrix[(i-1)*w + j-1];
|
||||
} else {
|
||||
isize remove = matrix[(i-1)*w + j] + 1;
|
||||
isize insert = matrix[i*w + j-1] + 1;
|
||||
isize substitute = matrix[(i-1)*w + j-1] + 1;
|
||||
isize minimum = remove;
|
||||
if (insert < minimum) {
|
||||
minimum = insert;
|
||||
}
|
||||
if (substitute < minimum) {
|
||||
minimum = substitute;
|
||||
}
|
||||
matrix[i*w + j] = minimum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isize res = matrix[a.len*w + b.len];
|
||||
gb_free(heap_allocator(), matrix);
|
||||
return res;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user