Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

checker,cgen: support lambda expressions in array methods like a.map(|x|x*10) too #19424

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions vlib/builtin/lambda_expr_array_test.v
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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))
Loading
Loading