From 5f1e634d823500ca720571f9e0627e676ee0170b Mon Sep 17 00:00:00 2001 From: joe-conigliaro Date: Mon, 4 Nov 2019 12:59:28 +1100 Subject: [PATCH] compiler: improve typo detection, support all types and fn definitions --- vlib/compiler/modules.v | 9 ++++ vlib/compiler/parser.v | 63 ++++++++++++--------------- vlib/compiler/table.v | 95 +++++++++++++++++++++++++++-------------- 3 files changed, 100 insertions(+), 67 deletions(-) diff --git a/vlib/compiler/modules.v b/vlib/compiler/modules.v index 07f0d9a757..177c987244 100644 --- a/vlib/compiler/modules.v +++ b/vlib/compiler/modules.v @@ -100,6 +100,15 @@ fn (it &ImportTable) is_used_import(alias string) bool { return alias in it.used_imports } +// should module be accessable +pub fn (p &Parser) is_mod_in_scope(mod string) bool { + mut mods_in_scope := ['', 'builtin', 'main', p.mod] + for _, m in p.import_table.imports { + mods_in_scope << m + } + return mod in mods_in_scope +} + // return resolved dep graph (order deps) pub fn (v &V) resolve_deps() &DepGraph { graph := v.import_graph() diff --git a/vlib/compiler/parser.v b/vlib/compiler/parser.v index fc1ef7d2b7..a8a716c8de 100644 --- a/vlib/compiler/parser.v +++ b/vlib/compiler/parser.v @@ -898,7 +898,11 @@ fn (p mut Parser) get_type() string { // for q in p.table.types { // println(q.name) // } - p.error('unknown type `$typ`') + mut t_suggest, tc_suggest := p.table.find_misspelled_type(typ, p, 0.50) + if t_suggest.len > 0 { + t_suggest = '. did you mean: ($tc_suggest) `$t_suggest`' + } + p.error('unknown type `$typ`$t_suggest') } } else if !t.is_public && t.mod != p.mod && !p.is_vgen && t.name != '' && !p.first_pass() { @@ -1676,10 +1680,17 @@ fn (p mut Parser) name_expr() string { if p.table.known_const(name) { return p.get_const_type(name, ptr) } - + // TODO: V script? Try os module. // Function (not method, methods are handled in `.dot()`) mut f := p.table.find_fn_is_script(name, p.v_script) or { - return p.get_undefined_fn_type(name, orig_name) + // First pass, the function can be defined later. + if p.first_pass() { + p.next() + return 'void' + } + // exhaused all options type,enum,const,mod,var,fn etc + // so show undefined error (also checks typos) + p.undefined_error(name, orig_name) return '' // panics } // no () after func, so func is an argument, just gen its name // TODO verify this and handle errors @@ -1838,40 +1849,20 @@ fn (p mut Parser) get_c_func_type(name string) string { return cfn.typ } -fn (p mut Parser) get_undefined_fn_type(name string, orig_name string) string { - if p.first_pass() { - p.next() - // First pass, the function can be defined later. - return 'void' - } else { - // We are in the second pass, that means this function was not defined, throw an error. - - // V script? Try os module. - // TODO - if p.v_script { - //name = name.replace('main__', 'os__') - //f = p.table.find_fn(name) - } - - // check for misspelled function / variable / module - name_dotted := mod_gen_name_rev(name.replace('__', '.')) - suggested := p.identify_typo(name) - if suggested.len != 0 { - p.error('undefined: `$name_dotted`. did you mean:\n$suggested\n') - } - - // If orig_name is a mod, then printing undefined: `mod` tells us nothing - // if p.table.known_mod(orig_name) { - if p.table.known_mod(orig_name) || p.import_table.known_alias(orig_name) { - m_name := mod_gen_name_rev(name.replace('__', '.')) - p.error('undefined function: `$m_name` (in module `$orig_name`)') - } else if orig_name in reserved_type_param_names { - p.error('the letter `$orig_name` is reserved for type parameters') - } else { - p.error('undefined: `$orig_name`') - } - return 'void' +fn (p mut Parser) undefined_error(name string, orig_name string) { + name_dotted := mod_gen_name_rev(name.replace('__', '.')) + // check for misspelled function / variable / module / type + suggested := p.identify_typo(name) + if suggested.len != 0 { + p.error('undefined: `$name_dotted`. did you mean:\n$suggested\n') } + // If orig_name is a mod, then printing undefined: `mod` tells us nothing + if p.table.known_mod(orig_name) || p.import_table.known_alias(orig_name) { + p.error('undefined: `$name_dotted` (in module `$orig_name`)') + } else if orig_name in reserved_type_param_names { + p.error('the letter `$orig_name` is reserved for type parameters') + } + p.error('undefined: `$orig_name`') } fn (p mut Parser) var_expr(v Var) string { diff --git a/vlib/compiler/table.v b/vlib/compiler/table.v index a5762e61dc..cf99d6a923 100644 --- a/vlib/compiler/table.v +++ b/vlib/compiler/table.v @@ -877,7 +877,7 @@ fn (p &Parser) identify_typo(name string) string { mut output := '' // check imported modules mut n := p.table.find_misspelled_imported_mod(name_dotted, p, min_match) - if n != '' { + if n.len > 0 { output += '\n * module: `$n`' } // check consts @@ -885,41 +885,45 @@ fn (p &Parser) identify_typo(name string) string { if n != '' { output += '\n * const: `$n`' } + // check types + typ, type_cat := p.table.find_misspelled_type(name, p, min_match) + if typ.len > 0 { + output += '\n * $type_cat: `$typ`' + } // check functions n = p.table.find_misspelled_fn(name, p, min_match) - if n != '' { + if n.len > 0 { output += '\n * function: `$n`' } // check function local variables n = p.find_misspelled_local_var(name_dotted, min_match) - if n != '' { + if n.len > 0 { output += '\n * variable: `$n`' } return output } +// compare just name part, some items are mod prefied +fn typo_compare_name_mod(a, b, b_mod string) f32 { + if a.len - b.len > 2 || b.len - a.len > 2 { return 0 } + auidx := a.index('__') + a_mod := if auidx != -1 { mod_gen_name_rev(a[..auidx]) } else { '' } + a_name := if auidx != -1 { a[auidx..] } else { a } + b_name := if b.contains('__') { b.all_after('__') } else { b } + if a_mod.len > 0 && b_mod.len > 0 && a_mod != b_mod { return 0 } + return strings.dice_coefficient(a_name, b_name) +} + // find function with closest name to `name` fn (table &Table) find_misspelled_fn(name string, p &Parser, min_match f32) string { mut closest := f32(0) mut closest_fn := '' - n1 := if name.starts_with('main__') { name[6..] } else { name } for _, f in table.fns { - if n1.len - f.name.len > 2 || f.name.len - n1.len > 2 { continue } - if !(f.mod in ['', 'main', 'builtin']) { - mut mod_imported := false - for _, m in p.import_table.imports { - if f.mod == m { - mod_imported = true - break - } - } - if !mod_imported { continue } - } - c := strings.dice_coefficient(n1, f.name) - f_name_orig := mod_gen_name_rev(f.name.replace('__', '.')) + if f.name.contains('__') && !p.is_mod_in_scope(f.mod) { continue } + c := typo_compare_name_mod(name, f.name, f.mod) if c > closest { closest = c - closest_fn = f_name_orig + closest_fn = mod_gen_name_rev(f.name.replace('__', '.')) } } return if closest >= min_match { closest_fn } else { '' } @@ -931,12 +935,10 @@ fn (table &Table) find_misspelled_imported_mod(name string, p &Parser, min_match mut closest_mod := '' n1 := if name.starts_with('main.') { name[5..] } else { name } for alias, mod in p.import_table.imports { - if n1.len - alias.len > 2 || alias.len - n1.len > 2 { continue } - mod_alias := if alias == mod { alias } else { '$alias ($mod)' } - c := strings.dice_coefficient(n1, alias) + c := typo_compare_name_mod(n1, alias, '') if c > closest { closest = c - closest_mod = '$mod_alias' + closest_mod = if alias == mod { alias } else { '$alias ($mod)' } } } return if closest >= min_match { closest_mod } else { '' } @@ -946,20 +948,51 @@ fn (table &Table) find_misspelled_imported_mod(name string, p &Parser, min_match fn (table &Table) find_misspelled_const(name string, p &Parser, min_match f32) string { mut closest := f32(0) mut closest_const := '' - mut mods_in_scope := ['builtin', 'main'] - for _, mod in p.import_table.imports { - mods_in_scope << mod - } for cnst in table.consts { - if cnst.mod != p.mod && !(cnst.mod in mods_in_scope) && cnst.mod.contains('__') { continue } - if name.len - cnst.name.len > 2 || cnst.name.len - name.len > 2 { continue } - const_name_orig := mod_gen_name_rev(cnst.name.replace('__', '.')) - c := strings.dice_coefficient(name, cnst.name.replace('builtin__', 'main__')) + if cnst.name.contains('__') && !p.is_mod_in_scope(cnst.mod) { continue } + c := typo_compare_name_mod(name, cnst.name, cnst.mod) if c > closest { closest = c - closest_const = const_name_orig + closest_const = mod_gen_name_rev(cnst.name.replace('__', '.')) } } return if closest >= min_match { closest_const } else { '' } } +// find type with closest name to `name` +fn (table &Table) find_misspelled_type(name string, p &Parser, min_match f32) (string, string) { + mut closest := f32(0) + mut closest_type := '' + mut type_cat := '' + for _, typ in table.typesmap { + if typ.name.contains('__') && !p.is_mod_in_scope(typ.mod) { continue } + c := typo_compare_name_mod(name, typ.name, typ.mod) + if c > closest { + closest = c + closest_type = mod_gen_name_rev(typ.name.replace('__', '.')) + type_cat = type_cat_str(typ.cat) + } + } + if closest >= min_match { + return closest_type, type_cat + } + return '', '' +} + +fn type_cat_str(tc TypeCategory) string { + tc_str := match tc { + .builtin { 'builtin' } + .struct_ { 'struct' } + .func { 'function' } + .interface_ { 'interface' } + .enum_ { 'enum' } + .union_ { 'union' } + .c_struct { 'C struct' } + .c_typedef { 'C typedef' } + .objc_interface { 'obj C interface' } + .array { 'array' } + .alias { 'type alias' } + else { 'unknown' } + } + return tc_str +}