Skip to content

Commit

Permalink
Suggest most similar function id when we can't find a function type
Browse files Browse the repository at this point in the history
Find identifier with smallest optimal string alignment (OSA) distance
(within reason) and suggest it. The max difference is the identifier
length - 3 clamped between 1 and 4. So a 4 character identifier will
only suggest 1 change edits, a 5 character identifier 2 character edits
and so on up to 4 edits.
  • Loading branch information
Alasdair committed Dec 1, 2023
1 parent 818a7b9 commit 6764ad3
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 2 deletions.
19 changes: 17 additions & 2 deletions src/lib/type_env.ml
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,7 @@ let add_typ_synonym id typq arg env =

let get_val_spec_orig id env =
try get_item (id_loc id) env (Bindings.find id env.global.val_specs)
with Not_found -> typ_error (id_loc id) ("No type signature found for " ^ string_of_id id)
with Not_found -> typ_error (id_loc id) ("No function type found for " ^ string_of_id id)

let get_val_spec_opt id env =
match Bindings.find_opt id env.global.val_specs with
Expand All @@ -922,7 +922,22 @@ let get_val_spec_opt id env =
let get_val_spec id env =
match get_val_spec_opt id env with
| Some (bind, _) -> bind
| None -> typ_error (id_loc id) ("No type declaration found for " ^ string_of_id id)
| None ->
(* Try to find the most similar function name, within reason, to include in the error *)
let closest = ref (Int.max_int, None) in
Bindings.iter
(fun other_id item ->
let id_str = string_of_id id in
let other_str = string_of_id other_id in
if abs (String.length id_str - String.length other_str) <= 2 then (
let distance = Util.levenshtein_distance ~osa:true id_str other_str in
let max_distance = min 4 (max 1 (String.length id_str - 3)) in
if distance <= max_distance && distance < fst !closest then closest := (distance, Some other_str)
)
)
env.global.val_specs;
let hint_msg = match snd !closest with Some other_str -> "\n\nDid you mean " ^ other_str ^ "?" | None -> "" in
typ_error (id_loc id) ("No function type found for " ^ string_of_id id ^ hint_msg)

let get_val_specs env = filter_items env env.global.val_specs

Expand Down
21 changes: 21 additions & 0 deletions src/lib/util.ml
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,27 @@ let list_init len f =
let rec list_init' len f acc = if acc >= len then [] else f acc :: list_init' len f (acc + 1) in
list_init' len f 0

let levenshtein_distance ?(osa = false) str1 str2 =
let dist = Array.make_matrix (String.length str1 + 1) (String.length str2 + 1) 0 in

for i = 1 to String.length str1 do
dist.(i).(0) <- i
done;
for j = 1 to String.length str2 do
dist.(0).(j) <- j
done;

for i = 1 to String.length str1 do
for j = 1 to String.length str2 do
let subst_cost = if str1.[i - 1] = str2.[j - 1] then 0 else 1 in
dist.(i).(j) <- min (min (dist.(i - 1).(j) + 1) (dist.(i).(j - 1) + 1)) (dist.(i - 1).(j - 1) + subst_cost);
if osa && i > 1 && j > 1 && str1.[i - 1] = str2.[j - 2] && str1.[i - 2] = str2.[j - 1] then
dist.(i).(j) <- min dist.(i).(j) (dist.(i - 2).(j - 2) + 1)
done
done;

dist.(String.length str1).(String.length str2)

let termcode n = if !opt_colors then "\x1B[" ^ string_of_int n ^ "m" else ""

let bold str = termcode 1 ^ str
Expand Down
6 changes: 6 additions & 0 deletions src/lib/util.mli
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,12 @@ val fold_left_index_last : (int -> bool -> 'a -> 'b -> 'a) -> 'a -> 'b list -> '

val list_init : int -> (int -> 'a) -> 'a list

(** Compute the levenshtein distance between two strings using the
Wagner–Fischer algorithm. If [~osa] is true computes the optimal
string alignment distance, which is similar but allows swaps as a
single action. *)
val levenshtein_distance : ?osa:bool -> string -> string -> int

(** {2 Files} *)

(** [copy_file src dst] copies file [src] to file [dst]. Only files are supported,
Expand Down
7 changes: 7 additions & 0 deletions test/typecheck/fail/no_function.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Type error:
fail/no_function.sail:10.10-17:
10 | let _ = foo_baz()
 | ^-----^
 | No function type found for foo_baz
 |
 | Did you mean foo_bar?
11 changes: 11 additions & 0 deletions test/typecheck/fail/no_function.sail
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
default Order dec

$include <prelude.sail>

val foo_quux : unit -> unit

val foo_bar : unit -> unit

function test() -> unit = {
let _ = foo_baz()
}
7 changes: 7 additions & 0 deletions test/typecheck/fail/no_function2.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Type error:
fail/no_function2.sail:8.10-17:
8 | let _ = foo_baz()
 | ^-----^
 | No function type found for foo_baz
 |
 | Did you mean foo_quux?
9 changes: 9 additions & 0 deletions test/typecheck/fail/no_function2.sail
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
default Order dec

$include <prelude.sail>

val foo_quux : unit -> unit

function test() -> unit = {
let _ = foo_baz()
}
5 changes: 5 additions & 0 deletions test/typecheck/fail/no_function3.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Type error:
fail/no_function3.sail:6.10-17:
6 | let _ = foo_baz()
 | ^-----^
 | No function type found for foo_baz
7 changes: 7 additions & 0 deletions test/typecheck/fail/no_function3.sail
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
default Order dec

$include <prelude.sail>

function test() -> unit = {
let _ = foo_baz()
}

0 comments on commit 6764ad3

Please sign in to comment.