From 52b627feb34ac77a74bd1f45162780ba6528be79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=C3=A4schle?= Date: Thu, 26 Nov 2020 11:28:54 +0100 Subject: [PATCH] checker: check filter, map and sort left type (#6952) --- vlib/builtin/string.v | 28 ----------- vlib/builtin/string_test.v | 31 ------------ vlib/v/checker/checker.v | 47 +++++++++++++------ .../tests/array_filter_anon_fn_err_a.out | 6 +-- .../tests/array_filter_anon_fn_err_b.out | 6 +-- .../v/checker/tests/array_filter_fn_err_a.out | 6 +-- .../v/checker/tests/array_filter_fn_err_b.out | 6 +-- .../checker/tests/array_map_anon_fn_err_a.out | 6 +-- .../checker/tests/array_map_anon_fn_err_b.out | 6 +-- vlib/v/checker/tests/array_map_fn_err_a.out | 6 +-- vlib/v/checker/tests/array_map_fn_err_b.out | 6 +-- .../v/checker/tests/filter_on_non_arr_err.out | 6 +++ vlib/v/checker/tests/filter_on_non_arr_err.vv | 3 ++ vlib/v/parser/parser.v | 10 +--- 14 files changed, 67 insertions(+), 106 deletions(-) create mode 100644 vlib/v/checker/tests/filter_on_non_arr_err.out create mode 100644 vlib/v/checker/tests/filter_on_non_arr_err.vv diff --git a/vlib/builtin/string.v b/vlib/builtin/string.v index fb87869741..771ba4fb75 100644 --- a/vlib/builtin/string.v +++ b/vlib/builtin/string.v @@ -1490,34 +1490,6 @@ pub fn (s string) fields() []string { return s.replace('\t', ' ').split(' ') } -pub fn (s string) map(func fn(byte) byte) string { - unsafe { - mut res := malloc(s.len + 1) - for i in 0..s.len { - res[i] = func(s[i]) - } - return tos(res, s.len) - } -} - -pub fn (s string) filter(func fn(b byte) bool) string { - mut new_len := 0 - mut buf := malloc(s.len + 1) - for i in 0 .. s.len { - mut b := s[i] - if func(b) { - unsafe { - buf[new_len] = b - } - new_len++ - } - } - unsafe { - buf[new_len] = 0 - return buf.vstring_with_len(new_len) - } -} - // Allows multi-line strings to be formatted in a way that removes white-space // before a delimeter. by default `|` is used. // Note: the delimiter has to be a byte at this time. That means surrounding diff --git a/vlib/builtin/string_test.v b/vlib/builtin/string_test.v index d5f3497911..324f7384eb 100644 --- a/vlib/builtin/string_test.v +++ b/vlib/builtin/string_test.v @@ -816,41 +816,10 @@ fn test_double_quote_inter() { assert '${a} ${b}' == "1 2" } -fn test_string_map() { - $if windows { - // TODO - return - } - original := 'Hello' - println('original.len = $original.len') - a := original.map(fn (b byte) byte { - return b + 1 - }) - expected := 'Ifmmp' - println('a[0] = ' + a[0].str()) - println('a[1] = ' + a[1].str()) - println('a[2] = ' + a[2].str()) - println('a[3] = ' + a[3].str()) - println('a[4] = ' + a[4].str()) - println('a.len = $a.len') - assert a.len == expected.len - assert a == expected - - assert 'foo'.map(foo) == r'\ee' -} - fn foo(b byte) byte { return b - 10 } -fn test_string_filter() { - foo := 'V is awesome!!!!'.filter(fn (b byte) bool { - return b != `!` - }) - assert foo == 'V is awesome' - assert 'Alexander'.filter(filter) == 'Alexnder' -} - fn filter(b byte) bool { return b != `a` } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index f23fffd59b..648f6a53d3 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1050,15 +1050,15 @@ fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ table.Type, call_e match arg_expr { ast.AnonFn { if arg_expr.decl.params.len > 1 { - c.error('function needs exactly 1 argument', call_expr.pos) + c.error('function needs exactly 1 argument', arg_expr.decl.pos) } else if is_map && (arg_expr.decl.return_type != elem_typ || arg_expr.decl.params[0].typ != elem_typ) { c.error('type mismatch, should use `fn(a $elem_sym.source_name) $elem_sym.source_name {...}`', - call_expr.pos) + arg_expr.decl.pos) } else if !is_map && (arg_expr.decl.return_type != table.bool_type || arg_expr.decl.params[0].typ != elem_typ) { c.error('type mismatch, should use `fn(a $elem_sym.source_name) bool {...}`', - call_expr.pos) + arg_expr.decl.pos) } } ast.Ident { @@ -1071,11 +1071,11 @@ fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ table.Type, call_e c.error('function needs exactly 1 argument', call_expr.pos) } else if is_map && (func.return_type != elem_typ || func.params[0].typ != elem_typ) { c.error('type mismatch, should use `fn(a $elem_sym.source_name) $elem_sym.source_name {...}`', - call_expr.pos) + arg_expr.pos) } else if !is_map && (func.return_type != table.bool_type || func.params[0].typ != elem_typ) { c.error('type mismatch, should use `fn(a $elem_sym.source_name) bool {...}`', - call_expr.pos) + arg_expr.pos) } } } @@ -1110,13 +1110,15 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type { is_sort := method_name == 'sort' if is_filter_map || is_sort { array_info := left_type_sym.info as table.Array - mut scope := c.file.scope.innermost(call_expr.pos.pos) + args_pos := call_expr.pos.pos + call_expr.name.len + mut scope := c.file.scope.innermost(args_pos) if is_filter_map { - scope.update_var_type('it', array_info.elem_type) + // position of `it` doesn't matter + scope_register_it(mut scope, call_expr.pos, array_info.elem_type) } else if is_sort { c.fail_if_immutable(call_expr.left) - scope.update_var_type('a', array_info.elem_type) - scope.update_var_type('b', array_info.elem_type) + // position of `a` and `b` doesn't matter, they're the same + scope_register_ab(mut scope, call_expr.pos, array_info.elem_type) // Verify `.sort(a < b)` if call_expr.args.len > 0 { if call_expr.args[0].expr !is ast.InfixExpr { @@ -2251,11 +2253,28 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { } } -fn (mut c Checker) open_scope(mut parent ast.Scope, start_pos int) &ast.Scope { - mut s := ast.new_scope(parent, start_pos) - s.end_pos = parent.end_pos - parent.children << s - return s +fn scope_register_it(mut s ast.Scope, pos token.Position, typ table.Type) { + s.register('it', ast.Var{ + name: 'it' + pos: pos + typ: typ + is_used: true + }) +} + +fn scope_register_ab(mut s ast.Scope, pos token.Position, typ table.Type) { + s.register('a', ast.Var{ + name: 'a' + pos: pos + typ: typ + is_used: true + }) + s.register('b', ast.Var{ + name: 'b' + pos: pos + typ: typ + is_used: true + }) } fn (mut c Checker) check_array_init_para_type(para string, expr ast.Expr, pos token.Position) { diff --git a/vlib/v/checker/tests/array_filter_anon_fn_err_a.out b/vlib/v/checker/tests/array_filter_anon_fn_err_a.out index d2d90953f4..a8c77d9d0c 100644 --- a/vlib/v/checker/tests/array_filter_anon_fn_err_a.out +++ b/vlib/v/checker/tests/array_filter_anon_fn_err_a.out @@ -1,6 +1,6 @@ -vlib/v/checker/tests/array_filter_anon_fn_err_a.vv:2:23: error: function needs exactly 1 argument +vlib/v/checker/tests/array_filter_anon_fn_err_a.vv:2:24: error: function needs exactly 1 argument 1 | fn main() { 2 | a := [1,2,3,4].filter(fn(a int, b int) bool { return a > 0 }) - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | ~~ 3 | println(a) - 4 | } + 4 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/array_filter_anon_fn_err_b.out b/vlib/v/checker/tests/array_filter_anon_fn_err_b.out index fd574ab084..b08034a71d 100644 --- a/vlib/v/checker/tests/array_filter_anon_fn_err_b.out +++ b/vlib/v/checker/tests/array_filter_anon_fn_err_b.out @@ -1,6 +1,6 @@ -vlib/v/checker/tests/array_filter_anon_fn_err_b.vv:2:23: error: type mismatch, should use `fn(a int) bool {...}` +vlib/v/checker/tests/array_filter_anon_fn_err_b.vv:2:24: error: type mismatch, should use `fn(a int) bool {...}` 1 | fn main() { 2 | a := [1,2,3,4].filter(fn(a string) bool { return a.len > 0 }) - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | ~~ 3 | println(a) - 4 | } + 4 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/array_filter_fn_err_a.out b/vlib/v/checker/tests/array_filter_fn_err_a.out index 1e82166d3a..257203f80b 100644 --- a/vlib/v/checker/tests/array_filter_fn_err_a.out +++ b/vlib/v/checker/tests/array_filter_fn_err_a.out @@ -1,7 +1,7 @@ -vlib/v/checker/tests/array_filter_fn_err_a.vv:5:23: error: function needs exactly 1 argument +vlib/v/checker/tests/array_filter_fn_err_a.vv:5:17: error: function needs exactly 1 argument 3 | } 4 | fn main() { 5 | a := [1,2,3,4].filter(fil) - | ~~~~~ + | ~~~~~~~~~~~ 6 | println(a) - 7 | } + 7 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/array_filter_fn_err_b.out b/vlib/v/checker/tests/array_filter_fn_err_b.out index ecc2480bc1..9e5a8917a9 100644 --- a/vlib/v/checker/tests/array_filter_fn_err_b.out +++ b/vlib/v/checker/tests/array_filter_fn_err_b.out @@ -1,7 +1,7 @@ -vlib/v/checker/tests/array_filter_fn_err_b.vv:5:23: error: type mismatch, should use `fn(a int) bool {...}` +vlib/v/checker/tests/array_filter_fn_err_b.vv:5:24: error: type mismatch, should use `fn(a int) bool {...}` 3 | } 4 | fn main() { 5 | a := [1,2,3,4].filter(fil) - | ~~~~~ + | ~~~ 6 | println(a) - 7 | } + 7 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/array_map_anon_fn_err_a.out b/vlib/v/checker/tests/array_map_anon_fn_err_a.out index 0243eccb46..156a7d711b 100644 --- a/vlib/v/checker/tests/array_map_anon_fn_err_a.out +++ b/vlib/v/checker/tests/array_map_anon_fn_err_a.out @@ -1,6 +1,6 @@ -vlib/v/checker/tests/array_map_anon_fn_err_a.vv:2:20: error: function needs exactly 1 argument +vlib/v/checker/tests/array_map_anon_fn_err_a.vv:2:21: error: function needs exactly 1 argument 1 | fn main() { 2 | a := [1,2,3,4].map(fn(a int, b int) int {return a + b}) - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | ~~ 3 | println(a) - 4 | } + 4 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/array_map_anon_fn_err_b.out b/vlib/v/checker/tests/array_map_anon_fn_err_b.out index 41fd5292fa..409d866d1a 100644 --- a/vlib/v/checker/tests/array_map_anon_fn_err_b.out +++ b/vlib/v/checker/tests/array_map_anon_fn_err_b.out @@ -1,6 +1,6 @@ -vlib/v/checker/tests/array_map_anon_fn_err_b.vv:2:20: error: type mismatch, should use `fn(a int) int {...}` +vlib/v/checker/tests/array_map_anon_fn_err_b.vv:2:21: error: type mismatch, should use `fn(a int) int {...}` 1 | fn main() { 2 | a := [1,2,3,4].map(fn(a string) string { return a }) - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | ~~ 3 | println(a) - 4 | } + 4 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/array_map_fn_err_a.out b/vlib/v/checker/tests/array_map_fn_err_a.out index 808aff808c..1b3269caf1 100644 --- a/vlib/v/checker/tests/array_map_fn_err_a.out +++ b/vlib/v/checker/tests/array_map_fn_err_a.out @@ -1,7 +1,7 @@ -vlib/v/checker/tests/array_map_fn_err_a.vv:5:20: error: function needs exactly 1 argument +vlib/v/checker/tests/array_map_fn_err_a.vv:5:17: error: function needs exactly 1 argument 3 | } 4 | fn main() { 5 | a := [1,2,3,4].map(add) - | ~~~~~ + | ~~~~~~~~ 6 | println(a) - 7 | } + 7 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/array_map_fn_err_b.out b/vlib/v/checker/tests/array_map_fn_err_b.out index 00a8bdbb77..bbdbcbad0c 100644 --- a/vlib/v/checker/tests/array_map_fn_err_b.out +++ b/vlib/v/checker/tests/array_map_fn_err_b.out @@ -1,7 +1,7 @@ -vlib/v/checker/tests/array_map_fn_err_b.vv:5:20: error: type mismatch, should use `fn(a int) int {...}` +vlib/v/checker/tests/array_map_fn_err_b.vv:5:21: error: type mismatch, should use `fn(a int) int {...}` 3 | } 4 | fn main() { 5 | a := [1,2,3,4].map(add) - | ~~~~~ + | ~~~ 6 | println(a) - 7 | } + 7 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/filter_on_non_arr_err.out b/vlib/v/checker/tests/filter_on_non_arr_err.out new file mode 100644 index 0000000000..2f0deabdd4 --- /dev/null +++ b/vlib/v/checker/tests/filter_on_non_arr_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/filter_on_non_arr_err.vv:2:14: error: unknown method: `string.filter`. +Did you mean `after`? + 1 | fn main() { + 2 | _ := 'test'.filter(it == `t`) + | ~~~~~~~~~~~~~~~~~ + 3 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/filter_on_non_arr_err.vv b/vlib/v/checker/tests/filter_on_non_arr_err.vv new file mode 100644 index 0000000000..ee1bb7cca2 --- /dev/null +++ b/vlib/v/checker/tests/filter_on_non_arr_err.vv @@ -0,0 +1,3 @@ +fn main() { + _ := 'test'.filter(it == `t`) +} \ No newline at end of file diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 1b7c051f83..522c1e3b71 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1276,21 +1276,13 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr { if p.tok.kind == .dollar { return p.comptime_method_call(left) } - mut name_pos := p.tok.position() + name_pos := p.tok.position() field_name := p.check_name() is_filter := field_name in ['filter', 'map'] if is_filter { p.open_scope() - name_pos = p.tok.position() - p.scope_register_it() - // wrong tok position when using defer - // defer { - // p.close_scope() - // } } else if field_name == 'sort' { p.open_scope() - name_pos = p.tok.position() - p.scope_register_ab() } // ! in mutable methods if p.tok.kind == .not && p.peek_tok.kind == .lpar {