From b71c131678c56adaf3feb0cff896176326cdd043 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Tue, 14 Mar 2023 08:49:29 -0300 Subject: [PATCH] cgen: fix map with comp-time reflection, improve comptime var handling (#17603) --- cmd/tools/vast/vast.v | 2 +- vlib/v/ast/ast.v | 16 ++- vlib/v/ast/scope.v | 7 ++ vlib/v/checker/assign.v | 6 +- vlib/v/checker/checker.v | 12 +- vlib/v/checker/comptime.v | 36 ++++++ vlib/v/checker/for.v | 25 ++++ vlib/v/gen/c/assign.v | 5 +- vlib/v/gen/c/cgen.v | 43 +++---- vlib/v/gen/c/comptime.v | 59 +++++++-- vlib/v/gen/c/dumpexpr.v | 4 +- vlib/v/gen/c/fn.v | 26 ++-- vlib/v/gen/c/for.v | 33 ++++- vlib/v/tests/comptime_map_test.v | 203 +++++++++++++++++++++++++++++++ 14 files changed, 407 insertions(+), 70 deletions(-) create mode 100644 vlib/v/tests/comptime_map_test.v diff --git a/cmd/tools/vast/vast.v b/cmd/tools/vast/vast.v index 0c02f36ef0..28e4c642a9 100644 --- a/cmd/tools/vast/vast.v +++ b/cmd/tools/vast/vast.v @@ -872,7 +872,7 @@ fn (t Tree) var(node ast.Var) &Node { obj.add_terse('is_mut', t.bool_node(node.is_mut)) obj.add('is_used', t.bool_node(node.is_used)) obj.add('is_changed', t.bool_node(node.is_changed)) - obj.add('is_comptime_field', t.bool_node(node.is_comptime_field)) + obj.add_terse('ct_type_var', t.enum_node(node.ct_type_var)) obj.add('is_or', t.bool_node(node.is_or)) obj.add('is_tmp', t.bool_node(node.is_tmp)) obj.add('is_autofree_tmp', t.bool_node(node.is_autofree_tmp)) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 076cb353b1..c52e62e772 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -649,6 +649,13 @@ pub mut: types []Type } +pub enum ComptimeVarKind { + no_comptime // it is not a comptime var + key_var // map key from `for k,v in t.$(field.name)` + value_var // map value from `for k,v in t.$(field.name)` + field_var // comptime field var `a := t.$(field.name)` +} + [minify] pub struct Var { pub: @@ -669,11 +676,10 @@ pub mut: // 10 <- original type (orig_type) // [11, 12, 13] <- cast order (smartcasts) // 12 <- the current casted type (typ) - pos token.Pos - is_used bool // whether the local variable was used in other expressions - is_changed bool // to detect mutable vars that are never changed - is_comptime_field bool // comptime field var `a := t.$(field.name)` - // + pos token.Pos + is_used bool // whether the local variable was used in other expressions + is_changed bool // to detect mutable vars that are never changed + ct_type_var ComptimeVarKind // comptime variable type // (for setting the position after the or block for autofree) is_or bool // `x := foo() or { ... }` is_tmp bool // for tmp for loop vars, so that autofree can skip them diff --git a/vlib/v/ast/scope.v b/vlib/v/ast/scope.v index 317b3ea4ca..b83910e4b7 100644 --- a/vlib/v/ast/scope.v +++ b/vlib/v/ast/scope.v @@ -122,6 +122,13 @@ pub fn (mut s Scope) update_var_type(name string, typ Type) { } } +pub fn (mut s Scope) update_ct_var_kind(name string, kind ComptimeVarKind) { + mut obj := unsafe { s.objects[name] } + if mut obj is Var { + obj.ct_type_var = kind + } +} + // selector_expr: name.field_name pub fn (mut s Scope) register_struct_field(name string, field ScopeStructField) { if f := s.struct_fields[name] { diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index e5fdc79777..b1c0aadd1f 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -318,14 +318,14 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { } } if right is ast.ComptimeSelector { - left.obj.is_comptime_field = true + left.obj.ct_type_var = .field_var left.obj.typ = c.comptime_fields_default_type } else if right is ast.Ident && (right as ast.Ident).obj is ast.Var && (right as ast.Ident).or_expr.kind == .absent { - left.obj.is_comptime_field = ((right as ast.Ident).obj as ast.Var).is_comptime_field + left.obj.ct_type_var = ((right as ast.Ident).obj as ast.Var).ct_type_var } else if right is ast.DumpExpr && (right as ast.DumpExpr).expr is ast.ComptimeSelector { - left.obj.is_comptime_field = true + left.obj.ct_type_var = .field_var left.obj.typ = c.comptime_fields_default_type } } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 2806d3aa71..4548bb5a71 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -98,6 +98,8 @@ mut: for_in_any_val_type ast.Type comptime_for_field_var string comptime_fields_default_type ast.Type + comptime_fields_key_type ast.Type // key type on `$for k, v in val.$(field.name)` + comptime_fields_val_type ast.Type // value type on `$for k, v in val.$(field.name)` comptime_fields_type map[string]ast.Type comptime_for_field_value ast.StructField // value of the field variable comptime_enum_field_value string // current enum value name @@ -2480,8 +2482,12 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { c.expected_type = ast.string_type node.expr_type = c.expr(node.expr) - if node.expr is ast.Ident && c.is_comptime_var(node.expr) { - node.expr_type = c.comptime_fields_default_type + if c.inside_comptime_for_field && node.expr is ast.Ident { + if c.is_comptime_var(node.expr) { + node.expr_type = c.get_comptime_var_type(node.expr as ast.Ident) + } else if (node.expr as ast.Ident).name in c.comptime_fields_type { + node.expr_type = c.comptime_fields_type[(node.expr as ast.Ident).name] + } } c.check_expr_opt_call(node.expr, node.expr_type) etidx := node.expr_type.idx() @@ -3656,7 +3662,7 @@ fn (c &Checker) has_return(stmts []ast.Stmt) ?bool { pub fn (mut c Checker) is_comptime_var(node ast.Expr) bool { return c.inside_comptime_for_field && node is ast.Ident - && (node as ast.Ident).info is ast.IdentVar && (node as ast.Ident).kind == .variable && ((node as ast.Ident).obj as ast.Var).is_comptime_field + && (node as ast.Ident).info is ast.IdentVar && (node as ast.Ident).kind == .variable && ((node as ast.Ident).obj as ast.Var).ct_type_var != .no_comptime } fn (mut c Checker) mark_as_referenced(mut node ast.Expr, as_interface bool) { diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index a2000624aa..73e0f5377e 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -10,6 +10,23 @@ import v.util import v.pkgconfig import v.checker.constants +[inline] +fn (mut c Checker) get_comptime_var_type(node ast.Expr) ast.Type { + if node is ast.Ident && (node as ast.Ident).obj is ast.Var { + return match (node.obj as ast.Var).ct_type_var { + .key_var { c.comptime_fields_key_type } + .value_var { c.comptime_fields_val_type } + .field_var { c.comptime_fields_default_type } + else { ast.void_type } + } + } else if node is ast.ComptimeSelector { + return c.get_comptime_selector_type(node, ast.void_type) + } else if node is ast.SelectorExpr && c.is_comptime_selector_type(node as ast.SelectorExpr) { + return c.comptime_fields_default_type + } + return ast.void_type +} + fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type { if node.left !is ast.EmptyExpr { node.left_type = c.expr(node.left) @@ -606,6 +623,16 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Pos) ComptimeBran } else if cond.left in [ast.Ident, ast.SelectorExpr, ast.TypeNode] { // `$if method.@type is string` c.expr(cond.left) + if cond.left is ast.SelectorExpr + && c.is_comptime_selector_type(cond.left as ast.SelectorExpr) + && cond.right is ast.ComptimeType { + checked_type := c.get_comptime_var_type(cond.left) + return if c.table.is_comptime_type(checked_type, cond.right) { + .eval + } else { + .skip + } + } return .unknown } else { c.error('invalid `\$if` condition: expected a type or a selector expression or an interface check', @@ -818,6 +845,15 @@ fn (mut c Checker) get_comptime_selector_type(node ast.ComptimeSelector, default return default_type } +// check_comptime_is_field_selector checks if the SelectorExpr is related to $for variable +[inline] +fn (mut c Checker) is_comptime_selector_type(node ast.SelectorExpr) bool { + if c.inside_comptime_for_field && node.expr is ast.Ident { + return (node.expr as ast.Ident).name == c.comptime_for_field_var && node.field_name == 'typ' + } + return false +} + // check_comptime_is_field_selector checks if the SelectorExpr is related to $for variable [inline] fn (mut c Checker) check_comptime_is_field_selector(node ast.SelectorExpr) bool { diff --git a/vlib/v/checker/for.v b/vlib/v/checker/for.v index 251a068ba8..eca12a3dea 100644 --- a/vlib/v/checker/for.v +++ b/vlib/v/checker/for.v @@ -81,6 +81,12 @@ fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) { node.high_type = high_type node.scope.update_var_type(node.val_var, node.val_type) } else { + mut is_comptime := false + if (node.cond is ast.Ident && c.is_comptime_var(node.cond)) + || node.cond is ast.ComptimeSelector { + is_comptime = true + typ = c.unwrap_generic(c.comptime_fields_default_type) + } mut sym := c.table.final_sym(typ) if sym.kind != .string { match mut node.cond { @@ -147,11 +153,21 @@ fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) { } node.key_type = key_type node.scope.update_var_type(node.key_var, key_type) + + if is_comptime { + c.comptime_fields_key_type = key_type + node.scope.update_ct_var_kind(node.key_var, .key_var) + } } value_type := c.table.value_type(unwrapped_typ) node.scope.update_var_type(node.val_var, value_type) + if is_comptime { + c.comptime_fields_val_type = value_type + node.scope.update_ct_var_kind(node.val_var, .value_var) + } + c.inside_for_in_any_cond = true c.for_in_any_val_type = value_type } else { @@ -167,6 +183,11 @@ fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) { } node.key_type = key_type node.scope.update_var_type(node.key_var, key_type) + + if is_comptime { + c.comptime_fields_key_type = key_type + node.scope.update_ct_var_kind(node.key_var, .key_var) + } } mut value_type := c.table.value_type(typ) if sym.kind == .string { @@ -216,6 +237,10 @@ fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) { node.kind = sym.kind node.val_type = value_type node.scope.update_var_type(node.val_var, value_type) + if is_comptime { + c.comptime_fields_val_type = value_type + node.scope.update_ct_var_kind(node.val_var, .value_var) + } } } c.check_loop_label(node.label, node.pos) diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 5c0fef11a5..78e16ea993 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -222,9 +222,8 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } } if mut left.obj is ast.Var { - if val is ast.Ident - && (val as ast.Ident).info is ast.IdentVar && (val as ast.Ident).kind == .variable && (val as ast.Ident).obj is ast.Var && ((val as ast.Ident).obj as ast.Var).is_comptime_field { - var_type = g.unwrap_generic(g.comptime_for_field_type) + if val is ast.Ident && g.is_comptime_var(val) { + var_type = g.unwrap_generic(g.get_comptime_var_type(val)) val_type = var_type gen_or = val.or_expr.kind != .absent if gen_or { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index cf118456fc..a77bf5dc37 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -199,8 +199,10 @@ mut: comptime_for_method_var string // $for method in T.methods {}; the variable name comptime_for_field_var string // $for field in T.fields {}; the variable name comptime_for_field_value ast.StructField // value of the field variable - comptime_enum_field_value string // value of enum name - comptime_for_field_type ast.Type // type of the field variable inferred from `$if field.typ is T {}` + comptime_for_field_type ast.Type // type of the field variable inferred from `$if field.typ is T {}` + comptime_for_field_key_type ast.Type // type of key on comptime for on map field + comptime_for_field_val_type ast.Type // type of value on comptime for on map field + comptime_enum_field_value string // value of enum name comptime_var_type_map map[string]ast.Type comptime_values_stack []CurrentComptimeValues // stores the values from the above on each $for loop, to make nesting them easier prevent_sum_type_unwrapping_once bool // needed for assign new values to sum type @@ -3168,7 +3170,7 @@ fn (mut g Gen) expr(node_ ast.Expr) { } } ast.IsRefType { - typ := g.get_type(node.typ) + typ := g.resolve_comptime_type(node.expr, g.get_type(node.typ)) node_typ := g.unwrap_generic(typ) sym := g.table.sym(node_typ) if sym.language == .v && sym.kind in [.placeholder, .any] { @@ -3376,7 +3378,7 @@ fn (mut g Gen) type_name(raw_type ast.Type) { } fn (mut g Gen) typeof_expr(node ast.TypeOf) { - typ := g.get_type(node.typ) + typ := g.resolve_comptime_type(node.expr, g.get_type(node.typ)) sym := g.table.sym(typ) if sym.kind == .sum_type { // When encountering a .sum_type, typeof() should be done at runtime, @@ -3399,20 +3401,6 @@ fn (mut g Gen) typeof_expr(node ast.TypeOf) { } } -fn (mut g Gen) comptime_typeof(node ast.TypeOf, default_type ast.Type) ast.Type { - if node.expr is ast.ComptimeSelector { - key_str := g.get_comptime_selector_key_type(node.expr) - if key_str != '' { - return g.comptime_var_type_map[key_str] or { default_type } - } - } else if g.inside_comptime_for_field && node.expr is ast.Ident - && (node.expr as ast.Ident).obj is ast.Var && ((node.expr as ast.Ident).obj as ast.Var).is_comptime_field == true { - // typeof(var) from T.fields - return g.comptime_for_field_type - } - return default_type -} - fn (mut g Gen) selector_expr(node ast.SelectorExpr) { prevent_sum_type_unwrapping_once := g.prevent_sum_type_unwrapping_once g.prevent_sum_type_unwrapping_once = false @@ -3432,14 +3420,14 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { // typeof(expr).name mut name_type := node.name_type if node.expr is ast.TypeOf { - name_type = g.comptime_typeof(node.expr, name_type) + name_type = g.resolve_comptime_type(node.expr.expr, name_type) } g.type_name(name_type) return } else if node.field_name == 'idx' { mut name_type := node.name_type if node.expr is ast.TypeOf { - name_type = g.comptime_typeof(node.expr, name_type) + name_type = g.resolve_comptime_type(node.expr.expr, name_type) } // `typeof(expr).idx` g.write(int(g.unwrap_generic(name_type)).str()) @@ -4076,7 +4064,7 @@ fn (mut g Gen) select_expr(node ast.SelectExpr) { pub fn (mut g Gen) is_comptime_var(node ast.Expr) bool { return g.inside_comptime_for_field && node is ast.Ident - && (node as ast.Ident).info is ast.IdentVar && ((node as ast.Ident).obj as ast.Var).is_comptime_field + && (node as ast.Ident).info is ast.IdentVar && ((node as ast.Ident).obj as ast.Var).ct_type_var != .no_comptime } fn (mut g Gen) ident(node ast.Ident) { @@ -4109,13 +4097,14 @@ fn (mut g Gen) ident(node ast.Ident) { mut is_auto_heap := false if node.info is ast.IdentVar { if node.obj is ast.Var { - if !g.is_assign_lhs && node.obj.is_comptime_field { - if g.comptime_for_field_type.has_flag(.option) { + if !g.is_assign_lhs && node.obj.ct_type_var != .no_comptime { + comptime_type := g.get_comptime_var_type(node) + if comptime_type.has_flag(.option) { if (g.inside_opt_or_res || g.left_is_opt) && node.or_expr.kind == .absent { g.write('${name}') } else { g.write('/*opt*/') - styp := g.base_type(g.comptime_for_field_type) + styp := g.base_type(comptime_type) g.write('(*(${styp}*)${name}.data)') } } else { @@ -4125,7 +4114,7 @@ fn (mut g Gen) ident(node ast.Ident) { && !g.is_assign_lhs) { stmt_str := g.go_before_stmt(0).trim_space() g.empty_line = true - g.or_block(name, node.or_expr, g.comptime_for_field_type) + g.or_block(name, node.or_expr, comptime_type) g.writeln(stmt_str) } return @@ -4234,7 +4223,7 @@ fn (mut g Gen) cast_expr(node ast.CastExpr) { mut expr_type := node.expr_type sym := g.table.sym(node_typ) if node.expr is ast.Ident && g.is_comptime_var(node.expr) { - expr_type = g.unwrap_generic(g.comptime_for_field_type) + expr_type = g.unwrap_generic(g.get_comptime_var_type(node.expr)) } if sym.kind in [.sum_type, .interface_] { if node.typ.has_flag(.option) && node.expr is ast.None { @@ -6161,7 +6150,7 @@ fn (mut g Gen) get_type(typ ast.Type) ast.Type { } fn (mut g Gen) size_of(node ast.SizeOf) { - typ := g.get_type(node.typ) + typ := g.resolve_comptime_type(node.expr, g.get_type(node.typ)) node_typ := g.unwrap_generic(typ) sym := g.table.sym(node_typ) if sym.language == .v && sym.kind in [.placeholder, .any] { diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 5ae45e748f..67941909ca 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -647,17 +647,29 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) { // struct CurrentComptimeValues { - inside_comptime_for_field bool - comptime_for_method string - comptime_for_method_var string - comptime_for_field_var string - comptime_for_field_value ast.StructField - comptime_for_field_type ast.Type - comptime_var_type_map map[string]ast.Type + inside_comptime_for_field bool + comptime_for_method string + comptime_for_method_var string + comptime_for_field_var string + comptime_for_field_value ast.StructField + comptime_for_field_type ast.Type + comptime_for_field_key_type ast.Type + comptime_for_field_val_type ast.Type + comptime_var_type_map map[string]ast.Type } fn (mut g Gen) push_existing_comptime_values() { - g.comptime_values_stack << CurrentComptimeValues{g.inside_comptime_for_field, g.comptime_for_method, g.comptime_for_method_var, g.comptime_for_field_var, g.comptime_for_field_value, g.comptime_for_field_type, g.comptime_var_type_map.clone()} + g.comptime_values_stack << CurrentComptimeValues{ + inside_comptime_for_field: g.inside_comptime_for_field + comptime_for_method: g.comptime_for_method + comptime_for_method_var: g.comptime_for_method_var + comptime_for_field_var: g.comptime_for_field_var + comptime_for_field_value: g.comptime_for_field_value + comptime_for_field_type: g.comptime_for_field_type + comptime_for_field_key_type: g.comptime_for_field_key_type + comptime_for_field_val_type: g.comptime_for_field_val_type + comptime_var_type_map: g.comptime_var_type_map.clone() + } } fn (mut g Gen) pop_existing_comptime_values() { @@ -668,9 +680,40 @@ fn (mut g Gen) pop_existing_comptime_values() { g.comptime_for_field_var = old.comptime_for_field_var g.comptime_for_field_value = old.comptime_for_field_value g.comptime_for_field_type = old.comptime_for_field_type + g.comptime_for_field_key_type = old.comptime_for_field_key_type + g.comptime_for_field_val_type = old.comptime_for_field_val_type g.comptime_var_type_map = old.comptime_var_type_map.clone() } +[inline] +fn (mut g Gen) get_comptime_var_type_from_kind(kind ast.ComptimeVarKind) ast.Type { + return match kind { + .key_var { g.comptime_for_field_key_type } + .value_var { g.comptime_for_field_val_type } + .field_var { g.comptime_for_field_type } + else { ast.void_type } + } +} + +fn (mut g Gen) get_comptime_var_type(node ast.Expr) ast.Type { + if node is ast.Ident && (node as ast.Ident).obj is ast.Var { + return g.get_comptime_var_type_from_kind((node.obj as ast.Var).ct_type_var) + } else if node is ast.ComptimeSelector { + key_str := g.get_comptime_selector_key_type(node) + if key_str != '' { + return g.comptime_var_type_map[key_str] or { ast.void_type } + } + } + return ast.void_type +} + +fn (mut g Gen) resolve_comptime_type(node ast.Expr, default_type ast.Type) ast.Type { + if (node is ast.Ident && g.is_comptime_var(node)) || node is ast.ComptimeSelector { + return g.get_comptime_var_type(node) + } + return default_type +} + // fn (mut g Gen) comptime_for(node ast.ComptimeFor) { diff --git a/vlib/v/gen/c/dumpexpr.v b/vlib/v/gen/c/dumpexpr.v index 37d9aad0e1..31fc7396f9 100644 --- a/vlib/v/gen/c/dumpexpr.v +++ b/vlib/v/gen/c/dumpexpr.v @@ -37,8 +37,8 @@ fn (mut g Gen) dump_expr(node ast.DumpExpr) { } } } - } else if node.expr is ast.Ident && g.is_comptime_var(node.expr) { - expr_type = g.comptime_for_field_type + } else if node.expr is ast.Ident && g.inside_comptime_for_field && g.is_comptime_var(node.expr) { + expr_type = g.get_comptime_var_type(node.expr) name = g.typ(g.unwrap_generic(expr_type.clear_flag(.shared_f).clear_flag(.result))).replace('*', '') } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 5367fe0d1b..939689a805 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -883,8 +883,8 @@ fn (mut g Gen) gen_to_str_method_call(node ast.CallExpr) bool { } } else if left_node is ast.Ident { if left_node.obj is ast.Var { - if left_node.obj.is_comptime_field { - rec_type = g.comptime_for_field_type + if left_node.obj.ct_type_var != .no_comptime { + rec_type = g.get_comptime_var_type(left_node) g.gen_expr_to_string(left_node, rec_type) return true } else if g.comptime_var_type_map.len > 0 { @@ -912,18 +912,18 @@ fn (mut g Gen) gen_to_str_method_call(node ast.CallExpr) bool { return false } -fn (mut g Gen) change_comptime_args(mut node_ ast.CallExpr) []int { - mut comptime_args := []int{} +fn (mut g Gen) change_comptime_args(mut node_ ast.CallExpr) map[int]ast.Type { + mut comptime_args := map[int]ast.Type{} 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 - if call_arg.expr.obj.is_comptime_field { - comptime_args << i + if call_arg.expr.obj.ct_type_var != .no_comptime { + comptime_args[i] = g.get_comptime_var_type_from_kind(call_arg.expr.obj.ct_type_var) } } } else if mut call_arg.expr is ast.ComptimeSelector { - comptime_args << i + comptime_args[i] = g.comptime_for_field_type } } return comptime_args @@ -940,7 +940,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) { left_type := g.unwrap_generic(node.left_type) mut unwrapped_rec_type := node.receiver_type mut for_in_any_var_type := ast.void_type - mut comptime_args := []int{} + mut comptime_args := map[int]ast.Type{} if g.cur_fn != unsafe { nil } && g.cur_fn.generic_names.len > 0 { // in generic fn unwrapped_rec_type = g.unwrap_generic(node.receiver_type) } else { // in non-generic fn @@ -1151,13 +1151,13 @@ fn (mut g Gen) method_call(node ast.CallExpr) { mut concrete_types := node.concrete_types.map(g.unwrap_generic(it)) arg_sym := g.table.sym(g.comptime_for_field_type) if m := g.table.find_method(g.table.sym(node.left_type), node.name) { - for k in comptime_args { + for k, v in comptime_args { if m.generic_names.len > 0 && arg_sym.kind == .array && m.params[k + 1].typ.has_flag(.generic) && g.table.final_sym(m.params[k + 1].typ).kind == .array { concrete_types[k] = (arg_sym.info as ast.Array).elem_type } else { - concrete_types[k] = g.comptime_for_field_type + concrete_types[k] = v } } } @@ -1288,7 +1288,7 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { // will be `0` for `foo()` mut is_interface_call := false mut is_selector_call := false - mut comptime_args := []int{} + mut comptime_args := map[int]ast.Type{} if node.left_type != 0 { left_sym := g.table.sym(node.left_type) if left_sym.kind == .interface_ { @@ -1408,12 +1408,12 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { && comptime_args.len > 0 { mut concrete_types := node.concrete_types.map(g.unwrap_generic(it)) arg_sym := g.table.sym(g.comptime_for_field_type) - for k in comptime_args { + for k, v in comptime_args { if arg_sym.kind == .array && func.params[k].typ.has_flag(.generic) && g.table.sym(func.params[k].typ).kind == .array { concrete_types[k] = (arg_sym.info as ast.Array).elem_type } else { - concrete_types[k] = g.comptime_for_field_type + concrete_types[k] = v } } name = g.generic_fn_name(concrete_types, name) diff --git a/vlib/v/gen/c/for.v b/vlib/v/gen/c/for.v index a19ba8ce45..fe1f3badbe 100644 --- a/vlib/v/gen/c/for.v +++ b/vlib/v/gen/c/for.v @@ -130,17 +130,40 @@ fn (mut g Gen) for_stmt(node ast.ForStmt) { fn (mut g Gen) for_in_stmt(node_ ast.ForInStmt) { mut node := unsafe { node_ } - if node.kind == .any { + mut is_comptime := false + + if (node.cond is ast.Ident && g.is_comptime_var(node.cond)) || node.cond is ast.ComptimeSelector { + is_comptime = true + mut unwrapped_typ := g.unwrap_generic(g.comptime_for_field_type) + mut unwrapped_sym := g.table.sym(unwrapped_typ) + node.cond_type = unwrapped_typ + node.val_type = g.table.value_type(unwrapped_typ) + node.scope.update_var_type(node.val_var, node.val_type) + node.kind = unwrapped_sym.kind + + g.comptime_for_field_val_type = node.val_type + node.scope.update_ct_var_kind(node.val_var, .value_var) + + if node.key_var.len > 0 { + key_type := match unwrapped_sym.kind { + .map { unwrapped_sym.map_info().key_type } + else { ast.int_type } + } + node.key_type = key_type + node.scope.update_var_type(node.key_var, key_type) + + g.comptime_for_field_key_type = node.key_type + node.scope.update_ct_var_kind(node.key_var, .key_var) + } + } + + if node.kind == .any && !is_comptime { g.inside_for_in_any_cond = true mut unwrapped_typ := g.unwrap_generic(node.cond_type) mut unwrapped_sym := g.table.sym(unwrapped_typ) node.kind = unwrapped_sym.kind node.cond_type = unwrapped_typ if node.key_var.len > 0 { - if g.is_comptime_var(node.cond) { - unwrapped_typ = g.unwrap_generic(g.comptime_for_field_type) - unwrapped_sym = g.table.sym(unwrapped_typ) - } key_type := match unwrapped_sym.kind { .map { unwrapped_sym.map_info().key_type } else { ast.int_type } diff --git a/vlib/v/tests/comptime_map_test.v b/vlib/v/tests/comptime_map_test.v new file mode 100644 index 0000000000..500d74b01c --- /dev/null +++ b/vlib/v/tests/comptime_map_test.v @@ -0,0 +1,203 @@ +struct StructType { +mut: + val string = 'string from StructType' +} + +struct Data { +mut: + not_map string + empty_to_test map[string]string + users map[string]StructType + extra map[string]map[string]int +} + +fn test_comptimeselector_map_different_types() { + data := Data{ + users: { + 'a': StructType{} + } + extra: { + 'b': { + 'c': 10 + } + } + } + + mut keys := []string{} + mut vals := []string{} + + $for field in Data.fields { + $if field.typ is $Map { + for k, v in data.$(field.name) { + keys << k.str() + vals << v.str() + } + } + } + assert keys == ['a', 'b'] + assert vals[0] == "StructType{ + val: 'string from StructType' +}" + assert vals[1] == "{'c': 10}" +} + +fn test_comptime_var_map_different_types() { + data := Data{ + users: { + 'a': StructType{} + } + extra: { + 'b': { + 'c': 10 + } + } + } + + mut keys := []string{} + mut vals := []string{} + + $for field in Data.fields { + $if field.typ is $Map { + gg := data.$(field.name) + for k, v in gg { + keys << k.str() + vals << v.str() + } + } + } + assert keys == ['a', 'b'] + assert vals[0] == "StructType{ + val: 'string from StructType' +}" + assert vals[1] == "{'c': 10}" +} + +fn test_comptime_with_dump() { + data := Data{ + users: { + 'a': StructType{} + } + extra: { + 'b': { + 'c': 10 + } + } + } + + $for field in Data.fields { + $if field.typ is $Map { + for k, v in data.$(field.name) { + dump(k) + dump(v) + } + } + } + assert true +} + +fn test_comptime_dump_for_key_and_value() { + data := Data{ + users: { + 'a': StructType{} + } + extra: { + 'b': { + 'c': 10 + } + } + } + + mut key_types := []string{} + mut val_types := []string{} + + $for field in Data.fields { + $if field.typ is $Map { + for k, v in data.$(field.name) { + key_types << typeof(k).name + val_types << typeof(v).name + } + } + } + assert val_types[0] == 'StructType' + assert val_types[1] == 'map[string]int' + + assert key_types[0] == 'string' + assert key_types[1] == 'string' +} + +fn test_comptime_key_value_var() { + data := Data{ + users: { + 'a': StructType{} + } + extra: { + 'b': { + 'c': 10 + } + } + } + + mut keys := []string{} + mut vals := []string{} + + $for field in Data.fields { + $if field.typ is $Map { + for k, v in data.$(field.name) { + if k == 'a' { + keys << dump(k) + vals << dump(v.str()) + } + if k == 'b' { + keys << dump(k) + vals << dump(v.str()) + } + } + } + } + assert keys[0] == 'a' + assert keys[1] == 'b' + + assert vals[0] == "StructType{ + val: 'string from StructType' +}" + assert vals[1] == "{'c': 10}" +} + +fn test_comptime_generic_argument() { + data := Data{ + users: { + 'a': StructType{} + } + extra: { + 'b': { + 'c': 10 + } + } + } + + $for field in Data.fields { + $if field.typ is $Map { + for k, v in data.$(field.name) { + process_value_from_map(v) + assert k in ['a', 'b'] + } + } + } +} + +fn process_value_from_map[T](v T) { + $if T is $Map { + assert typeof(v).name == 'map[string]int' + assert v.str() == "{'c': 10}" + assert true + return + } $else $if T is $Struct { + assert typeof(v).name == 'StructType' + assert v.str() == "StructType{ + val: 'string from StructType' +}" + assert true + return + } + assert false +}