checker: stricter `unknown type` checks, show better suggestions (#8816)

pull/8833/head
Swastik Baranwal 2021-02-19 14:53:13 +05:30 committed by GitHub
parent 6a752512b2
commit ad162cd6fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 120 additions and 142 deletions

View File

@ -67,7 +67,7 @@ pub struct C.FONStextIter {
codepoint u32 codepoint u32
isize i16 isize i16
iblur i16 iblur i16
font &FONSfont font &C.FONSfont
prevGlyphIndex int prevGlyphIndex int
str byteptr str byteptr
next byteptr next byteptr

View File

@ -54,7 +54,7 @@ fn C.sqlite3_column_text(&C.sqlite3_stmt, int) byteptr
fn C.sqlite3_column_int(&C.sqlite3_stmt, int) int fn C.sqlite3_column_int(&C.sqlite3_stmt, int) int
fn C.sqlite3_column_int64(&C.sqlite3_stmt, int) int64 fn C.sqlite3_column_int64(&C.sqlite3_stmt, int) i64
fn C.sqlite3_column_double(&C.sqlite3_stmt, int) f64 fn C.sqlite3_column_double(&C.sqlite3_stmt, int) f64

View File

@ -50,7 +50,7 @@ struct C.timespec {
tv_nsec i64 tv_nsec i64
} }
fn C._mkgmtime(&C.tm) time_t fn C._mkgmtime(&C.tm) C.time_t
fn C.QueryPerformanceCounter(&u64) C.BOOL fn C.QueryPerformanceCounter(&u64) C.BOOL

View File

@ -338,49 +338,21 @@ pub fn (mut c Checker) interface_decl(decl ast.InterfaceDecl) {
c.check_valid_pascal_case(decl.name, 'interface name', decl.pos) c.check_valid_pascal_case(decl.name, 'interface name', decl.pos)
for method in decl.methods { for method in decl.methods {
c.check_valid_snake_case(method.name, 'method name', method.pos) c.check_valid_snake_case(method.name, 'method name', method.pos)
if method.return_type != table.Type(0) {
c.ensure_type_exists(method.return_type, method.pos) or { return }
}
for param in method.params {
c.ensure_type_exists(param.typ, param.pos) or { return }
}
} }
// TODO: copy pasta from StructDecl
for i, field in decl.fields { for i, field in decl.fields {
c.check_valid_snake_case(field.name, 'field name', field.pos) c.check_valid_snake_case(field.name, 'field name', field.pos)
sym := c.table.get_type_symbol(field.typ) c.ensure_type_exists(field.typ, field.pos) or { return }
for j in 0 .. i { for j in 0 .. i {
if field.name == decl.fields[j].name { if field.name == decl.fields[j].name {
c.error('field name `$field.name` duplicate', field.pos) c.error('field name `$field.name` duplicate', field.pos)
} }
} }
if sym.kind == .placeholder && !sym.name.starts_with('C.') {
c.error(util.new_suggestion(sym.name, c.table.known_type_names()).say('unknown type `$sym.name`'),
field.type_pos)
}
// Separate error condition for `int_literal` and `float_literal` because `util.suggestion` may give different
// suggestions due to f32 comparision issue.
if sym.kind in [.int_literal, .float_literal] {
msg := if sym.kind == .int_literal {
'unknown type `$sym.name`.\nDid you mean `int`?'
} else {
'unknown type `$sym.name`.\nDid you mean `f64`?'
}
c.error(msg, field.type_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(util.new_suggestion(elem_sym.name, c.table.known_type_names()).say('unknown type `$elem_sym.name`'),
field.type_pos)
}
}
if sym.kind == .map {
info := sym.map_info()
key_sym := c.table.get_type_symbol(info.key_type)
value_sym := c.table.get_type_symbol(info.value_type)
if key_sym.kind == .placeholder {
c.error('unknown type `$key_sym.name`', field.type_pos)
}
if value_sym.kind == .placeholder {
c.error('unknown type `$value_sym.name`', field.type_pos)
}
}
} }
} }
@ -407,6 +379,7 @@ pub fn (mut c Checker) struct_decl(mut decl ast.StructDecl) {
} }
} }
for i, field in decl.fields { for i, field in decl.fields {
c.ensure_type_exists(field.typ, field.type_pos) or { return }
if decl.language == .v { if decl.language == .v {
c.check_valid_snake_case(field.name, 'field name', field.pos) c.check_valid_snake_case(field.name, 'field name', field.pos)
} }
@ -416,45 +389,12 @@ pub fn (mut c Checker) struct_decl(mut decl ast.StructDecl) {
c.error('field name `$field.name` duplicate', field.pos) c.error('field name `$field.name` duplicate', field.pos)
} }
} }
if sym.kind == .placeholder && decl.language != .c && !sym.name.starts_with('C.') {
c.error(util.new_suggestion(sym.name, c.table.known_type_names()).say('unknown type `$sym.name`'),
field.type_pos)
}
// Separate error condition for `int_literal` and `float_literal` because `util.suggestion` may give different
// suggestions due to f32 comparision issue.
if sym.kind in [.int_literal, .float_literal] {
msg := if sym.kind == .int_literal {
'unknown type `$sym.name`.\nDid you mean `int`?'
} else {
'unknown type `$sym.name`.\nDid you mean `f64`?'
}
c.error(msg, field.type_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(util.new_suggestion(elem_sym.name, c.table.known_type_names()).say('unknown type `$elem_sym.name`'),
field.type_pos)
}
}
if sym.kind == .struct_ { if sym.kind == .struct_ {
info := sym.info as table.Struct info := sym.info as table.Struct
if info.is_heap && !field.typ.is_ptr() { if info.is_heap && !field.typ.is_ptr() {
struct_sym.info.is_heap = true struct_sym.info.is_heap = true
} }
} }
if sym.kind == .map {
info := sym.map_info()
key_sym := c.table.get_type_symbol(info.key_type)
value_sym := c.table.get_type_symbol(info.value_type)
if key_sym.kind == .placeholder {
c.error('unknown type `$key_sym.name`', field.type_pos)
}
if value_sym.kind == .placeholder {
c.error('unknown type `$value_sym.name`', field.type_pos)
}
}
if field.has_default_expr { if field.has_default_expr {
c.expected_type = field.typ c.expected_type = field.typ
field_expr_type := c.expr(field.default_expr) field_expr_type := c.expr(field.default_expr)
@ -509,10 +449,8 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type {
struct_init.typ = c.expected_type struct_init.typ = c.expected_type
} }
} }
if struct_init.typ == 0 {
c.error('unknown type', struct_init.pos)
}
utyp := c.unwrap_generic(struct_init.typ) utyp := c.unwrap_generic(struct_init.typ)
c.ensure_type_exists(utyp, struct_init.pos) or { }
type_sym := c.table.get_type_symbol(utyp) type_sym := c.table.get_type_symbol(utyp)
if type_sym.kind == .sum_type && struct_init.fields.len == 1 { if type_sym.kind == .sum_type && struct_init.fields.len == 1 {
sexpr := struct_init.fields[0].expr.str() sexpr := struct_init.fields[0].expr.str()
@ -1107,14 +1045,8 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) {
} }
ast.SelectorExpr { ast.SelectorExpr {
// retrieve table.Field // retrieve table.Field
if expr.expr_type == 0 { c.ensure_type_exists(expr.expr_type, expr.pos) or { return '', pos }
c.error('0 type in SelectorExpr', expr.pos) mut typ_sym := c.table.get_final_type_symbol(c.unwrap_generic(expr.expr_type))
return '', pos
}
mut typ_sym := c.table.get_type_symbol(c.unwrap_generic(expr.expr_type))
if mut typ_sym.info is table.Alias {
typ_sym = c.table.get_type_symbol(typ_sym.info.parent_type)
}
match typ_sym.kind { match typ_sym.kind {
.struct_ { .struct_ {
struct_info := typ_sym.info as table.Struct struct_info := typ_sym.info as table.Struct
@ -1874,9 +1806,7 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
gts := c.table.get_type_symbol(call_expr.generic_types[0]) gts := c.table.get_type_symbol(call_expr.generic_types[0])
nrt := '$rts.name<$gts.name>' nrt := '$rts.name<$gts.name>'
idx := c.table.type_idxs[nrt] idx := c.table.type_idxs[nrt]
if idx == 0 { c.ensure_type_exists(idx, call_expr.pos) or { }
c.error('unknown type: $nrt', call_expr.pos)
}
call_expr.return_type = table.new_type(idx).derive(f.return_type) call_expr.return_type = table.new_type(idx).derive(f.return_type)
} }
} }
@ -4278,10 +4208,8 @@ pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) table.Type {
cond_type := c.expr(node.cond) cond_type := c.expr(node.cond)
// we setting this here rather than at the end of the method // we setting this here rather than at the end of the method
// since it is used in c.match_exprs() it saves checking twice // since it is used in c.match_exprs() it saves checking twice
node.cond_type = cond_type node.cond_type = c.table.mktyp(cond_type)
if cond_type == 0 { c.ensure_type_exists(node.cond_type, node.pos) or { return table.void_type }
c.error('compiler bug: match 0 cond type', node.pos)
}
cond_type_sym := c.table.get_type_symbol(cond_type) cond_type_sym := c.table.get_type_symbol(cond_type)
if cond_type_sym.kind !in [.interface_, .sum_type] { if cond_type_sym.kind !in [.interface_, .sum_type] {
node.is_sum_type = false node.is_sum_type = false
@ -5661,17 +5589,10 @@ fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) table.Type {
defer { defer {
c.inside_sql = false c.inside_sql = false
} }
sym := c.table.get_type_symbol(node.table_expr.typ) c.ensure_type_exists(node.table_expr.typ, node.pos) or { return table.void_type }
if node.table_expr.typ == 0 {
c.error('orm: unknown type `$sym.name`', node.pos)
}
if sym.kind == .placeholder {
c.error('orm: unknown type `$sym.name`', node.pos)
return table.void_type
}
c.cur_orm_ts = sym
info := sym.info as table.Struct
table_sym := c.table.get_type_symbol(node.table_expr.typ) table_sym := c.table.get_type_symbol(node.table_expr.typ)
c.cur_orm_ts = table_sym
info := table_sym.info as table.Struct
fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, table_sym.name) fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, table_sym.name)
mut sub_structs := map[int]ast.SqlStmt{} mut sub_structs := map[int]ast.SqlStmt{}
for f in fields.filter(c.table.types[int(it.typ)].kind == .struct_) { for f in fields.filter(c.table.types[int(it.typ)].kind == .struct_) {
@ -5788,19 +5709,11 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
if node.language == .v { if node.language == .v {
// Make sure all types are valid // Make sure all types are valid
for arg in node.params { for arg in node.params {
sym := c.table.get_type_symbol(arg.typ) c.ensure_type_exists(arg.typ, node.pos) or { return }
if sym.kind == .placeholder
|| (sym.kind in [table.Kind.int_literal, .float_literal] && !c.is_builtin_mod) {
c.error('unknown type `$sym.name`', node.pos)
}
} }
} }
if node.return_type != table.Type(0) { if node.return_type != table.Type(0) {
return_sym := c.table.get_type_symbol(node.return_type) c.ensure_type_exists(node.return_type, node.pos) or { return }
if node.language == .v && return_sym.kind in [.placeholder, .int_literal, .float_literal]
&& return_sym.language == .v {
c.error('unknown type `$return_sym.name`', node.pos)
}
if node.language == .v && node.is_method && node.name == 'str' { if node.language == .v && node.is_method && node.name == 'str' {
if node.return_type != table.string_type { if node.return_type != table.string_type {
c.error('.str() methods should return `string`', node.pos) c.error('.str() methods should return `string`', node.pos)
@ -5949,3 +5862,41 @@ fn (mut c Checker) trace(fbase string, message string) {
println('> c.trace | ${fbase:-10s} | $message') println('> c.trace | ${fbase:-10s} | $message')
} }
} }
fn (mut c Checker) ensure_type_exists(typ table.Type, pos token.Position) ? {
if typ == 0 {
c.error('unknown type', pos)
}
sym := c.table.get_type_symbol(typ)
match sym.kind {
.placeholder {
if sym.language == .v && !sym.name.starts_with('C.') {
c.error(util.new_suggestion(sym.name, c.table.known_type_names()).say('unknown type `$sym.name`'),
pos)
return none
}
}
.int_literal, .float_literal {
// Separate error condition for `int_literal` and `float_literal` because `util.suggestion` may give different
// suggestions due to f32 comparision issue.
if !c.is_builtin_mod {
msg := if sym.kind == .int_literal {
'unknown type `$sym.name`.\nDid you mean `int`?'
} else {
'unknown type `$sym.name`.\nDid you mean `f64`?'
}
c.error(msg, pos)
return none
}
}
.array {
c.ensure_type_exists((sym.info as table.Array).elem_type, pos) ?
}
.map {
info := sym.info as table.Map
c.ensure_type_exists(info.key_type, pos) ?
c.ensure_type_exists(info.value_type, pos) ?
}
else {}
}
}

View File

@ -9,16 +9,14 @@ vlib/v/checker/tests/any_int_float_ban_err.vv:2:1: error: type `int_literal` doe
| ~~~~~~~~ | ~~~~~~~~
3 | 3 |
4 | struct Int { 4 | struct Int {
vlib/v/checker/tests/any_int_float_ban_err.vv:5:7: error: unknown type `int_literal`. vlib/v/checker/tests/any_int_float_ban_err.vv:5:7: error: unknown type `int_literal`
Did you mean `float_literal`?
3 | 3 |
4 | struct Int { 4 | struct Int {
5 | i int_literal 5 | i int_literal
| ~~~~~~~~~~~ | ~~~~~~~~~~~
6 | f float_literal 6 | f float_literal
7 | } 7 | }
vlib/v/checker/tests/any_int_float_ban_err.vv:6:7: error: unknown type `float_literal`. vlib/v/checker/tests/any_int_float_ban_err.vv:6:7: error: unknown type `float_literal`
Did you mean `int_literal`?
4 | struct Int { 4 | struct Int {
5 | i int_literal 5 | i int_literal
6 | f float_literal 6 | f float_literal

View File

@ -1,6 +1,5 @@
vlib/v/checker/tests/filter_on_non_arr_err.vv:2:14: error: unknown method: `string.filter`. vlib/v/checker/tests/filter_on_non_arr_err.vv:2:14: error: unknown method: `string.filter`
Did you mean `after`?
1 | fn main() { 1 | fn main() {
2 | _ := 'test'.filter(it == `t`) 2 | _ := 'test'.filter(it == `t`)
| ~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~
3 | } 3 | }

View File

@ -3,7 +3,8 @@ vlib/v/checker/tests/import_symbol_type_err.vv:1:17: error: module `crypto` has
| ~~~~ | ~~~~
2 | fn main() { 2 | fn main() {
3 | println(Coin{}) 3 | println(Coin{})
vlib/v/checker/tests/import_symbol_type_err.vv:3:11: error: unknown struct: crypto.Coin vlib/v/checker/tests/import_symbol_type_err.vv:3:11: error: unknown type `crypto.Coin`.
Did you mean `crypto.Hash`?
1 | import crypto { Coin } 1 | import crypto { Coin }
2 | fn main() { 2 | fn main() {
3 | println(Coin{}) 3 | println(Coin{})

View File

@ -0,0 +1,14 @@
vlib/v/checker/tests/interface_return_parameter_err.vv:2:5: error: unknown type `Baz`.
Did you mean `Foo`?
1 | interface Foo {
2 | bar(string) []Baz
| ~~~~~~~~~~~
3 | bar2(Bax) string
4 | }
vlib/v/checker/tests/interface_return_parameter_err.vv:3:10: error: unknown type `Bax`.
Did you mean `Foo`?
1 | interface Foo {
2 | bar(string) []Baz
3 | bar2(Bax) string
| ~~~
4 | }

View File

@ -0,0 +1,4 @@
interface Foo {
bar(string) []Baz
bar2(Bax) string
}

View File

@ -2,4 +2,4 @@ vlib/v/checker/tests/map_unknown_value.vv:2:23: error: unknown type `DoesNotExis
1 | struct App { 1 | struct App {
2 | my_map map[string]DoesNotExist 2 | my_map map[string]DoesNotExist
| ~~~~~~~~~~~~ | ~~~~~~~~~~~~
3 | } 3 | }

View File

@ -1,6 +1,7 @@
vlib/v/checker/tests/unknown_array_element_type_b.vv:2:6: error: unknown type `abc` vlib/v/checker/tests/unknown_array_element_type_b.vv:2:6: error: unknown type `abc`.
Did you mean `Aaa`?
1 | struct Aaa { 1 | struct Aaa {
2 | a []abc 2 | a []abc
| ~~~ | ~~~
3 | } 3 | }
4 | 4 |

View File

@ -13,4 +13,4 @@ Did you mean `translate`?
27 | z := p.tranzlate(v) 27 | z := p.tranzlate(v)
| ~~~~~~~~~~~~ | ~~~~~~~~~~~~
28 | println('p: $p') 28 | println('p: $p')
29 | println('v: $v') 29 | println('v: $v')

View File

@ -397,7 +397,27 @@ pub fn (mut t Table) register_type_symbol(typ TypeSymbol) int {
} }
pub fn (t &Table) known_type(name string) bool { pub fn (t &Table) known_type(name string) bool {
t.find_type(name) or { return false } return t.find_type_idx(name) != 0
}
pub fn (t &Table) known_type_idx(typ Type) bool {
if typ == 0 {
return false
}
sym := t.get_type_symbol(typ)
match sym.kind {
.placeholder {
return sym.language != .v || sym.name.starts_with('C.')
}
.array {
return t.known_type_idx((sym.info as Array).elem_type)
}
.map {
info := sym.info as Map
return t.known_type_idx(info.key_type) && t.known_type_idx(info.value_type)
}
else {}
}
return true return true
} }
@ -756,14 +776,13 @@ pub fn (mytable &Table) sumtype_has_variant(parent Type, variant Type) bool {
return false return false
} }
pub fn (mytable &Table) known_type_names() []string { pub fn (t &Table) known_type_names() []string {
mut res := []string{} mut res := []string{cap: t.type_idxs.len}
for _, idx in mytable.type_idxs { for _, idx in t.type_idxs {
// Skip `int_literal_type_idx` and `float_literal_type_idx` because they shouldn't be visible to the User. // Skip `int_literal_type_idx` and `float_literal_type_idx` because they shouldn't be visible to the User.
if idx in [0, int_literal_type_idx, float_literal_type_idx] { if idx !in [0, int_literal_type_idx, float_literal_type_idx] && t.known_type_idx(idx) {
continue res << t.type_to_str(idx)
} }
res << mytable.type_to_str(idx)
} }
return res return res
} }

View File

@ -9,17 +9,6 @@ mut:
similarity f32 similarity f32
} }
fn compare_by_similarity(a &Possibility, b &Possibility) int {
if a.similarity < b.similarity {
return -1
}
if a.similarity > b.similarity {
return 1
}
return 0
}
//
struct Suggestion { struct Suggestion {
mut: mut:
known []Possibility known []Possibility
@ -45,10 +34,12 @@ pub fn (mut s Suggestion) add(val string) {
if sval in [s.wanted, s.swanted] { if sval in [s.wanted, s.swanted] {
return return
} }
// round to 3 decimal places to avoid float comparison issues
similarity := f32(int(strings.dice_coefficient(s.swanted, sval) * 1000)) / 1000
s.known << Possibility{ s.known << Possibility{
value: val value: val
svalue: sval svalue: sval
similarity: strings.dice_coefficient(s.swanted, sval) similarity: similarity
} }
} }
@ -59,7 +50,7 @@ pub fn (mut s Suggestion) add_many(many []string) {
} }
pub fn (mut s Suggestion) sort() { pub fn (mut s Suggestion) sort() {
s.known.sort_with_compare(compare_by_similarity) s.known.sort(a.similarity < b.similarity)
} }
pub fn (s Suggestion) say(msg string) string { pub fn (s Suggestion) say(msg string) string {
@ -67,7 +58,7 @@ pub fn (s Suggestion) say(msg string) string {
mut found := false mut found := false
if s.known.len > 0 { if s.known.len > 0 {
top_posibility := s.known.last() top_posibility := s.known.last()
if top_posibility.similarity > 0.10 { if top_posibility.similarity > 0.5 {
val := top_posibility.value val := top_posibility.value
if !val.starts_with('[]') { if !val.starts_with('[]') {
res += '.\nDid you mean `$val`?' res += '.\nDid you mean `$val`?'