checker: check filter, map and sort left type (#6952)

pull/6968/head
Daniel Däschle 2020-11-26 11:28:54 +01:00 committed by GitHub
parent e03ae19372
commit 52b627feb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 67 additions and 106 deletions

View File

@ -1490,34 +1490,6 @@ pub fn (s string) fields() []string {
return s.replace('\t', ' ').split(' ') 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 // Allows multi-line strings to be formatted in a way that removes white-space
// before a delimeter. by default `|` is used. // before a delimeter. by default `|` is used.
// Note: the delimiter has to be a byte at this time. That means surrounding // Note: the delimiter has to be a byte at this time. That means surrounding

View File

@ -816,41 +816,10 @@ fn test_double_quote_inter() {
assert '${a} ${b}' == "1 2" 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 { fn foo(b byte) byte {
return b - 10 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 { fn filter(b byte) bool {
return b != `a` return b != `a`
} }

View File

@ -1050,15 +1050,15 @@ fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ table.Type, call_e
match arg_expr { match arg_expr {
ast.AnonFn { ast.AnonFn {
if arg_expr.decl.params.len > 1 { 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 && } else if is_map &&
(arg_expr.decl.return_type != elem_typ || arg_expr.decl.params[0].typ != elem_typ) { (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 {...}`', 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 && } else if !is_map &&
(arg_expr.decl.return_type != table.bool_type || arg_expr.decl.params[0].typ != elem_typ) { (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 {...}`', c.error('type mismatch, should use `fn(a $elem_sym.source_name) bool {...}`',
call_expr.pos) arg_expr.decl.pos)
} }
} }
ast.Ident { 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) 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) { } 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 {...}`', 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 && } else if !is_map &&
(func.return_type != table.bool_type || func.params[0].typ != elem_typ) { (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 {...}`', 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' is_sort := method_name == 'sort'
if is_filter_map || is_sort { if is_filter_map || is_sort {
array_info := left_type_sym.info as table.Array 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 { 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 { } else if is_sort {
c.fail_if_immutable(call_expr.left) c.fail_if_immutable(call_expr.left)
scope.update_var_type('a', array_info.elem_type) // position of `a` and `b` doesn't matter, they're the same
scope.update_var_type('b', array_info.elem_type) scope_register_ab(mut scope, call_expr.pos, array_info.elem_type)
// Verify `.sort(a < b)` // Verify `.sort(a < b)`
if call_expr.args.len > 0 { if call_expr.args.len > 0 {
if call_expr.args[0].expr !is ast.InfixExpr { 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 { fn scope_register_it(mut s ast.Scope, pos token.Position, typ table.Type) {
mut s := ast.new_scope(parent, start_pos) s.register('it', ast.Var{
s.end_pos = parent.end_pos name: 'it'
parent.children << s pos: pos
return s 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) { fn (mut c Checker) check_array_init_para_type(para string, expr ast.Expr, pos token.Position) {

View File

@ -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() { 1 | fn main() {
2 | a := [1,2,3,4].filter(fn(a int, b int) bool { return a > 0 }) 2 | a := [1,2,3,4].filter(fn(a int, b int) bool { return a > 0 })
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ~~
3 | println(a) 3 | println(a)
4 | } 4 | }

View File

@ -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() { 1 | fn main() {
2 | a := [1,2,3,4].filter(fn(a string) bool { return a.len > 0 }) 2 | a := [1,2,3,4].filter(fn(a string) bool { return a.len > 0 })
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ~~
3 | println(a) 3 | println(a)
4 | } 4 | }

View File

@ -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 | } 3 | }
4 | fn main() { 4 | fn main() {
5 | a := [1,2,3,4].filter(fil) 5 | a := [1,2,3,4].filter(fil)
| ~~~~~ | ~~~~~~~~~~~
6 | println(a) 6 | println(a)
7 | } 7 | }

View File

@ -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 | } 3 | }
4 | fn main() { 4 | fn main() {
5 | a := [1,2,3,4].filter(fil) 5 | a := [1,2,3,4].filter(fil)
| ~~~~~ | ~~~
6 | println(a) 6 | println(a)
7 | } 7 | }

View File

@ -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() { 1 | fn main() {
2 | a := [1,2,3,4].map(fn(a int, b int) int {return a + b}) 2 | a := [1,2,3,4].map(fn(a int, b int) int {return a + b})
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ~~
3 | println(a) 3 | println(a)
4 | } 4 | }

View File

@ -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() { 1 | fn main() {
2 | a := [1,2,3,4].map(fn(a string) string { return a }) 2 | a := [1,2,3,4].map(fn(a string) string { return a })
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ~~
3 | println(a) 3 | println(a)
4 | } 4 | }

View File

@ -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 | } 3 | }
4 | fn main() { 4 | fn main() {
5 | a := [1,2,3,4].map(add) 5 | a := [1,2,3,4].map(add)
| ~~~~~ | ~~~~~~~~
6 | println(a) 6 | println(a)
7 | } 7 | }

View File

@ -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 | } 3 | }
4 | fn main() { 4 | fn main() {
5 | a := [1,2,3,4].map(add) 5 | a := [1,2,3,4].map(add)
| ~~~~~ | ~~~
6 | println(a) 6 | println(a)
7 | } 7 | }

View File

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

View File

@ -0,0 +1,3 @@
fn main() {
_ := 'test'.filter(it == `t`)
}

View File

@ -1276,21 +1276,13 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr {
if p.tok.kind == .dollar { if p.tok.kind == .dollar {
return p.comptime_method_call(left) return p.comptime_method_call(left)
} }
mut name_pos := p.tok.position() name_pos := p.tok.position()
field_name := p.check_name() field_name := p.check_name()
is_filter := field_name in ['filter', 'map'] is_filter := field_name in ['filter', 'map']
if is_filter { if is_filter {
p.open_scope() 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' { } else if field_name == 'sort' {
p.open_scope() p.open_scope()
name_pos = p.tok.position()
p.scope_register_ab()
} }
// ! in mutable methods // ! in mutable methods
if p.tok.kind == .not && p.peek_tok.kind == .lpar { if p.tok.kind == .not && p.peek_tok.kind == .lpar {