From 8787ab660a4eb21f74784cbbc2d93cca54277f19 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 23 Sep 2023 22:08:48 +0300 Subject: [PATCH] checker: support lambda expressions in array methods like `a.map(|x|x*10)` too (#19424) --- vlib/builtin/lambda_expr_array_test.v | 35 +++++ vlib/v/ast/ast.v | 4 +- vlib/v/checker/fn.v | 47 ++++-- vlib/v/checker/lambda_expr.v | 41 +++-- .../tests/lambda_expression_in_map.out | 12 ++ .../checker/tests/lambda_expression_in_map.vv | 6 + vlib/v/gen/c/array.v | 145 +++++++++++------- 7 files changed, 202 insertions(+), 88 deletions(-) create mode 100644 vlib/builtin/lambda_expr_array_test.v create mode 100644 vlib/v/checker/tests/lambda_expression_in_map.out create mode 100644 vlib/v/checker/tests/lambda_expression_in_map.vv diff --git a/vlib/builtin/lambda_expr_array_test.v b/vlib/builtin/lambda_expr_array_test.v new file mode 100644 index 00000000000000..a937d9f797c097 --- /dev/null +++ b/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) +} diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 22ae538af26847..946f8b9c8c126f 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -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 diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index b5dcf645165b59..0ce523e5e37878 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -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) { - 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 }) } diff --git a/vlib/v/checker/lambda_expr.v b/vlib/v/checker/lambda_expr.v index 4ca0a1ed8bc791..20c77fb520e736 100644 --- a/vlib/v/checker/lambda_expr.v +++ b/vlib/v/checker/lambda_expr.v @@ -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) +} diff --git a/vlib/v/checker/tests/lambda_expression_in_map.out b/vlib/v/checker/tests/lambda_expression_in_map.out new file mode 100644 index 00000000000000..e2c46e734dd4dc --- /dev/null +++ b/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)) + | ^ diff --git a/vlib/v/checker/tests/lambda_expression_in_map.vv b/vlib/v/checker/tests/lambda_expression_in_map.vv new file mode 100644 index 00000000000000..ce1cac282a32e4 --- /dev/null +++ b/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)) diff --git a/vlib/v/gen/c/array.v b/vlib/v/gen/c/array.v index 5ea36b04d57dc0..4faaf164164784 100644 --- a/vlib/v/gen/c/array.v +++ b/vlib/v/gen/c/array.v @@ -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' + } +}