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

This commit is contained in:
Delyan Angelov 2023-09-23 22:08:48 +03:00 committed by GitHub
parent 9954f89e3f
commit a685088fbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 204 additions and 90 deletions

View File

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

View File

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

View File

@ -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
@ -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' {
@ -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)
@ -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) {
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: 'it'
name: name
pos: pos
typ: typ
is_used: true
})
}
fn scope_register_a_b(mut s ast.Scope, pos token.Pos, typ ast.Type) {
s.register(ast.Var{
name: 'a'
pos: pos
typ: typ.ref()
is_used: true
})
s.register(ast.Var{
name: 'b'
pos: pos
typ: typ.ref()
is_used: true
})
}

View File

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

View File

@ -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))
| ^

View File

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

View File

@ -455,7 +455,7 @@ fn (mut g Gen) array_init_with_fields(node ast.ArrayInit, elem_type Type, is_amp
}
}
fn (mut g Gen) write_closure_fn(mut expr ast.AnonFn) {
fn (mut g Gen) write_closure_fn(mut expr ast.AnonFn, var_name string) {
past := g.past_tmp_var_new()
fn_ptr_name := g.fn_var_signature(expr.decl.return_type, expr.decl.params.map(it.typ),
past.tmp_var)
@ -463,7 +463,7 @@ fn (mut g Gen) write_closure_fn(mut expr ast.AnonFn) {
g.gen_anon_fn(mut expr)
g.writeln(';')
g.past_tmp_var_done(past)
g.write('(it)')
g.write('(${var_name})') // usually `it`
}
// `nums.map(it % 2 == 0)`
@ -490,14 +490,16 @@ fn (mut g Gen) gen_array_map(node ast.CallExpr) {
verror('map() requires an array')
}
mut expr := node.args[0].expr
mut closure_var_decl := ''
if node.args[0].expr is ast.SelectorExpr {
if node.args[0].expr.typ != ast.void_type {
var_sym := g.table.sym(node.args[0].expr.typ)
tmp_map_expr_result_name := g.new_tmp_var()
if mut expr is ast.SelectorExpr {
if expr.typ != ast.void_type {
var_sym := g.table.sym(expr.typ)
if var_sym.info is ast.FnType {
ret_elem_type = 'voidptr'
closure_var_decl = g.fn_var_signature(var_sym.info.func.return_type, var_sym.info.func.params.map(it.typ),
'ti')
tmp_map_expr_result_name)
}
}
}
@ -508,34 +510,34 @@ fn (mut g Gen) gen_array_map(node ast.CallExpr) {
i := g.new_tmp_var()
g.writeln('for (int ${i} = 0; ${i} < ${past.tmp_var}_len; ++${i}) {')
g.indent++
g.write_prepared_it(inp_info, inp_elem_type, past.tmp_var, i)
var_name := g.get_array_expr_param_name(mut expr)
g.write_prepared_var(var_name, inp_info, inp_elem_type, past.tmp_var, i)
g.set_current_pos_as_last_stmt_pos()
mut is_embed_map_filter := false
mut expr := node.args[0].expr
match mut expr {
ast.AnonFn {
g.write('${ret_elem_type} ti = ')
g.write('${ret_elem_type} ${tmp_map_expr_result_name} = ')
if expr.inherited_vars.len > 0 {
g.write_closure_fn(mut expr)
g.write_closure_fn(mut expr, var_name)
} else {
g.gen_anon_fn_decl(mut expr)
g.write('${expr.decl.name}(it)')
g.write('${expr.decl.name}(${var_name})')
}
}
ast.Ident {
g.write('${ret_elem_type} ti = ')
g.write('${ret_elem_type} ${tmp_map_expr_result_name} = ')
if expr.kind == .function {
g.write('${c_name(expr.name)}(it)')
g.write('${c_name(expr.name)}(${var_name})')
} else if expr.kind == .variable {
var_info := expr.var_info()
sym := g.table.sym(var_info.typ)
if sym.kind == .function {
g.write('${c_name(expr.name)}(it)')
g.write('${c_name(expr.name)}(${var_name})')
} else {
g.expr(node.args[0].expr)
g.expr(expr)
}
} else {
g.expr(node.args[0].expr)
g.expr(expr)
}
}
ast.CallExpr {
@ -543,8 +545,8 @@ fn (mut g Gen) gen_array_map(node ast.CallExpr) {
is_embed_map_filter = true
g.set_current_pos_as_last_stmt_pos()
}
g.write('${ret_elem_type} ti = ')
g.expr(node.args[0].expr)
g.write('${ret_elem_type} ${tmp_map_expr_result_name} = ')
g.expr(expr)
}
ast.CastExpr {
// value.map(Type(it)) when `value` is a comptime var
@ -554,20 +556,24 @@ fn (mut g Gen) gen_array_map(node ast.CallExpr) {
expr.expr_type = g.table.value_type(ctyp)
}
}
g.write('${ret_elem_type} ti = ')
g.expr(node.args[0].expr)
g.write('${ret_elem_type} ${tmp_map_expr_result_name} = ')
g.expr(expr)
}
ast.LambdaExpr {
g.write('${ret_elem_type} ${tmp_map_expr_result_name} = ')
g.expr(expr.expr)
}
else {
if closure_var_decl != '' {
g.write('${closure_var_decl} = ')
} else {
g.write('${ret_elem_type} ti = ')
g.write('${ret_elem_type} ${tmp_map_expr_result_name} = ')
}
g.expr(node.args[0].expr)
g.expr(expr)
}
}
g.writeln(';')
g.writeln('array_push${noscan}((array*)&${past.tmp_var}, &ti);')
g.writeln('array_push${noscan}((array*)&${past.tmp_var}, &${tmp_map_expr_result_name});')
g.indent--
g.writeln('}')
if !is_embed_map_filter {
@ -742,34 +748,35 @@ fn (mut g Gen) gen_array_filter(node ast.CallExpr) {
i := g.new_tmp_var()
g.writeln('for (int ${i} = 0; ${i} < ${past.tmp_var}_len; ++${i}) {')
g.indent++
g.write_prepared_it(info, elem_type_str, past.tmp_var, i)
mut expr := node.args[0].expr
var_name := g.get_array_expr_param_name(mut expr)
g.write_prepared_var(var_name, info, elem_type_str, past.tmp_var, i)
g.set_current_pos_as_last_stmt_pos()
mut is_embed_map_filter := false
mut expr := node.args[0].expr
match mut expr {
ast.AnonFn {
g.write('if (')
if expr.inherited_vars.len > 0 {
g.write_closure_fn(mut expr)
g.write_closure_fn(mut expr, var_name)
} else {
g.gen_anon_fn_decl(mut expr)
g.write('${expr.decl.name}(it)')
g.write('${expr.decl.name}(${var_name})')
}
}
ast.Ident {
g.write('if (')
if expr.kind == .function {
g.write('${c_name(expr.name)}(it)')
g.write('${c_name(expr.name)}(${var_name})')
} else if expr.kind == .variable {
var_info := expr.var_info()
sym_t := g.table.sym(var_info.typ)
if sym_t.kind == .function {
g.write('${c_name(expr.name)}(it)')
g.write('${c_name(expr.name)}(${var_name})')
} else {
g.expr(node.args[0].expr)
g.expr(expr)
}
} else {
g.expr(node.args[0].expr)
g.expr(expr)
}
}
ast.CallExpr {
@ -778,15 +785,19 @@ fn (mut g Gen) gen_array_filter(node ast.CallExpr) {
g.set_current_pos_as_last_stmt_pos()
}
g.write('if (')
g.expr(node.args[0].expr)
g.expr(expr)
}
ast.LambdaExpr {
g.write('if (')
g.expr(expr.expr)
}
else {
g.write('if (')
g.expr(node.args[0].expr)
g.expr(expr)
}
}
g.writeln(') {')
g.writeln('\tarray_push${noscan}((array*)&${past.tmp_var}, &it);')
g.writeln('\tarray_push${noscan}((array*)&${past.tmp_var}, &${var_name});')
g.writeln('}')
g.indent--
g.writeln('}')
@ -1127,34 +1138,35 @@ fn (mut g Gen) gen_array_any(node ast.CallExpr) {
i := g.new_tmp_var()
g.writeln('for (int ${i} = 0; ${i} < ${past.tmp_var}_len; ++${i}) {')
g.indent++
g.write_prepared_it(info, elem_type_str, past.tmp_var, i)
mut expr := node.args[0].expr
var_name := g.get_array_expr_param_name(mut expr)
g.write_prepared_var(var_name, info, elem_type_str, past.tmp_var, i)
g.set_current_pos_as_last_stmt_pos()
mut is_embed_map_filter := false
mut expr := node.args[0].expr
match mut expr {
ast.AnonFn {
g.write('if (')
if expr.inherited_vars.len > 0 {
g.write_closure_fn(mut expr)
g.write_closure_fn(mut expr, var_name)
} else {
g.gen_anon_fn_decl(mut expr)
g.write('${expr.decl.name}(it)')
g.write('${expr.decl.name}(${var_name})')
}
}
ast.Ident {
g.write('if (')
if expr.kind == .function {
g.write('${c_name(expr.name)}(it)')
g.write('${c_name(expr.name)}(${var_name})')
} else if expr.kind == .variable {
var_info := expr.var_info()
sym_t := g.table.sym(var_info.typ)
if sym_t.kind == .function {
g.write('${c_name(expr.name)}(it)')
g.write('${c_name(expr.name)}(${var_name})')
} else {
g.expr(node.args[0].expr)
g.expr(expr)
}
} else {
g.expr(node.args[0].expr)
g.expr(expr)
}
}
ast.CallExpr {
@ -1163,11 +1175,15 @@ fn (mut g Gen) gen_array_any(node ast.CallExpr) {
g.set_current_pos_as_last_stmt_pos()
}
g.write('if (')
g.expr(node.args[0].expr)
g.expr(expr)
}
ast.LambdaExpr {
g.write('if (')
g.expr(expr.expr)
}
else {
g.write('if (')
g.expr(node.args[0].expr)
g.expr(expr)
}
}
g.writeln(') {')
@ -1202,35 +1218,36 @@ fn (mut g Gen) gen_array_all(node ast.CallExpr) {
i := g.new_tmp_var()
g.writeln('for (int ${i} = 0; ${i} < ${past.tmp_var}_len; ++${i}) {')
g.indent++
g.write_prepared_it(info, elem_type_str, past.tmp_var, i)
mut expr := node.args[0].expr
var_name := g.get_array_expr_param_name(mut expr)
g.write_prepared_var(var_name, info, elem_type_str, past.tmp_var, i)
g.empty_line = true
g.set_current_pos_as_last_stmt_pos()
mut is_embed_map_filter := false
mut expr := node.args[0].expr
match mut expr {
ast.AnonFn {
g.write('if (!(')
if expr.inherited_vars.len > 0 {
g.write_closure_fn(mut expr)
g.write_closure_fn(mut expr, var_name)
} else {
g.gen_anon_fn_decl(mut expr)
g.write('${expr.decl.name}(it)')
g.write('${expr.decl.name}(${var_name})')
}
}
ast.Ident {
g.write('if (!(')
if expr.kind == .function {
g.write('${c_name(expr.name)}(it)')
g.write('${c_name(expr.name)}(${var_name})')
} else if expr.kind == .variable {
var_info := expr.var_info()
sym_t := g.table.sym(var_info.typ)
if sym_t.kind == .function {
g.write('${c_name(expr.name)}(it)')
g.write('${c_name(expr.name)}(${var_name})')
} else {
g.expr(node.args[0].expr)
g.expr(expr)
}
} else {
g.expr(node.args[0].expr)
g.expr(expr)
}
}
ast.CallExpr {
@ -1239,11 +1256,15 @@ fn (mut g Gen) gen_array_all(node ast.CallExpr) {
g.set_current_pos_as_last_stmt_pos()
}
g.write('if (!(')
g.expr(node.args[0].expr)
g.expr(expr)
}
ast.LambdaExpr {
g.write('if (!(')
g.expr(expr.expr)
}
else {
g.write('if (!(')
g.expr(node.args[0].expr)
g.expr(expr)
}
}
g.writeln(')) {')
@ -1290,12 +1311,12 @@ fn (mut g Gen) write_prepared_tmp_value(tmp string, node &ast.CallExpr, tmp_styp
return has_infix_left_var_name
}
fn (mut g Gen) write_prepared_it(inp_info ast.Array, inp_elem_type string, tmp string, i string) {
fn (mut g Gen) write_prepared_var(var_name string, inp_info ast.Array, inp_elem_type string, tmp string, i string) {
if g.table.sym(inp_info.elem_type).kind == .array_fixed {
g.writeln('${inp_elem_type} it;')
g.writeln('memcpy(&it, ((${inp_elem_type}*) ${tmp}_orig.data)[${i}], sizeof(${inp_elem_type}));')
g.writeln('${inp_elem_type} ${var_name};')
g.writeln('memcpy(&${var_name}, ((${inp_elem_type}*) ${tmp}_orig.data)[${i}], sizeof(${inp_elem_type}));')
} else {
g.writeln('${inp_elem_type} it = ((${inp_elem_type}*) ${tmp}_orig.data)[${i}];')
g.writeln('${inp_elem_type} ${var_name} = ((${inp_elem_type}*) ${tmp}_orig.data)[${i}];')
}
}
@ -1313,3 +1334,11 @@ fn (mut g Gen) fixed_array_var_init(expr ast.Expr, size int) {
}
g.write('}')
}
fn (mut g Gen) get_array_expr_param_name(mut expr ast.Expr) string {
return if mut expr is ast.LambdaExpr {
expr.params[0].name
} else {
'it'
}
}