From 9be80198fc4281c5f2bf4322a6e5d19f7254a721 Mon Sep 17 00:00:00 2001 From: yuyi Date: Tue, 1 Aug 2023 02:30:12 +0800 Subject: [PATCH] checker: fix generic struct field with default fn_type value (fix #19011) (#19014) --- vlib/v/checker/check_types.v | 2 +- vlib/v/checker/checker.v | 109 ++++++++++-------- vlib/v/checker/struct.v | 23 +++- ...s_struct_field_with_default_fn_type_test.v | 30 +++++ 4 files changed, 113 insertions(+), 51 deletions(-) create mode 100644 vlib/v/tests/generics_struct_field_with_default_fn_type_test.v diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 39095b073d..d71455129a 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -173,7 +173,7 @@ fn (mut c Checker) check_types(got ast.Type, expected ast.Type) bool { return false } } - if expected.has_flag(.generic) { + if expected.has_flag(.generic) && !got.has_flag(.generic) { return false } return true diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 88d894bef9..46f128f9cf 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -39,46 +39,49 @@ pub const ( pub struct Checker { pref &pref.Preferences = unsafe { nil } // Preferences shared from V struct pub mut: - table &ast.Table = unsafe { nil } - file &ast.File = unsafe { nil } - nr_errors int - nr_warnings int - nr_notices int - errors []errors.Error - warnings []errors.Warning - notices []errors.Notice - error_lines []int // to avoid printing multiple errors for the same line - expected_type ast.Type - expected_or_type ast.Type // fn() or { 'this type' } eg. string. expected or block type - expected_expr_type ast.Type // if/match is_expr: expected_type - mod string // current module name - const_var &ast.ConstField = unsafe { nil } // the current constant, when checking const declarations - const_deps []string - const_names []string - global_names []string - locked_names []string // vars that are currently locked - rlocked_names []string // vars that are currently read-locked - in_for_count int // if checker is currently in a for loop - 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_just_builtin_mod bool // true only inside 'builtin' - 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_x_is_type bool // true inside the Type expression of `if x is Type {` - 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 - smartcast_mut_pos token.Pos // match mut foo, if mut foo is Foo - smartcast_cond_pos token.Pos // match cond - ct_cond_stack []ast.Expr + table &ast.Table = unsafe { nil } + file &ast.File = unsafe { nil } + nr_errors int + nr_warnings int + nr_notices int + errors []errors.Error + warnings []errors.Warning + notices []errors.Notice + error_lines []int // to avoid printing multiple errors for the same line + expected_type ast.Type + expected_or_type ast.Type // fn() or { 'this type' } eg. string. expected or block type + expected_expr_type ast.Type // if/match is_expr: expected_type + mod string // current module name + const_var &ast.ConstField = unsafe { nil } // the current constant, when checking const declarations + const_deps []string + const_names []string + global_names []string + locked_names []string // vars that are currently locked + rlocked_names []string // vars that are currently read-locked + in_for_count int // if checker is currently in a for loop + 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_just_builtin_mod bool // true only inside 'builtin' + 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_x_is_type bool // true inside the Type expression of `if x is Type {` + inside_comptime_for_field bool + inside_generic_struct_init bool + cur_struct_generic_types []ast.Type + cur_struct_concrete_types []ast.Type + 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 + smartcast_mut_pos token.Pos // match mut foo, if mut foo is Foo + smartcast_cond_pos token.Pos // match cond + 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` @@ -2438,11 +2441,19 @@ fn (mut c Checker) stmts_ending_with_expression(mut stmts []ast.Stmt) { } fn (mut c Checker) unwrap_generic(typ ast.Type) ast.Type { - if typ.has_flag(.generic) && c.table.cur_fn != unsafe { nil } { - if t_typ := c.table.resolve_generic_to_concrete(typ, c.table.cur_fn.generic_names, - c.table.cur_concrete_types) - { - return t_typ + if typ.has_flag(.generic) { + if c.inside_generic_struct_init { + generic_names := c.cur_struct_generic_types.map(c.table.sym(it).name) + if t_typ := c.table.resolve_generic_to_concrete(typ, generic_names, c.cur_struct_concrete_types) { + return t_typ + } + } + if c.table.cur_fn != unsafe { nil } { + if t_typ := c.table.resolve_generic_to_concrete(typ, c.table.cur_fn.generic_names, + c.table.cur_concrete_types) + { + return t_typ + } } } return typ @@ -3329,7 +3340,9 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { if func := c.table.find_fn(node.name) { if func.generic_names.len > 0 { concrete_types := node.concrete_types.map(c.unwrap_generic(it)) - c.table.register_fn_concrete_types(func.fkey(), concrete_types) + if concrete_types.all(!it.has_flag(.generic)) { + c.table.register_fn_concrete_types(func.fkey(), concrete_types) + } } } return info.typ @@ -3493,7 +3506,9 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { concrete_types) { fn_type = typ_ - c.table.register_fn_concrete_types(func.fkey(), concrete_types) + if concrete_types.all(!it.has_flag(.generic)) { + c.table.register_fn_concrete_types(func.fkey(), concrete_types) + } } } node.name = name diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v index a9acd2d3db..3b9204d005 100644 --- a/vlib/v/checker/struct.v +++ b/vlib/v/checker/struct.v @@ -155,6 +155,9 @@ fn (mut c Checker) struct_decl(mut node ast.StructDecl) { true) } } + } else if c.table.final_sym(field.typ).kind == .function + && field.default_expr_typ.is_pointer() { + continue } else { c.error('incompatible initializer for field `${field.name}`: ${err.msg()}', field.default_expr.pos()) @@ -352,6 +355,17 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini c.error('a non generic struct `${node.typ_str}` used like a generic struct', node.name_pos) } + if struct_sym.info.generic_types.len > 0 + && struct_sym.info.generic_types.len == struct_sym.info.concrete_types.len { + c.inside_generic_struct_init = true + c.cur_struct_generic_types = struct_sym.info.generic_types.clone() + c.cur_struct_concrete_types = struct_sym.info.concrete_types.clone() + defer { + c.inside_generic_struct_init = false + c.cur_struct_generic_types = [] + c.cur_struct_concrete_types = [] + } + } } else if struct_sym.info is ast.Alias { parent_sym := c.table.sym(struct_sym.info.parent_type) // e.g. ´x := MyMapAlias{}´, should be a cast to alias type ´x := MyMapAlias(map[...]...)´ @@ -613,10 +627,10 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini // and the second part is all fields embedded in the structure // If the return value data composition form in `c.table.struct_fields()` is modified, // need to modify here accordingly. - fields := c.table.struct_fields(type_sym) + mut fields := c.table.struct_fields(type_sym) mut checked_types := []ast.Type{} - for i, field in fields { + for i, mut field in fields { if field.name in inited_fields { continue } @@ -628,7 +642,7 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini } if field.has_default_expr { if i < info.fields.len && field.default_expr_typ == 0 { - if field.default_expr is ast.StructInit { + if mut field.default_expr is ast.StructInit { idx := c.table.find_type_idx(field.default_expr.typ_str) if idx != 0 { info.fields[i].default_expr_typ = ast.new_type(idx) @@ -637,6 +651,9 @@ fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_ini if field.typ.is_any_kind_of_pointer() { info.fields[i].default_expr_typ = field.typ } + } else if field.default_expr is ast.Ident + && field.default_expr.info is ast.IdentFn { + c.expr(mut field.default_expr) } else { if const_field := c.table.global_scope.find_const('${field.default_expr}') { info.fields[i].default_expr_typ = const_field.typ diff --git a/vlib/v/tests/generics_struct_field_with_default_fn_type_test.v b/vlib/v/tests/generics_struct_field_with_default_fn_type_test.v new file mode 100644 index 0000000000..16d3547bf7 --- /dev/null +++ b/vlib/v/tests/generics_struct_field_with_default_fn_type_test.v @@ -0,0 +1,30 @@ +[heap] +struct BloomFilter1[T] { + hash_func fn (T) u32 = unsafe { nil } // hash function, input [T] , output u32 + table_size int // every entry is one-bit, packed into `table` + num_functions int // 1~16 +} + +[heap] +struct BloomFilter2[T] { + // TODO V bug + hash_func fn (T) u32 = default_cb[T] // hash function, input [T] , output u32 + table_size int // every entry is one-bit, packed into `table` + num_functions int // 1~16 +mut: + table []u8 +} + +fn default_cb[T](x T) u32 { + return 22 +} + +fn test_generic_struct_field_with_default_fn_type() { + filter1 := BloomFilter1[int]{} + println(filter1) + assert true + + filter2 := BloomFilter2[int]{} + println(filter2.hash_func(11)) + assert filter2.hash_func(11) == 22 +}