compiler: improve typo detection, support all types and fn definitions

pull/2643/head
joe-conigliaro 2019-11-04 12:59:28 +11:00 committed by Alexander Medvednikov
parent a6a233df6b
commit 5f1e634d82
3 changed files with 100 additions and 67 deletions

View File

@ -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()

View File

@ -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 {

View File

@ -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
}