Skip to content

Commit

Permalink
checker: support lambda expressions in array methods like `a.map(|x|x…
Browse files Browse the repository at this point in the history
…*10)` too (#19424)
  • Loading branch information
spytheman committed Sep 23, 2023
1 parent 9954f89 commit a685088
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 88 deletions.
35 changes: 35 additions & 0 deletions vlib/builtin/lambda_expr_array_test.v
@@ -0,0 +1,35 @@
const a = [4, 5, 1, 2, 5, 9]

fn test_map() {
assert a.map(it) == a
assert a.map(it * 10) == [40, 50, 10, 20, 50, 90]
//
assert a.map(|x| x) == a
assert a.map(|x| x * 10) == [40, 50, 10, 20, 50, 90]
assert a.map(|x| 'x: ${x}') == ['x: 4', 'x: 5', 'x: 1', 'x: 2', 'x: 5', 'x: 9']
assert a.map(|x| f64(x) * 10.0) == [40.0, 50.0, 10.0, 20.0, 50.0, 90.0]
}

fn test_filter() {
assert a.filter(it > 4) == [5, 5, 9]
assert a.filter(it < 4) == [1, 2]
//
assert a.filter(|x| x > 4) == [5, 5, 9]
assert a.filter(|x| x < 4) == [1, 2]
}

fn test_any() {
assert a.any(it > 4)
assert !a.any(it > 40)

assert a.any(|x| x > 4)
assert !a.any(|x| x > 40)
}

fn test_all() {
assert !a.all(it > 4)
assert a.all(it < 40)

assert !a.all(|x| x > 4)
assert a.all(|x| x < 40)
}
4 changes: 2 additions & 2 deletions vlib/v/ast/ast.v
Expand Up @@ -1786,9 +1786,9 @@ pub:

pub struct LambdaExpr {
pub:
pos token.Pos
params []Ident
pos token.Pos
pub mut:
params []Ident
pos_expr token.Pos
expr Expr
pos_end token.Pos
Expand Down
47 changes: 30 additions & 17 deletions vlib/v/checker/fn.v
Expand Up @@ -2587,6 +2587,7 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
mut elem_typ := ast.void_type
if method_name == 'slice' && !c.is_builtin_mod {
c.error('.slice() is a private method, use `x[start..end]` instead', node.pos)
return ast.void_type
}
array_info := if left_sym.info is ast.Array {
left_sym.info as ast.Array
Expand All @@ -2595,10 +2596,27 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
}
elem_typ = array_info.elem_type
if method_name in ['filter', 'map', 'any', 'all'] {
// position of `it` doesn't matter
scope_register_it(mut node.scope, node.pos, elem_typ)
if node.args.len > 0 && mut node.args[0].expr is ast.LambdaExpr {
if node.args[0].expr.params.len != 1 {
c.error('lambda expressions used in the builtin array methods require exactly 1 parameter',
node.args[0].expr.pos)
return ast.void_type
}
if method_name == 'map' {
c.lambda_expr_fix_type_of_param(mut node.args[0].expr, mut node.args[0].expr.params[0],
elem_typ)
le_type := c.expr(mut node.args[0].expr.expr)
// eprintln('>>>>> node.args[0].expr: ${ast.Expr(node.args[0].expr)} | elem_typ: ${elem_typ} | etype: ${le_type}')
c.support_lambda_expr_one_param(elem_typ, le_type, mut node.args[0].expr)
} else {
c.support_lambda_expr_one_param(elem_typ, ast.bool_type, mut node.args[0].expr)
}
} else {
// position of `it` doesn't matter
scope_register_it(mut node.scope, node.pos, elem_typ)
}
} else if method_name == 'sorted_with_compare' && node.args.len == 1 {
if mut node.args[0].expr is ast.LambdaExpr {
if node.args.len > 0 && mut node.args[0].expr is ast.LambdaExpr {
c.support_lambda_expr_in_sort(elem_typ.ref(), ast.int_type, mut node.args[0].expr)
}
} else if method_name == 'sort' || method_name == 'sorted' {
Expand Down Expand Up @@ -2671,6 +2689,7 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
arg_type = c.check_expr_opt_call(arg.expr, c.expr(mut arg.expr))
}
if method_name == 'map' {
// eprintln('>>>>>>> map node.args[0].expr: ${node.args[0].expr}, left_type: ${left_type} | elem_typ: ${elem_typ} | arg_type: ${arg_type}')
// check fn
c.check_map_and_filter(true, elem_typ, node)
arg_sym := c.table.sym(arg_type)
Expand Down Expand Up @@ -2777,25 +2796,19 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
}

fn scope_register_it(mut s ast.Scope, pos token.Pos, typ ast.Type) {
s.register(ast.Var{
name: 'it'
pos: pos
typ: typ
is_used: true
})
scope_register_var_name(mut s, pos, typ, 'it')
}

fn scope_register_a_b(mut s ast.Scope, pos token.Pos, typ ast.Type) {
scope_register_var_name(mut s, pos, typ.ref(), 'a')
scope_register_var_name(mut s, pos, typ.ref(), 'b')
}

fn scope_register_var_name(mut s ast.Scope, pos token.Pos, typ ast.Type, name string) {
s.register(ast.Var{
name: 'a'
pos: pos
typ: typ.ref()
is_used: true
})
s.register(ast.Var{
name: 'b'
name: name
pos: pos
typ: typ.ref()
typ: typ
is_used: true
})
}
41 changes: 30 additions & 11 deletions vlib/v/checker/lambda_expr.v
Expand Up @@ -33,17 +33,7 @@ pub fn (mut c Checker) lambda_expr(mut node ast.LambdaExpr, exp_typ ast.Type) as
eparam := exp_sym.info.func.params[idx]
eparam_type := eparam.typ
eparam_auto_deref := eparam.typ.is_ptr()
if mut v := node.scope.find(x.name) {
if mut v is ast.Var {
v.is_arg = true
v.typ = eparam_type
v.expr = ast.empty_expr
v.is_auto_deref = eparam_auto_deref
}
}
c.ident(mut x)
x.obj.typ = eparam_type

c.lambda_expr_fix_type_of_param(mut node, mut x, eparam_type)
params << ast.Param{
pos: x.pos
name: x.name
Expand Down Expand Up @@ -100,6 +90,19 @@ pub fn (mut c Checker) lambda_expr(mut node ast.LambdaExpr, exp_typ ast.Type) as
return exp_typ
}

pub fn (mut c Checker) lambda_expr_fix_type_of_param(mut node ast.LambdaExpr, mut pident ast.Ident, ptype ast.Type) {
if mut v := node.scope.find(pident.name) {
if mut v is ast.Var {
v.is_arg = true
v.typ = ptype
v.is_auto_deref = ptype.is_ptr()
v.expr = ast.empty_expr
}
}
c.ident(mut pident)
pident.obj.typ = ptype
}

pub fn (mut c Checker) support_lambda_expr_in_sort(param_type ast.Type, return_type ast.Type, mut expr ast.LambdaExpr) {
is_auto_rec := param_type.is_ptr()
mut expected_fn := ast.Fn{
Expand All @@ -121,3 +124,19 @@ pub fn (mut c Checker) support_lambda_expr_in_sort(param_type ast.Type, return_t
false))
c.lambda_expr(mut expr, expected_fn_type)
}

pub fn (mut c Checker) support_lambda_expr_one_param(param_type ast.Type, return_type ast.Type, mut expr ast.LambdaExpr) {
mut expected_fn := ast.Fn{
params: [
ast.Param{
name: 'xx'
typ: param_type
is_auto_rec: param_type.is_ptr()
},
]
return_type: return_type
}
cb_type := c.table.find_or_register_fn_type(expected_fn, true, false)
expected_fn_type := ast.new_type(cb_type)
c.lambda_expr(mut expr, expected_fn_type)
}
12 changes: 12 additions & 0 deletions vlib/v/checker/tests/lambda_expression_in_map.out
@@ -0,0 +1,12 @@
vlib/v/checker/tests/lambda_expression_in_map.vv:3:12: error: lambda expressions used in the builtin array methods require exactly 1 parameter
1 | a := [4, 5]
2 | dump(a.map(it))
3 | dump(a.map(|| 5))
| ~~
4 | dump(a.map(|x| 5 * x))
5 | dump(a.map(|x| x))
vlib/v/checker/tests/lambda_expression_in_map.vv:6:12: error: lambda expressions used in the builtin array methods require exactly 1 parameter
4 | dump(a.map(|x| 5 * x))
5 | dump(a.map(|x| x))
6 | dump(a.map(|x, y| x))
| ^
6 changes: 6 additions & 0 deletions vlib/v/checker/tests/lambda_expression_in_map.vv
@@ -0,0 +1,6 @@
a := [4, 5]
dump(a.map(it))
dump(a.map(|| 5))
dump(a.map(|x| 5 * x))
dump(a.map(|x| x))
dump(a.map(|x, y| x))

0 comments on commit a685088

Please sign in to comment.