Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggest most similar function id when we can't find a function type #384

Merged
merged 1 commit into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
}
Loading