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;
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;
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;
}
#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;
+}
+