diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 295e709691..990a91f669 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -534,6 +534,7 @@ pub mut: should_be_skipped bool // true for calls to `[if someflag?]` functions, when there is no `-d someflag` concrete_types []Type // concrete types, e.g. concrete_list_pos token.Pos + raw_concrete_types []Type free_receiver bool // true if the receiver expression needs to be freed scope &Scope from_embed_types []Type // holds the type of the embed that the method is called from diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index 5edb95adf3..38b122b0bc 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -135,6 +135,8 @@ pub fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { } } } + } else if right is ast.ComptimeSelector { + right_type = c.comptime_fields_default_type } if is_decl { // check generic struct init and return unwrap generic struct type diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 9abcf04d6e..65020f66cb 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -70,21 +70,22 @@ pub mut: rlocked_names []string // vars that are currently read-locked in_for_count int // if checker is currently in a for loop // checked_ident string // to avoid infinite checker loops - should_abort bool // when too many errors/warnings/notices are accumulated, .should_abort becomes true. It is checked in statement/expression loops, so the checker can return early, instead of wasting time. - returns bool - scope_returns bool - is_builtin_mod bool // true inside the 'builtin', 'os' or 'strconv' modules; TODO: remove the need for special casing this - is_generated bool // true for `[generated] module xyz` .v files - inside_unsafe bool // true inside `unsafe {}` blocks - inside_const bool // true inside `const ( ... )` blocks - inside_anon_fn bool // true inside `fn() { ... }()` - inside_ref_lit bool // true inside `a := &something` - inside_defer bool // true inside `defer {}` blocks - inside_fn_arg bool // `a`, `b` in `a.f(b)` - inside_ct_attr bool // true inside `[if expr]` - skip_flags bool // should `#flag` and `#include` be skipped - fn_level int // 0 for the top level, 1 for `fn abc() {}`, 2 for a nested fn, etc - ct_cond_stack []ast.Expr + should_abort bool // when too many errors/warnings/notices are accumulated, .should_abort becomes true. It is checked in statement/expression loops, so the checker can return early, instead of wasting time. + returns bool + scope_returns bool + is_builtin_mod bool // true inside the 'builtin', 'os' or 'strconv' modules; TODO: remove the need for special casing this + is_generated bool // true for `[generated] module xyz` .v files + inside_unsafe bool // true inside `unsafe {}` blocks + inside_const bool // true inside `const ( ... )` blocks + inside_anon_fn bool // true inside `fn() { ... }()` + inside_ref_lit bool // true inside `a := &something` + inside_defer bool // true inside `defer {}` blocks + inside_fn_arg bool // `a`, `b` in `a.f(b)` + inside_ct_attr bool // true inside `[if expr]` + inside_comptime_for_field bool + skip_flags bool // should `#flag` and `#include` be skipped + fn_level int // 0 for the top level, 1 for `fn abc() {}`, 2 for a nested fn, etc + ct_cond_stack []ast.Expr mut: stmt_level int // the nesting level inside each stmts list; // .stmt_level is used to check for `evaluated but not used` ExprStmts like `1 << 1` diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index c2503603bd..e534044736 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -109,10 +109,19 @@ fn (mut c Checker) comptime_for(node ast.ComptimeFor) { c.error('unknown type `$sym.name`', node.typ_pos) } if node.kind == .fields { - c.comptime_fields_type[node.val_var] = node.typ - c.comptime_fields_default_type = node.typ + if sym.kind == .struct_ { + sym_info := sym.info as ast.Struct + c.inside_comptime_for_field = true + for field in sym_info.fields { + c.comptime_fields_type[node.val_var] = node.typ + c.comptime_fields_default_type = field.typ + c.stmts(node.stmts) + } + c.inside_comptime_for_field = false + } + } else { + c.stmts(node.stmts) } - c.stmts(node.stmts) } // comptime const eval diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 5b2938bbc4..d8e85ab1d1 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -428,6 +428,7 @@ pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) } mut has_generic := false // foo() instead of foo() mut concrete_types := []ast.Type{} + node.concrete_types = node.raw_concrete_types for concrete_type in node.concrete_types { if concrete_type.has_flag(.generic) { has_generic = true @@ -777,6 +778,13 @@ pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) typ := c.check_expr_opt_call(call_arg.expr, c.expr(call_arg.expr)) node.args[i].typ = typ + if c.inside_comptime_for_field { + if mut call_arg.expr is ast.Ident { + if mut call_arg.expr.obj is ast.Var { + node.args[i].typ = call_arg.expr.obj.typ + } + } + } typ_sym := c.table.sym(typ) param_typ_sym := c.table.sym(param.typ) if func.is_variadic && typ.has_flag(.variadic) && node.args.len - 1 > i { @@ -936,6 +944,9 @@ pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) continue } c.check_expected_call_arg(utyp, unwrap_typ, node.language, call_arg) or { + if c.comptime_fields_type.len > 0 { + continue + } c.error('$err.msg() in argument ${i + 1} to `$fn_name`', call_arg.pos) } } diff --git a/vlib/v/checker/tests/comptime_for.out b/vlib/v/checker/tests/comptime_for.out index 0eea091a31..57e339b3a1 100644 --- a/vlib/v/checker/tests/comptime_for.out +++ b/vlib/v/checker/tests/comptime_for.out @@ -18,20 +18,6 @@ vlib/v/checker/tests/comptime_for.vv:4:12: error: unknown type `T` | ^ 5 | $if f.typ is Huh {} 6 | $if f.typ is T {} -vlib/v/checker/tests/comptime_for.vv:5:16: error: unknown type `Huh` - 3 | $for f in Huh.fields {} - 4 | $for f in T.fields { - 5 | $if f.typ is Huh {} - | ~~~ - 6 | $if f.typ is T {} - 7 | } -vlib/v/checker/tests/comptime_for.vv:6:16: error: unknown type `T` - 4 | $for f in T.fields { - 5 | $if f.typ is Huh {} - 6 | $if f.typ is T {} - | ^ - 7 | } - 8 | _ = m vlib/v/checker/tests/comptime_for.vv:8:6: error: undefined ident: `m` 6 | $if f.typ is T {} 7 | } diff --git a/vlib/v/checker/tests/comptime_for.vv b/vlib/v/checker/tests/comptime_for.vv index a6d67a063c..1808d13e63 100644 --- a/vlib/v/checker/tests/comptime_for.vv +++ b/vlib/v/checker/tests/comptime_for.vv @@ -20,4 +20,6 @@ struct S1 { i int } -gf() +fn main() { + gf() +} diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 33f2ee8888..5b9b351caf 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -46,122 +46,123 @@ struct Gen { timers_should_print bool table &ast.Table mut: - out strings.Builder - cheaders strings.Builder - includes strings.Builder // all C #includes required by V modules - typedefs strings.Builder - enum_typedefs strings.Builder // enum types - definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) - type_definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) - alias_definitions strings.Builder // alias fixed array of non-builtin - hotcode_definitions strings.Builder // -live declarations & functions - channel_definitions strings.Builder // channel related code - comptime_definitions strings.Builder // custom defines, given by -d/-define flags on the CLI - global_inits map[string]strings.Builder // default initializers for globals (goes in _vinit()) - global_init strings.Builder // thread local of the above - inits map[string]strings.Builder // contents of `void _vinit/2{}` - init strings.Builder - cleanup strings.Builder - cleanups map[string]strings.Builder // contents of `void _vcleanup(){}` - gowrappers strings.Builder // all go callsite wrappers - stringliterals strings.Builder // all string literals (they depend on tos3() beeing defined - auto_str_funcs strings.Builder // function bodies of all auto generated _str funcs - dump_funcs strings.Builder // function bodies of all auto generated _str funcs - pcs_declarations strings.Builder // -prof profile counter declarations for each function - embedded_data strings.Builder // data to embed in the executable/binary - shared_types strings.Builder // shared/lock types - shared_functions strings.Builder // shared constructors - options strings.Builder // `Option_xxxx` types - json_forward_decls strings.Builder // json type forward decls - sql_buf strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc - file &ast.File - unique_file_path_hash u64 // a hash of file.path, used for making auxilary fn generation unique (like `compare_xyz`) - fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0 - last_fn_c_name string - tmp_count int // counter for unique tmp vars (_tmp1, _tmp2 etc); resets at the start of each fn. - tmp_count_af int // a separate tmp var counter for autofree fn calls - tmp_count_declarations int // counter for unique tmp names (_d1, _d2 etc); does NOT reset, used for C declarations - global_tmp_count int // like tmp_count but global and not resetted in each function - discard_or_result bool // do not safe last ExprStmt of `or` block in tmp variable to defer ongoing expr usage - is_assign_lhs bool // inside left part of assign expr (for array_set(), etc) - is_void_expr_stmt bool // ExprStmt whos result is discarded - is_arraymap_set bool // map or array set value state - is_amp bool // for `&Foo{}` to merge PrefixExpr `&` and StructInit `Foo{}`; also for `&byte(0)` etc - is_sql bool // Inside `sql db{}` statement, generating sql instead of C (e.g. `and` instead of `&&` etc) - is_shared bool // for initialization of hidden mutex in `[rw]shared` literals - is_vlines_enabled bool // is it safe to generate #line directives when -g is passed - is_autofree bool // false, inside the bodies of fns marked with [manualfree], otherwise === g.pref.autofree - is_builtin_mod bool - is_json_fn bool // inside json.encode() - is_js_call bool // for handling a special type arg #1 `json.decode(User, ...)` - is_fn_index_call bool - vlines_path string // set to the proper path for generating #line directives - optionals map[string]string // to avoid duplicates - done_optionals shared []string // to avoid duplicates - chan_pop_optionals map[string]string // types for `x := <-ch or {...}` - chan_push_optionals map[string]string // types for `ch <- x or {...}` - mtxs string // array of mutexes if the `lock` has multiple variables - labeled_loops map[string]&ast.Stmt - inner_loop &ast.Stmt - shareds map[int]string // types with hidden mutex for which decl has been emitted - inside_ternary int // ?: comma separated statements on a single line - inside_map_postfix bool // inside map++/-- postfix expr - inside_map_infix bool // inside map< fn counter name - hotcode_fn_names []string - embedded_files []ast.EmbeddedFile - sql_i int - sql_stmt_name string - sql_bind_name string - sql_idents []string - sql_idents_types []ast.Type - sql_left_type ast.Type - sql_table_name string - sql_fkey string - sql_parent_id string - sql_side SqlExprSide // left or right, to distinguish idents in `name == name` - strs_to_free0 []string // strings.Builder + out strings.Builder + cheaders strings.Builder + includes strings.Builder // all C #includes required by V modules + typedefs strings.Builder + enum_typedefs strings.Builder // enum types + definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) + type_definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) + alias_definitions strings.Builder // alias fixed array of non-builtin + hotcode_definitions strings.Builder // -live declarations & functions + channel_definitions strings.Builder // channel related code + comptime_definitions strings.Builder // custom defines, given by -d/-define flags on the CLI + global_inits map[string]strings.Builder // default initializers for globals (goes in _vinit()) + global_init strings.Builder // thread local of the above + inits map[string]strings.Builder // contents of `void _vinit/2{}` + init strings.Builder + cleanup strings.Builder + cleanups map[string]strings.Builder // contents of `void _vcleanup(){}` + gowrappers strings.Builder // all go callsite wrappers + stringliterals strings.Builder // all string literals (they depend on tos3() beeing defined + auto_str_funcs strings.Builder // function bodies of all auto generated _str funcs + dump_funcs strings.Builder // function bodies of all auto generated _str funcs + pcs_declarations strings.Builder // -prof profile counter declarations for each function + embedded_data strings.Builder // data to embed in the executable/binary + shared_types strings.Builder // shared/lock types + shared_functions strings.Builder // shared constructors + options strings.Builder // `Option_xxxx` types + json_forward_decls strings.Builder // json type forward decls + sql_buf strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc + file &ast.File + unique_file_path_hash u64 // a hash of file.path, used for making auxilary fn generation unique (like `compare_xyz`) + fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0 + last_fn_c_name string + tmp_count int // counter for unique tmp vars (_tmp1, _tmp2 etc); resets at the start of each fn. + tmp_count_af int // a separate tmp var counter for autofree fn calls + tmp_count_declarations int // counter for unique tmp names (_d1, _d2 etc); does NOT reset, used for C declarations + global_tmp_count int // like tmp_count but global and not resetted in each function + discard_or_result bool // do not safe last ExprStmt of `or` block in tmp variable to defer ongoing expr usage + is_assign_lhs bool // inside left part of assign expr (for array_set(), etc) + is_void_expr_stmt bool // ExprStmt whos result is discarded + is_arraymap_set bool // map or array set value state + is_amp bool // for `&Foo{}` to merge PrefixExpr `&` and StructInit `Foo{}`; also for `&byte(0)` etc + is_sql bool // Inside `sql db{}` statement, generating sql instead of C (e.g. `and` instead of `&&` etc) + is_shared bool // for initialization of hidden mutex in `[rw]shared` literals + is_vlines_enabled bool // is it safe to generate #line directives when -g is passed + is_autofree bool // false, inside the bodies of fns marked with [manualfree], otherwise === g.pref.autofree + is_builtin_mod bool + is_json_fn bool // inside json.encode() + is_js_call bool // for handling a special type arg #1 `json.decode(User, ...)` + is_fn_index_call bool + vlines_path string // set to the proper path for generating #line directives + optionals map[string]string // to avoid duplicates + done_optionals shared []string // to avoid duplicates + chan_pop_optionals map[string]string // types for `x := <-ch or {...}` + chan_push_optionals map[string]string // types for `ch <- x or {...}` + mtxs string // array of mutexes if the `lock` has multiple variables + labeled_loops map[string]&ast.Stmt + inner_loop &ast.Stmt + shareds map[int]string // types with hidden mutex for which decl has been emitted + inside_ternary int // ?: comma separated statements on a single line + inside_map_postfix bool // inside map++/-- postfix expr + inside_map_infix bool // inside map< fn counter name + hotcode_fn_names []string + embedded_files []ast.EmbeddedFile + sql_i int + sql_stmt_name string + sql_bind_name string + sql_idents []string + sql_idents_types []ast.Type + sql_left_type ast.Type + sql_table_name string + sql_fkey string + sql_parent_id string + sql_side SqlExprSide // left or right, to distinguish idents in `name == name` + strs_to_free0 []string // strings.Builder // strs_to_free []string // strings.Builder // tmp_arg_vars_to_free []string // autofree_pregen map[string]string diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 3e38406bf3..0985b31949 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -503,13 +503,16 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) { } } else if node.kind == .fields { // TODO add fields - if sym.info is ast.Struct { - if sym.info.fields.len > 0 { + if sym.kind == .struct_ { + sym_info := sym.info as ast.Struct + if sym_info.fields.len > 0 { g.writeln('\tFieldData $node.val_var = {0};') } - for field in sym.info.fields { + g.inside_comptime_for_field = true + for field in sym_info.fields { g.comptime_for_field_var = node.val_var g.comptime_for_field_value = field + g.comptime_for_field_type = field.typ g.writeln('/* field $i */ {') g.writeln('\t${node.val_var}.name = _SLIT("$field.name");') if field.attrs.len == 0 { @@ -531,7 +534,9 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) { g.stmts(node.stmts) i++ g.writeln('}') + g.comptime_for_field_type = 0 } + g.inside_comptime_for_field = false g.comptime_var_type_map.delete(node.val_var) } } else if node.kind == .attributes { diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index bb7c8e5fdd..df60db62b3 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1120,6 +1120,16 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { } is_selector_call = true } + if g.inside_comptime_for_field { + mut node_ := unsafe { node } + for i, mut call_arg in node_.args { + if mut call_arg.expr is ast.Ident { + if mut call_arg.expr.obj is ast.Var { + node_.args[i].typ = call_arg.expr.obj.typ + } + } + } + } mut name := node.name is_print := name in ['print', 'println', 'eprint', 'eprintln', 'panic'] print_method := name @@ -1201,14 +1211,22 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { } } if !is_selector_call { - name = g.generic_fn_name(node.concrete_types, name, false) + if func := g.table.find_fn(node.name) { + if func.generic_names.len > 0 { + if g.comptime_for_field_type != 0 && g.inside_comptime_for_field { + name = g.generic_fn_name([g.comptime_for_field_type], name, false) + } else { + name = g.generic_fn_name(node.concrete_types, name, false) + } + } + } } // TODO2 // cgen shouldn't modify ast nodes, this should be moved // g.generate_tmp_autofree_arg_vars(node, name) // Handle `print(x)` mut print_auto_str := false - if is_print && (node.args[0].typ != ast.string_type || g.comptime_for_method.len > 0) { // && !free_tmp_arg_vars { + if is_print && (node.args[0].typ != ast.string_type || g.comptime_for_method.len > 0) { mut typ := node.args[0].typ if typ == 0 { g.checker_bug('print arg.typ is 0', node.pos) diff --git a/vlib/v/gen/c/str_intp.v b/vlib/v/gen/c/str_intp.v index 7ea30feab7..c34d2bcc7c 100644 --- a/vlib/v/gen/c/str_intp.v +++ b/vlib/v/gen/c/str_intp.v @@ -197,6 +197,16 @@ fn (mut g Gen) str_val(node ast.StringInterLiteral, i int) { fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { // fn (mut g Gen) str_int2(node ast.StringInterLiteral) { + if g.inside_comptime_for_field { + mut node_ := unsafe { node } + for i, expr in node_.exprs { + if mut expr is ast.Ident { + if mut expr.obj is ast.Var { + node_.expr_types[i] = expr.obj.typ + } + } + } + } g.write(' str_intp($node.vals.len, ') g.write('_MOV((StrIntpData[]){') for i, val in node.vals { diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index c4b8140de8..a400e0f46a 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -92,6 +92,7 @@ pub fn (mut p Parser) call_expr(language ast.Language, mod string) ast.CallExpr language: language concrete_types: concrete_types concrete_list_pos: concrete_list_pos + raw_concrete_types: concrete_types or_block: ast.OrExpr{ stmts: or_stmts kind: or_kind diff --git a/vlib/v/tests/comptime_for_in_field_with_generic_fn_test.v b/vlib/v/tests/comptime_for_in_field_with_generic_fn_test.v new file mode 100644 index 0000000000..974112dfcd --- /dev/null +++ b/vlib/v/tests/comptime_for_in_field_with_generic_fn_test.v @@ -0,0 +1,49 @@ +module main + +// exploring `decode` options with nested structs + +struct Parent { + name string + age int + child Child +} + +struct Child { + name string + age int +} + +fn inspect(t T) string { + mut output_str := '' + println('$T.name') + $for field in T.fields { + val := t.$(field.name) + $if field.typ is string { + println(' $field.name = $val') + output_str += ' $field.name = $val\n' + } $else $if field.typ is int { + println(' $field.name = $val') + output_str += ' $field.name = $val\n' + } $else { + str := inspect(val) + output_str += str + } + } + return output_str +} + +fn test_comptime_for_in_field_with_generic_fn() { + p := Parent{ + name: 'parent' + age: 30 + child: Child{ + name: 'child' + age: 5 + } + } + ret := inspect(p) + assert ret.contains('name = parent') + assert ret.contains('age = 30') + assert ret.contains('name = child') + assert ret.contains('age = 5') +} diff --git a/vlib/v/tests/for_t_fields_with_comptime_if_test.v b/vlib/v/tests/for_t_fields_with_comptime_if_test.v index e2b4272295..dead6c474b 100644 --- a/vlib/v/tests/for_t_fields_with_comptime_if_test.v +++ b/vlib/v/tests/for_t_fields_with_comptime_if_test.v @@ -39,7 +39,7 @@ fn decode2() T { x.$(field.name) = byte(-1) } $else $if field.typ is int { x.$(field.name) = int(-1) - } $else { + } $else $if field.typ is string { x.$(field.name) = 'hi' } }