diff --git a/cmd/tools/vast/vast.v b/cmd/tools/vast/vast.v index 32a79386b4..3a9807152a 100644 --- a/cmd/tools/vast/vast.v +++ b/cmd/tools/vast/vast.v @@ -626,6 +626,8 @@ fn (t Tree) struct_decl(node ast.StructDecl) &Node { obj.add_terse('attrs', t.array_node_attr(node.attrs)) obj.add('end_comments', t.array_node_comment(node.end_comments)) obj.add_terse('embeds', t.array_node_embed(node.embeds)) + obj.add('is_implements', t.bool_node(node.is_implements)) + obj.add_terse('implements_types', t.array_node_type_expr(node.implements_types)) return obj } diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 4f9326b0a1..59faaffc48 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -418,7 +418,7 @@ pub: embeds []Embed is_implements bool - implements_types []Type + implements_types []TypeNode pub mut: fields []StructField } diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index 18e228ff57..8180b7e2ae 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -952,6 +952,11 @@ pub fn (t &Struct) is_empty_struct() bool { return t.fields.len == 0 && t.embeds.len == 0 } +@[inline] +pub fn (t &Struct) is_unresolved_generic() bool { + return t.generic_types.len > 0 && t.concrete_types.len == 0 +} + pub fn (t &TypeSymbol) is_array_fixed() bool { if t.info is ArrayFixed { return true diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v index bbb9d647d8..d1ca514c0e 100644 --- a/vlib/v/checker/struct.v +++ b/vlib/v/checker/struct.v @@ -334,7 +334,24 @@ fn (mut c Checker) struct_decl(mut node ast.StructDecl) { // cgen error if I use `println(sym)` without handling the option with `or{}` struct_type := c.table.find_type_idx(node.name) // or { panic(err) } for t in node.implements_types { - c.type_implements(struct_type, t, node.pos) + t_sym := c.table.sym(t.typ) + if t_sym.info is ast.Interface { + if t_sym.info.is_generic { + itype_name := c.table.type_to_str(t.typ) + if !itype_name.contains('[') { + c.error('missing generic type on ${t_sym.name}', t.pos) + } + if itype_name.contains('<') { + struct_generic_letters := node.generic_types.map(c.table.type_to_str(it)) + unknown_letters := itype_name.all_after('<').all_before('>').split(',').filter(it !in struct_generic_letters) + if unknown_letters.len > 0 { + c.error('unknown generic type ${unknown_letters.first()}', + t.pos) + } + } + } + } + c.type_implements(struct_type, t.typ, node.pos) } } } diff --git a/vlib/v/checker/tests/implements_generic_err.out b/vlib/v/checker/tests/implements_generic_err.out new file mode 100644 index 0000000000..af4c172d45 --- /dev/null +++ b/vlib/v/checker/tests/implements_generic_err.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/implements_generic_err.vv:12:31: error: missing generic type on IB + 10 | } + 11 | + 12 | struct Foo1[T] implements IA, IB { + | ~~ + 13 | a int + 14 | b T +vlib/v/checker/tests/implements_generic_err.vv:17:31: error: unknown generic type Y + 15 | } + 16 | + 17 | struct Foo2[T] implements IA, IB[Y] { + | ~~ + 18 | a int + 19 | b T +vlib/v/checker/tests/implements_generic_err.vv:22:31: error: unknown generic type Y + 20 | } + 21 | + 22 | struct Foo3[T] implements IA, IB[T,Y] { + | ~~ + 23 | a int + 24 | b T diff --git a/vlib/v/checker/tests/implements_generic_err.vv b/vlib/v/checker/tests/implements_generic_err.vv new file mode 100644 index 0000000000..61060289e6 --- /dev/null +++ b/vlib/v/checker/tests/implements_generic_err.vv @@ -0,0 +1,37 @@ +interface IA { + a int + fa() +} + +interface IB[T] { + a int + b T + fa() +} + +struct Foo1[T] implements IA, IB { + a int + b T +} + +struct Foo2[T] implements IA, IB[Y] { + a int + b T +} + +struct Foo3[T] implements IA, IB[T,Y] { + a int + b T +} + +fn (foo Foo1[T]) fa() { + println(foo.b) +} + +fn (foo Foo2[T]) fa() { + println(foo.b) +} + +fn (foo Foo3[T]) fa() { + println(foo.b) +} \ No newline at end of file diff --git a/vlib/v/fmt/struct.v b/vlib/v/fmt/struct.v index bc4cfb1650..8355b5d23a 100644 --- a/vlib/v/fmt/struct.v +++ b/vlib/v/fmt/struct.v @@ -34,11 +34,11 @@ pub fn (mut f Fmt) struct_decl(node ast.StructDecl, is_anon bool) { if node.is_implements { f.write(' implements ') for i, t in node.implements_types { - f.write(f.table.type_to_str_using_aliases(t, f.mod2alias)) + f.write(f.table.type_to_str_using_aliases(t.typ, f.mod2alias)) if i < node.implements_types.len - 1 { f.write(', ') } - f.mark_types_import_as_used(t) + f.mark_types_import_as_used(t.typ) } } // Calculate the alignments first diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 29e62ff727..0e539337c8 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -1055,6 +1055,9 @@ pub fn (mut g Gen) write_typeof_functions() { g.writeln('static char * v_typeof_interface_${sym.cname}(int sidx) { /* ${sym.name} */ ') for t in inter_info.types { sub_sym := g.table.sym(ast.mktyp(t)) + if sub_sym.info is ast.Struct && sub_sym.info.is_unresolved_generic() { + continue + } g.writeln('\tif (sidx == _${sym.cname}_${sub_sym.cname}_index) return "${util.strip_main_name(sub_sym.name)}";') } g.writeln('\treturn "unknown ${util.strip_main_name(sym.name)}";') @@ -1063,6 +1066,9 @@ pub fn (mut g Gen) write_typeof_functions() { g.writeln('static int v_typeof_interface_idx_${sym.cname}(int sidx) { /* ${sym.name} */ ') for t in inter_info.types { sub_sym := g.table.sym(ast.mktyp(t)) + if sub_sym.info is ast.Struct && sub_sym.info.is_unresolved_generic() { + continue + } g.writeln('\tif (sidx == _${sym.cname}_${sub_sym.cname}_index) return ${int(t.set_nr_muls(0))};') } g.writeln('\treturn ${int(ityp)};') @@ -7688,6 +7694,10 @@ fn (mut g Gen) interface_table() string { iinidx_minimum_base := 1000 // Note: NOT 0, to avoid map entries set to 0 later, so `if already_generated_mwrappers[name] > 0 {` works. mut current_iinidx := iinidx_minimum_base for st in inter_info.types { + st_sym_info := g.table.sym(st) + if st_sym_info.info is ast.Struct && st_sym_info.info.is_unresolved_generic() { + continue + } st_sym := g.table.sym(ast.mktyp(st)) // cctype is the Cleaned Concrete Type name, *without ptr*, // i.e. cctype is always just Cat, not Cat_ptr: diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index a2f85d2b35..1d6ef1f152 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -94,7 +94,7 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { mut is_field_pub := false mut is_field_global := false mut is_implements := false - mut implements_types := []ast.Type{cap: 3} // ast.void_type + mut implements_types := []ast.TypeNode{cap: 3} // ast.void_type mut last_line := p.prev_tok.pos().line_nr + 1 mut end_comments := []ast.Comment{} if !no_body { @@ -102,7 +102,11 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { is_implements = true for { p.next() - implements_types << p.parse_type() + type_pos := p.tok.pos() + implements_types << ast.TypeNode{ + typ: p.parse_type() + pos: type_pos + } if p.tok.kind != .comma { break } diff --git a/vlib/v/tests/interfaces/interface_generic_test.v b/vlib/v/tests/interfaces/interface_generic_test.v new file mode 100644 index 0000000000..51f6563a02 --- /dev/null +++ b/vlib/v/tests/interfaces/interface_generic_test.v @@ -0,0 +1,27 @@ +interface IA { + a int + fa() +} + +interface IB[T] { + a int + b T + fa() +} + +struct Foo[T] implements IA, IB[T] { + a int + b T +} + +fn (foo Foo[T]) fa() { + println(foo.b) +} + +fn test_main() { + foo := Foo[u8]{ + b: 16 + } + foo.fa() + assert true +}