checker: check filter, map and sort left type (#6952)
parent
e03ae19372
commit
52b627feb3
|
@ -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
|
||||||
|
|
|
@ -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`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 | }
|
|
@ -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 | }
|
|
@ -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 | }
|
|
@ -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 | }
|
|
@ -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 | }
|
|
@ -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 | }
|
|
@ -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 | }
|
|
@ -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 | }
|
|
@ -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 | }
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
_ := 'test'.filter(it == `t`)
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue