Просмотр исходного кода

Add "Suggestion: Did you mean?" for selector expression typos

gingerBill 4 лет назад
Родитель
Сommit
35230b1a11
6 измененных файлов с 168 добавлено и 50 удалено
  1. 13 0
      src/array.cpp
  2. 9 0
      src/check_builtin.cpp
  3. 53 2
      src/check_expr.cpp
  4. 87 4
      src/common.cpp
  5. 6 6
      src/main.cpp
  6. 0 38
      src/string.cpp

+ 13 - 0
src/array.cpp

@@ -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));

+ 9 - 0
src/check_builtin.cpp

@@ -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) {

+ 53 - 2
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<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);

+ 87 - 4
src/common.cpp

@@ -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);
+}

+ 6 - 6
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));

+ 0 - 38
src/string.cpp

@@ -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;
-}