From 6764ad336da759d90f43d3d7164fdcfb84d21424 Mon Sep 17 00:00:00 2001 From: Alasdair Date: Thu, 30 Nov 2023 22:09:42 +0000 Subject: [PATCH] Suggest most similar function id when we can't find a function type 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. --- src/lib/type_env.ml | 19 +++++++++++++++++-- src/lib/util.ml | 21 +++++++++++++++++++++ src/lib/util.mli | 6 ++++++ test/typecheck/fail/no_function.expect | 7 +++++++ test/typecheck/fail/no_function.sail | 11 +++++++++++ test/typecheck/fail/no_function2.expect | 7 +++++++ test/typecheck/fail/no_function2.sail | 9 +++++++++ test/typecheck/fail/no_function3.expect | 5 +++++ test/typecheck/fail/no_function3.sail | 7 +++++++ 9 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 test/typecheck/fail/no_function.expect create mode 100644 test/typecheck/fail/no_function.sail create mode 100644 test/typecheck/fail/no_function2.expect create mode 100644 test/typecheck/fail/no_function2.sail create mode 100644 test/typecheck/fail/no_function3.expect create mode 100644 test/typecheck/fail/no_function3.sail diff --git a/src/lib/type_env.ml b/src/lib/type_env.ml index b9e8bca59..731bdffa4 100644 --- a/src/lib/type_env.ml +++ b/src/lib/type_env.ml @@ -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 @@ -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 diff --git a/src/lib/util.ml b/src/lib/util.ml index 02dad649e..18d1aa707 100644 --- a/src/lib/util.ml +++ b/src/lib/util.ml @@ -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 diff --git a/src/lib/util.mli b/src/lib/util.mli index d7a2314db..4e3df46a7 100644 --- a/src/lib/util.mli +++ b/src/lib/util.mli @@ -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, diff --git a/test/typecheck/fail/no_function.expect b/test/typecheck/fail/no_function.expect new file mode 100644 index 000000000..c5ce20804 --- /dev/null +++ b/test/typecheck/fail/no_function.expect @@ -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? diff --git a/test/typecheck/fail/no_function.sail b/test/typecheck/fail/no_function.sail new file mode 100644 index 000000000..be72f8b77 --- /dev/null +++ b/test/typecheck/fail/no_function.sail @@ -0,0 +1,11 @@ +default Order dec + +$include + +val foo_quux : unit -> unit + +val foo_bar : unit -> unit + +function test() -> unit = { + let _ = foo_baz() +} diff --git a/test/typecheck/fail/no_function2.expect b/test/typecheck/fail/no_function2.expect new file mode 100644 index 000000000..73769c16f --- /dev/null +++ b/test/typecheck/fail/no_function2.expect @@ -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? diff --git a/test/typecheck/fail/no_function2.sail b/test/typecheck/fail/no_function2.sail new file mode 100644 index 000000000..c4fed8a93 --- /dev/null +++ b/test/typecheck/fail/no_function2.sail @@ -0,0 +1,9 @@ +default Order dec + +$include + +val foo_quux : unit -> unit + +function test() -> unit = { + let _ = foo_baz() +} diff --git a/test/typecheck/fail/no_function3.expect b/test/typecheck/fail/no_function3.expect new file mode 100644 index 000000000..99218b2bf --- /dev/null +++ b/test/typecheck/fail/no_function3.expect @@ -0,0 +1,5 @@ +Type error: +fail/no_function3.sail:6.10-17: +6 | let _ = foo_baz() +  | ^-----^ +  | No function type found for foo_baz diff --git a/test/typecheck/fail/no_function3.sail b/test/typecheck/fail/no_function3.sail new file mode 100644 index 000000000..b00cd5ee0 --- /dev/null +++ b/test/typecheck/fail/no_function3.sail @@ -0,0 +1,7 @@ +default Order dec + +$include + +function test() -> unit = { + let _ = foo_baz() +}