diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index a7ea12aab2..08583e07ce 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -662,6 +662,20 @@ pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) } if !found { continue_check = false + if dot_index := fn_name.index('.') { + if !fn_name[0].is_capital() { + mod_name := fn_name#[..dot_index] + mut mod_func_names := []string{} + for ctfnk, ctfnv in c.table.fns { + if ctfnv.is_pub && ctfnk.starts_with(mod_name) { + mod_func_names << ctfnk + } + } + suggestion := util.new_suggestion(fn_name, mod_func_names) + c.error(suggestion.say('unknown function: $fn_name '), node.pos) + return ast.void_type + } + } c.error('unknown function: $fn_name', node.pos) return ast.void_type } diff --git a/vlib/v/checker/tests/misspelled_mod_fn_name_should_have_suggestion.out b/vlib/v/checker/tests/misspelled_mod_fn_name_should_have_suggestion.out new file mode 100644 index 0000000000..8ff5208c5d --- /dev/null +++ b/vlib/v/checker/tests/misspelled_mod_fn_name_should_have_suggestion.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/misspelled_mod_fn_name_should_have_suggestion.vv:3:9: error: unknown function: os.read_fil . +Did you mean `os.read_file`? + 1 | import os + 2 | + 3 | dump(os.read_fil('abc')) + | ~~~~~~~~~~~~~~~ diff --git a/vlib/v/checker/tests/misspelled_mod_fn_name_should_have_suggestion.vv b/vlib/v/checker/tests/misspelled_mod_fn_name_should_have_suggestion.vv new file mode 100644 index 0000000000..e2b0964fbd --- /dev/null +++ b/vlib/v/checker/tests/misspelled_mod_fn_name_should_have_suggestion.vv @@ -0,0 +1,3 @@ +import os + +dump(os.read_fil('abc')) diff --git a/vlib/v/checker/tests/unknown_function.out b/vlib/v/checker/tests/unknown_function.out index b3f1be6747..8acc209fa9 100644 --- a/vlib/v/checker/tests/unknown_function.out +++ b/vlib/v/checker/tests/unknown_function.out @@ -1,4 +1,5 @@ -vlib/v/checker/tests/unknown_function.vv:4:15: error: unknown function: math.max_i64 +vlib/v/checker/tests/unknown_function.vv:4:15: error: unknown function: math.max_i64 . +Did you mean `math.max`? 2 | 3 | fn main() { 4 | println(math.max_i64()) diff --git a/vlib/v/util/suggestions.v b/vlib/v/util/suggestions.v index e1c35ea9c2..568c4d4739 100644 --- a/vlib/v/util/suggestions.v +++ b/vlib/v/util/suggestions.v @@ -1,14 +1,20 @@ module util +import term import strings +// Possibility is a simple pair of a string, with a similarity coefficient +// determined by the editing distance to a wanted value. struct Possibility { value string svalue string mut: - similarity f32 + similarity f32 // Note: 0.0 for *equal* strings. } +// Suggestion is set of known possibilities and a wanted string. +// It has helper methods for making educated guesses based on the possibilities, +// on which of them match best the wanted string. struct Suggestion { mut: known []Possibility @@ -16,6 +22,7 @@ mut: swanted string } +// new_suggestion creates a new Suggestion, given a wanted value and a list of possibilities. pub fn new_suggestion(wanted string, possibilities []string) Suggestion { mut s := Suggestion{ wanted: wanted @@ -26,6 +33,8 @@ pub fn new_suggestion(wanted string, possibilities []string) Suggestion { return s } +// add adds the `val` to the list of known possibilities of the suggestion. +// It calculates the similarity metric towards the wanted value. pub fn (mut s Suggestion) add(val string) { if val in [s.wanted, s.swanted] { return @@ -43,16 +52,21 @@ pub fn (mut s Suggestion) add(val string) { } } +// add adds all of the `many` to the list of known possibilities of the suggestion pub fn (mut s Suggestion) add_many(many []string) { for x in many { s.add(x) } } +// sort sorts the list of known possibilities, based on their similarity metric. +// Equal strings will be first, followed by less similar ones, very distinct ones will be last. pub fn (mut s Suggestion) sort() { s.known.sort(a.similarity < b.similarity) } +// say produces a final suggestion message, based on the preset `wanted` and +// `possibilities` fields, accumulated in the Suggestion. pub fn (s Suggestion) say(msg string) string { mut res := msg mut found := false @@ -61,14 +75,14 @@ pub fn (s Suggestion) say(msg string) string { if top_posibility.similarity > 0.5 { val := top_posibility.value if !val.starts_with('[]') { - res += '.\nDid you mean `$val`?' + res += '.\nDid you mean `${highlight_suggestion(val)}`?' found = true } } } if !found { if s.known.len > 0 { - mut values := s.known.map('`$it.svalue`') + mut values := s.known.map('`${highlight_suggestion(it.svalue)}`') values.sort() if values.len == 1 { res += '.\n1 possibility: ${values[0]}.' @@ -81,6 +95,8 @@ pub fn (s Suggestion) say(msg string) string { return res } +// short_module_name returns a shortened version of the fully qualified `name`, +// i.e. `xyz.def.abc.symname` -> `abc.symname` pub fn short_module_name(name string) string { if !name.contains('.') { return name @@ -93,3 +109,10 @@ pub fn short_module_name(name string) string { symname := vals[vals.len - 1] return '${mname}.$symname' } + +// highlight_suggestion returns a colorfull/highlighted version of `message`, +// but only if the standart error output allows for color messages, otherwise +// the plain message will be returned. +pub fn highlight_suggestion(message string) string { + return term.ecolorize(term.bright_blue, message) +}