From f300f787f3535eb5ee96b0eabd1d90643657f0c0 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Thu, 30 Jul 2020 17:34:05 +0300 Subject: [PATCH] checker: add suggestions for method mispellings and unknown types --- vlib/v/checker/checker.v | 10 ++- .../tests/unknown_method_suggest_name.out | 14 +++ .../tests/unknown_method_suggest_name.vv | 31 +++++++ vlib/v/table/table.v | 11 +++ vlib/v/util/suggestions.v | 90 +++++++++++++++++++ 5 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 vlib/v/checker/tests/unknown_method_suggest_name.out create mode 100644 vlib/v/checker/tests/unknown_method_suggest_name.vv create mode 100644 vlib/v/util/suggestions.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 637eb2b3a2..f07622c504 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -315,13 +315,15 @@ pub fn (mut c Checker) struct_decl(decl ast.StructDecl) { } sym := c.table.get_type_symbol(field.typ) if sym.kind == .placeholder && decl.language != .c && !sym.name.starts_with('C.') { - c.error('unknown type `$sym.name`', field.pos) + c.error(util.new_suggestion(sym.name, c.table.known_type_names()).say('unknown type `$sym.name`'), + field.pos) } if sym.kind == .array { array_info := sym.array_info() elem_sym := c.table.get_type_symbol(array_info.elem_type) if elem_sym.kind == .placeholder { - c.error('unknown type `$elem_sym.name`', field.pos) + c.error(util.new_suggestion(elem_sym.name, c.table.known_type_names()).say('unknown type `$elem_sym.name`'), + field.pos) } } if sym.kind == .struct_ { @@ -1024,7 +1026,9 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type { } } if left_type != table.void_type { - c.error('unknown method: `${left_type_sym.name}.$method_name`', call_expr.pos) + suggestion := util.new_suggestion(method_name, left_type_sym.methods.map(it.name)) + c.error(suggestion.say('unknown method: `${left_type_sym.name}.$method_name`'), + call_expr.pos) } return table.void_type } diff --git a/vlib/v/checker/tests/unknown_method_suggest_name.out b/vlib/v/checker/tests/unknown_method_suggest_name.out new file mode 100644 index 0000000000..fd541c808f --- /dev/null +++ b/vlib/v/checker/tests/unknown_method_suggest_name.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/unknown_method_suggest_name.v:7:2: error: unknown type `hash.crc32.Crc33`. Did you mean `crc32.Crc33` ? + 5 | y int + 6 | z int + 7 | ccc crc32.Crc33 + | ~~~~~~~~~~~~~~~ + 8 | } + 9 | +vlib/v/checker/tests/unknown_method_suggest_name.v:27:9: error: unknown method: `Point.tranzlate`. Did you mean `translate` ? + 25 | p := Point{1, 2, 3} + 26 | v := Vector{5, 5, 10} + 27 | z := p.tranzlate(v) + | ~~~~~~~~~~~~ + 28 | println('p: $p') + 29 | println('v: $v') diff --git a/vlib/v/checker/tests/unknown_method_suggest_name.vv b/vlib/v/checker/tests/unknown_method_suggest_name.vv new file mode 100644 index 0000000000..44d1f849d2 --- /dev/null +++ b/vlib/v/checker/tests/unknown_method_suggest_name.vv @@ -0,0 +1,31 @@ +import hash.crc32 + +struct Point { + x int + y int + z int + ccc crc32.Crc33 +} + +struct Vector { + x int + y int + z int +} + +fn (p Point) translate(v Vector) Point { + return Point{p.x + v.x, p.y + v.y, p.z + v.z} +} + +fn (p Point) identity() Point { + return Point{1, 1, 1} +} + +fn main() { + p := Point{1, 2, 3} + v := Vector{5, 5, 10} + z := p.tranzlate(v) + println('p: $p') + println('v: $v') + println('z: $z') +} diff --git a/vlib/v/table/table.v b/vlib/v/table/table.v index a85f9aa656..f93c001ae0 100644 --- a/vlib/v/table/table.v +++ b/vlib/v/table/table.v @@ -529,3 +529,14 @@ pub fn (table &Table) sumtype_has_variant(parent, variant Type) bool { return false } + +pub fn (table &Table) known_type_names() []string { + mut res := []string{} + for _, idx in table.type_idxs { + if idx == 0 { + continue + } + res << table.type_to_str(idx) + } + return res +} diff --git a/vlib/v/util/suggestions.v b/vlib/v/util/suggestions.v new file mode 100644 index 0000000000..a0849e362b --- /dev/null +++ b/vlib/v/util/suggestions.v @@ -0,0 +1,90 @@ +module util + +import strings + +struct Possibility { + value string + svalue string +mut: + similarity f32 +} + +fn compare_by_similarity(a, b &Possibility) int { + if a.similarity < b.similarity { + return -1 + } + if a.similarity > b.similarity { + return 1 + } + return 0 +} + +// +struct Suggestion { +mut: + known []Possibility + wanted string + swanted string +} + +pub fn new_suggestion(wanted string, possibilities []string) Suggestion { + mut s := Suggestion{ + wanted: wanted + swanted: short_module_name(wanted) + } + s.add_many(possibilities) + s.sort() + return s +} + +pub fn (mut s Suggestion) add(val string) { + if val == s.wanted { + return + } + sval := short_module_name(val) + if sval == s.wanted { + return + } + s.known << Possibility{ + value: val + svalue: sval + similarity: strings.dice_coefficient(s.swanted, sval) + } +} + +pub fn (mut s Suggestion) add_many(many []string) { + for x in many { + s.add(x) + } +} + +pub fn (mut s Suggestion) sort() { + s.known.sort_with_compare(compare_by_similarity) +} + +pub fn (s Suggestion) say(msg string) string { + mut res := msg + if s.known.len > 0 { + top_posibility := s.known.last() + if top_posibility.similarity > 0.10 { + val := top_posibility.value + if !val.starts_with('[]') { + res += '. Did you mean `$val` ?' + } + } + } + return res +} + +pub fn short_module_name(name string) string { + if !name.contains('.') { + return name + } + vals := name.split('.') + if vals.len < 2 { + return name + } + mname := vals[vals.len - 2] + symname := vals[vals.len - 1] + return '${mname}.$symname' +}