v: initial support for generic interfaces and sumtypes (#10795)

pull/10808/head
spaceface 2021-07-15 07:29:13 +02:00 committed by GitHub
parent 7687b28d8d
commit 6e942bf4c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 755 additions and 160 deletions

View File

@ -264,15 +264,16 @@ pub:
pub struct InterfaceDecl {
pub:
name string
typ Type
name_pos token.Position
language Language
field_names []string
is_pub bool
mut_pos int // mut:
pos token.Position
pre_comments []Comment
name string
typ Type
name_pos token.Position
language Language
field_names []string
is_pub bool
mut_pos int // mut:
pos token.Position
pre_comments []Comment
generic_types []Type
pub mut:
methods []FnDecl
fields []StructField
@ -965,11 +966,12 @@ pub:
// New implementation of sum types
pub struct SumTypeDecl {
pub:
name string
is_pub bool
pos token.Position
comments []Comment
typ Type
name string
is_pub bool
pos token.Position
comments []Comment
typ Type
generic_types []Type
pub mut:
variants []TypeNode
}

View File

@ -70,7 +70,6 @@ pub struct Fn {
pub:
is_variadic bool
language Language
generic_names []string
is_pub bool
is_deprecated bool // `[deprecated] fn abc(){}`
is_noreturn bool // `[noreturn] fn abc(){}`
@ -90,6 +89,7 @@ pub mut:
source_fn voidptr // set in the checker, while processing fn declarations
usages int
//
generic_names []string
attrs []Attr // all fn attributes
is_conditional bool // true for `[if abc]fn(){}`
ctdefine_idx int // the index of the attribute, containing the compile time define [if mytag]
@ -1180,6 +1180,9 @@ pub fn (mut t Table) resolve_generic_to_concrete(generic_type Type, generic_name
return none
}
typ := concrete_types[index]
if typ == 0 {
return none
}
return typ.derive_add_muls(generic_type).clear_flag(.generic)
}
match mut sym.info {
@ -1273,7 +1276,7 @@ pub fn (mut t Table) resolve_generic_to_concrete(generic_type Type, generic_name
return new_type(idx).derive_add_muls(generic_type).clear_flag(.generic)
}
}
Struct {
Struct, Interface, SumType {
if sym.info.is_generic {
mut nrt := '$sym.name<'
for i in 0 .. sym.info.generic_types.len {

View File

@ -760,6 +760,11 @@ pub mut:
fields []StructField
methods []Fn
ifaces []Type
// generic interface support
is_generic bool
generic_types []Type
concrete_types []Type
parent_type Type
}
pub struct Enum {
@ -846,6 +851,11 @@ pub:
pub mut:
fields []StructField
found_fields bool
// generic sumtype support
is_generic bool
generic_types []Type
concrete_types []Type
parent_type Type
}
// human readable type name
@ -964,17 +974,21 @@ pub fn (t &Table) type_to_str_using_aliases(typ Type, import_aliases map[string]
}
res += ')'
}
.struct_ {
.struct_, .interface_, .sum_type {
if typ.has_flag(.generic) {
info := sym.info as Struct
res += '<'
for i, gtyp in info.generic_types {
res += t.get_type_symbol(gtyp).name
if i != info.generic_types.len - 1 {
res += ', '
match sym.info {
Struct, Interface, SumType {
res += '<'
for i, gtyp in sym.info.generic_types {
res += t.get_type_symbol(gtyp).name
if i != sym.info.generic_types.len - 1 {
res += ', '
}
}
res += '>'
}
else {}
}
res += '>'
} else {
res = t.shorten_user_defined_typenames(res, import_aliases)
}
@ -1004,7 +1018,7 @@ pub fn (t &Table) type_to_str_using_aliases(typ Type, import_aliases map[string]
res = 'thread ' + t.type_to_str_using_aliases(rtype, import_aliases)
}
}
.alias, .any, .sum_type, .interface_, .size_t, .aggregate, .placeholder, .enum_ {
.alias, .any, .size_t, .aggregate, .placeholder, .enum_ {
res = t.shorten_user_defined_typenames(res, import_aliases)
}
}
@ -1123,6 +1137,47 @@ pub fn (t &TypeSymbol) find_method(name string) ?Fn {
return none
}
pub fn (t &TypeSymbol) find_method_with_generic_parent(name string) ?Fn {
if m := t.find_method(name) {
return m
}
mut table := global_table
match t.info {
Struct, Interface, SumType {
if t.info.parent_type.has_flag(.generic) {
parent_sym := table.get_type_symbol(t.info.parent_type)
if x := parent_sym.find_method(name) {
match parent_sym.info {
Struct, Interface, SumType {
mut method := x
generic_names := parent_sym.info.generic_types.map(table.get_type_symbol(it).name)
if rt := table.resolve_generic_to_concrete(method.return_type,
generic_names, t.info.concrete_types)
{
method.return_type = rt
}
method.params = method.params.clone()
for mut param in method.params {
if pt := table.resolve_generic_to_concrete(param.typ,
generic_names, t.info.concrete_types)
{
param.typ = pt
}
}
method.generic_names.clear()
return method
}
else {}
}
} else {
}
}
}
else {}
}
return none
}
pub fn (t &TypeSymbol) str_method_info() (bool, bool, int) {
mut has_str_method := false
mut expects_ptr := false

View File

@ -80,7 +80,7 @@ pub fn (mut b Builder) front_stages(v_files []string) ? {
pub fn (mut b Builder) middle_stages() ? {
util.timing_start('CHECK')
b.checker.generic_struct_insts_to_concrete()
b.checker.generic_insts_to_concrete()
b.checker.check_files(b.parsed_files)
util.timing_measure('CHECK')
b.print_warnings_and_errors()

View File

@ -584,22 +584,24 @@ pub fn (mut c Checker) infer_fn_generic_types(f ast.Fn, mut call_expr ast.CallEx
// resolve generic struct receiver
if i == 0 && call_expr.is_method && param.typ.has_flag(.generic) {
sym := c.table.get_type_symbol(call_expr.receiver_type)
if sym.kind == .struct_ {
info := sym.info as ast.Struct
if c.table.cur_fn.generic_names.len > 0 { // in generic fn
if gt_name in c.table.cur_fn.generic_names
&& c.table.cur_fn.generic_names.len == c.table.cur_concrete_types.len {
idx := c.table.cur_fn.generic_names.index(gt_name)
typ = c.table.cur_concrete_types[idx]
}
} else { // in non-generic fn
receiver_generic_names := info.generic_types.map(c.table.get_type_symbol(it).name)
if gt_name in receiver_generic_names
&& info.generic_types.len == info.concrete_types.len {
idx := receiver_generic_names.index(gt_name)
typ = info.concrete_types[idx]
match sym.info {
ast.Struct, ast.Interface, ast.SumType {
if c.table.cur_fn.generic_names.len > 0 { // in generic fn
if gt_name in c.table.cur_fn.generic_names
&& c.table.cur_fn.generic_names.len == c.table.cur_concrete_types.len {
idx := c.table.cur_fn.generic_names.index(gt_name)
typ = c.table.cur_concrete_types[idx]
}
} else { // in non-generic fn
receiver_generic_names := sym.info.generic_types.map(c.table.get_type_symbol(it).name)
if gt_name in receiver_generic_names
&& sym.info.generic_types.len == sym.info.concrete_types.len {
idx := receiver_generic_names.index(gt_name)
typ = sym.info.concrete_types[idx]
}
}
}
else {}
}
}
arg_i := if i != 0 && call_expr.is_method { i - 1 } else { i }
@ -667,12 +669,20 @@ pub fn (mut c Checker) infer_fn_generic_types(f ast.Fn, mut call_expr ast.CallEx
}
} else if param.typ.has_flag(.variadic) {
to_set = c.table.mktyp(arg.typ)
} else if arg_sym.kind == .struct_ {
info := arg_sym.info as ast.Struct
generic_names := info.generic_types.map(c.table.get_type_symbol(it).name)
if gt_name in generic_names && info.generic_types.len == info.concrete_types.len {
} else if arg_sym.kind in [.struct_, .interface_, .sum_type] {
mut generic_types := []ast.Type{}
mut concrete_types := []ast.Type{}
match mut arg_sym.info {
ast.Struct, ast.Interface, ast.SumType {
generic_types = arg_sym.info.generic_types
concrete_types = arg_sym.info.concrete_types
}
else {}
}
generic_names := generic_types.map(c.table.get_type_symbol(it).name)
if gt_name in generic_names && generic_types.len == concrete_types.len {
idx := generic_names.index(gt_name)
typ = info.concrete_types[idx]
typ = concrete_types[idx]
}
}
}

View File

@ -636,12 +636,19 @@ pub fn (mut c Checker) struct_decl(mut decl ast.StructDecl) {
}
}
fn (mut c Checker) unwrap_generic_struct(struct_type ast.Type, generic_names []string, concrete_types []ast.Type) ast.Type {
ts := c.table.get_type_symbol(struct_type)
if ts.info is ast.Struct {
if ts.info.is_generic {
mut nrt := '$ts.name<'
mut c_nrt := '${ts.cname}_T_'
fn (mut c Checker) unwrap_generic_type(typ ast.Type, generic_names []string, concrete_types []ast.Type) ast.Type {
mut final_concrete_types := []ast.Type{}
mut fields := []ast.StructField{}
mut nrt := ''
mut c_nrt := ''
ts := c.table.get_type_symbol(typ)
match mut ts.info {
ast.Struct, ast.Interface, ast.SumType {
if !ts.info.is_generic {
return typ
}
nrt = '$ts.name<'
c_nrt = '${ts.cname}_T_'
for i in 0 .. ts.info.generic_types.len {
if ct := c.table.resolve_generic_to_concrete(ts.info.generic_types[i],
generic_names, concrete_types)
@ -658,15 +665,15 @@ fn (mut c Checker) unwrap_generic_struct(struct_type ast.Type, generic_names []s
nrt += '>'
idx := c.table.type_idxs[nrt]
if idx != 0 && c.table.type_symbols[idx].kind != .placeholder {
return ast.new_type(idx).derive(struct_type).clear_flag(.generic)
return ast.new_type(idx).derive(typ).clear_flag(.generic)
} else {
// fields type translate to concrete type
mut fields := ts.info.fields.clone()
fields = ts.info.fields.clone()
for i in 0 .. fields.len {
if fields[i].typ.has_flag(.generic) {
sym := c.table.get_type_symbol(fields[i].typ)
if sym.kind == .struct_ && fields[i].typ.idx() != struct_type.idx() {
fields[i].typ = c.unwrap_generic_struct(fields[i].typ, generic_names,
if sym.kind == .struct_ && fields[i].typ.idx() != typ.idx() {
fields[i].typ = c.unwrap_generic_type(fields[i].typ, generic_names,
concrete_types)
} else {
if t_typ := c.table.resolve_generic_to_concrete(fields[i].typ,
@ -678,35 +685,84 @@ fn (mut c Checker) unwrap_generic_struct(struct_type ast.Type, generic_names []s
}
}
// update concrete types
mut info_concrete_types := []ast.Type{}
for i in 0 .. ts.info.generic_types.len {
if t_typ := c.table.resolve_generic_to_concrete(ts.info.generic_types[i],
generic_names, concrete_types)
{
info_concrete_types << t_typ
final_concrete_types << t_typ
}
}
mut info := ts.info
info.is_generic = false
info.concrete_types = info_concrete_types
info.parent_type = struct_type
info.fields = fields
stru_idx := c.table.register_type_symbol(ast.TypeSymbol{
kind: .struct_
name: nrt
cname: util.no_dots(c_nrt)
mod: c.mod
info: info
})
return ast.new_type(stru_idx).derive(struct_type).clear_flag(.generic)
}
}
else {}
}
return struct_type
match mut ts.info {
ast.Struct {
mut info := ts.info
info.is_generic = false
info.concrete_types = final_concrete_types
info.parent_type = typ
info.fields = fields
new_idx := c.table.register_type_symbol(
kind: .struct_
name: nrt
cname: util.no_dots(c_nrt)
mod: c.mod
info: info
)
return ast.new_type(new_idx).derive(typ).clear_flag(.generic)
}
ast.Interface {
// resolve generic types inside methods
mut imethods := ts.info.methods.clone()
for mut method in imethods {
if t := c.table.resolve_generic_to_concrete(method.return_type, generic_names,
concrete_types)
{
method.return_type = t
}
for mut param in method.params {
if t := c.table.resolve_generic_to_concrete(param.typ, generic_names,
concrete_types)
{
param.typ = t
}
}
}
mut all_methods := ts.methods
for imethod in imethods {
for mut method in all_methods {
if imethod.name == method.name {
method = imethod
}
}
}
mut info := ts.info
info.is_generic = false
info.concrete_types = final_concrete_types
info.parent_type = typ
info.fields = fields
info.methods = imethods
new_idx := c.table.register_type_symbol(
kind: .interface_
name: nrt
cname: util.no_dots(c_nrt)
mod: c.mod
info: info
)
mut ts_copy := c.table.get_type_symbol(new_idx)
for method in all_methods {
ts_copy.register_method(method)
}
return ast.new_type(new_idx).derive(typ).clear_flag(.generic)
}
else {}
}
return typ
}
// generic struct instantiations to concrete types
pub fn (mut c Checker) generic_struct_insts_to_concrete() {
pub fn (mut c Checker) generic_insts_to_concrete() {
for mut typ in c.table.type_symbols {
if typ.kind == .generic_struct_inst {
info := typ.info as ast.GenericStructInst
@ -715,32 +771,126 @@ pub fn (mut c Checker) generic_struct_insts_to_concrete() {
typ.kind = .placeholder
continue
}
mut parent_info := parent.info as ast.Struct
mut fields := parent_info.fields.clone()
if parent_info.generic_types.len == info.concrete_types.len {
generic_names := parent_info.generic_types.map(c.table.get_type_symbol(it).name)
for i in 0 .. fields.len {
if fields[i].typ.has_flag(.generic) {
sym := c.table.get_type_symbol(fields[i].typ)
if sym.kind == .struct_ && fields[i].typ.idx() != info.parent_idx {
fields[i].typ = c.unwrap_generic_struct(fields[i].typ, generic_names,
info.concrete_types)
} else {
match parent.info {
ast.Struct {
mut parent_info := parent.info as ast.Struct
mut fields := parent_info.fields.clone()
if parent_info.generic_types.len == info.concrete_types.len {
generic_names := parent_info.generic_types.map(c.table.get_type_symbol(it).name)
for i in 0 .. fields.len {
if fields[i].typ.has_flag(.generic) {
sym := c.table.get_type_symbol(fields[i].typ)
if sym.kind == .struct_ && fields[i].typ.idx() != info.parent_idx {
fields[i].typ = c.unwrap_generic_type(fields[i].typ,
generic_names, info.concrete_types)
} else {
if t_typ := c.table.resolve_generic_to_concrete(fields[i].typ,
generic_names, info.concrete_types)
{
fields[i].typ = t_typ
}
}
}
}
parent_info.is_generic = false
parent_info.concrete_types = info.concrete_types.clone()
parent_info.fields = fields
parent_info.parent_type = ast.new_type(info.parent_idx).set_flag(.generic)
typ.info = ast.Struct{
...parent_info
is_generic: false
concrete_types: info.concrete_types.clone()
fields: fields
parent_type: ast.new_type(info.parent_idx).set_flag(.generic)
}
typ.is_public = true
typ.kind = parent.kind
}
}
ast.Interface {
mut parent_info := parent.info as ast.Interface
if parent_info.generic_types.len == info.concrete_types.len {
mut fields := parent_info.fields.clone()
generic_names := parent_info.generic_types.map(c.table.get_type_symbol(it).name)
for i in 0 .. fields.len {
if t_typ := c.table.resolve_generic_to_concrete(fields[i].typ,
generic_names, info.concrete_types)
{
fields[i].typ = t_typ
}
}
mut imethods := parent_info.methods.clone()
for mut method in imethods {
method.generic_names.clear()
if pt := c.table.resolve_generic_to_concrete(method.return_type,
generic_names, info.concrete_types)
{
method.return_type = pt
}
method.params = method.params.clone()
for mut param in method.params {
if pt := c.table.resolve_generic_to_concrete(param.typ,
generic_names, info.concrete_types)
{
param.typ = pt
}
}
typ.register_method(method)
}
mut all_methods := parent.methods
for imethod in imethods {
for mut method in all_methods {
if imethod.name == method.name {
method = imethod
}
}
}
typ.info = ast.Interface{
...parent_info
is_generic: false
concrete_types: info.concrete_types.clone()
fields: fields
methods: imethods
parent_type: ast.new_type(info.parent_idx).set_flag(.generic)
}
typ.is_public = true
typ.kind = parent.kind
typ.methods = all_methods
}
}
parent_info.is_generic = false
parent_info.concrete_types = info.concrete_types.clone()
parent_info.fields = fields
parent_info.parent_type = ast.new_type(info.parent_idx).set_flag(.generic)
typ.is_public = true
typ.kind = .struct_
typ.info = parent_info
ast.SumType {
mut parent_info := parent.info as ast.SumType
if parent_info.generic_types.len == info.concrete_types.len {
mut fields := parent_info.fields.clone()
mut variants := parent_info.variants.clone()
generic_names := parent_info.generic_types.map(c.table.get_type_symbol(it).name)
for i in 0 .. fields.len {
if t_typ := c.table.resolve_generic_to_concrete(fields[i].typ,
generic_names, info.concrete_types)
{
fields[i].typ = t_typ
}
}
for i in 0 .. variants.len {
if t_typ := c.table.resolve_generic_to_concrete(variants[i],
generic_names, info.concrete_types)
{
variants[i] = t_typ
}
}
typ.info = ast.SumType{
...parent_info
is_generic: false
concrete_types: info.concrete_types.clone()
fields: fields
variants: variants
parent_type: ast.new_type(info.parent_idx).set_flag(.generic)
}
typ.is_public = true
typ.kind = parent.kind
}
}
else {}
}
}
}
@ -778,7 +928,7 @@ pub fn (mut c Checker) struct_init(mut node ast.StructInit) ast.Type {
return ast.void_type
}
}
unwrapped_struct_type := c.unwrap_generic_struct(node.typ, c.table.cur_fn.generic_names,
unwrapped_struct_type := c.unwrap_generic_type(node.typ, c.table.cur_fn.generic_names,
c.table.cur_concrete_types)
c.ensure_type_exists(unwrapped_struct_type, node.pos) or {}
type_sym := c.table.get_type_symbol(unwrapped_struct_type)
@ -2118,7 +2268,7 @@ pub fn (mut c Checker) method_call(mut call_expr ast.CallExpr) ast.Type {
}
// resolve return generics struct to concrete type
if method.generic_names.len > 0 && method.return_type.has_flag(.generic) {
call_expr.return_type = c.unwrap_generic_struct(method.return_type, method.generic_names,
call_expr.return_type = c.unwrap_generic_type(method.return_type, method.generic_names,
concrete_types)
} else {
call_expr.return_type = method.return_type
@ -2697,7 +2847,7 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type {
concrete_types = call_expr.concrete_types
}
if func.generic_names.len > 0 {
for i, call_arg in call_expr.args {
for i, mut call_arg in call_expr.args {
param := if func.is_variadic && i >= func.params.len - 1 {
func.params[func.params.len - 1]
} else {
@ -2711,7 +2861,18 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type {
if unwrap_typ := c.table.resolve_generic_to_concrete(param.typ, func.generic_names,
concrete_types)
{
c.check_expected_call_arg(c.unwrap_generic(typ), unwrap_typ, call_expr.language) or {
utyp := c.unwrap_generic(typ)
unwrap_sym := c.table.get_type_symbol(unwrap_typ)
if unwrap_sym.kind == .interface_ {
if c.type_implements(utyp, unwrap_typ, call_arg.expr.position()) {
if !utyp.is_ptr() && !utyp.is_pointer() && !c.inside_unsafe
&& c.table.get_type_symbol(utyp).kind != .interface_ {
c.mark_as_referenced(mut &call_arg.expr, true)
}
}
continue
}
c.check_expected_call_arg(utyp, unwrap_typ, call_expr.language) or {
c.error('$err.msg in argument ${i + 1} to `$fn_name`', call_arg.pos)
}
}
@ -2720,7 +2881,7 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type {
}
// resolve return generics struct to concrete type
if func.generic_names.len > 0 && func.return_type.has_flag(.generic) {
call_expr.return_type = c.unwrap_generic_struct(func.return_type, func.generic_names,
call_expr.return_type = c.unwrap_generic_type(func.return_type, func.generic_names,
concrete_types)
} else {
call_expr.return_type = func.return_type
@ -2782,13 +2943,92 @@ fn semicolonize(main string, details string) string {
return '$main; $details'
}
fn (mut c Checker) resolve_generic_interface(typ ast.Type, interface_type ast.Type, pos token.Position) ast.Type {
utyp := c.unwrap_generic(typ)
typ_sym := c.table.get_type_symbol(utyp)
mut inter_sym := c.table.get_type_symbol(interface_type)
if mut inter_sym.info is ast.Interface {
if inter_sym.info.is_generic {
mut inferred_types := []ast.Type{}
for ifield in inter_sym.info.fields {
if ifield.typ.has_flag(.generic) {
if field := c.table.find_field_with_embeds(typ_sym, ifield.name) {
if field.typ !in inferred_types {
inferred_types << field.typ
}
}
}
}
for imethod in inter_sym.info.methods {
method := typ_sym.find_method(imethod.name) or {
typ_sym.find_method_with_generic_parent(imethod.name) or { ast.Fn{} }
}
if imethod.return_type.has_flag(.generic) {
if method.return_type !in inferred_types {
inferred_types << method.return_type
}
}
for i, iparam in imethod.params {
param := method.params[i] or { ast.Param{} }
if iparam.typ.has_flag(.generic) {
if param.typ !in inferred_types {
inferred_types << param.typ
}
}
}
if inferred_types !in c.table.fn_generic_types[imethod.name] {
c.table.fn_generic_types[imethod.name] << inferred_types
}
}
if inferred_types.len == 0 {
c.error('cannot infer generic types for ${c.table.type_to_str(interface_type)}',
pos)
return ast.void_type
}
if inferred_types.len > 1 {
c.error('cannot infer generic types for ${c.table.type_to_str(interface_type)}: got conflicting type information',
pos)
return ast.void_type
}
inferred_type := inferred_types[0]
if inferred_type !in inter_sym.info.concrete_types {
inter_sym.info.concrete_types << inferred_type
}
generic_names := inter_sym.info.generic_types.map(c.table.get_type_name(it))
return c.unwrap_generic_type(interface_type, generic_names, inter_sym.info.concrete_types)
}
}
return interface_type
}
fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos token.Position) bool {
$if debug_interface_type_implements ? {
eprintln('> type_implements typ: $typ.debug() | inter_typ: $interface_type.debug()')
eprintln('> type_implements typ: $typ.debug() (`${c.table.type_to_str(typ)}`) | inter_typ: $interface_type.debug() (`${c.table.type_to_str(interface_type)}`)')
}
utyp := c.unwrap_generic(typ)
typ_sym := c.table.get_type_symbol(utyp)
mut inter_sym := c.table.get_type_symbol(interface_type)
if mut inter_sym.info is ast.Interface {
mut generic_type := interface_type
mut generic_info := inter_sym.info
if inter_sym.info.parent_type.has_flag(.generic) {
parent_sym := c.table.get_type_symbol(inter_sym.info.parent_type)
if parent_sym.info is ast.Interface {
generic_type = inter_sym.info.parent_type
generic_info = parent_sym.info
}
}
mut inferred_type := interface_type
if generic_info.is_generic {
inferred_type = c.resolve_generic_interface(typ, generic_type, pos)
if inferred_type == 0 {
return false
}
}
if inter_sym.info.is_generic {
return c.type_implements(typ, inferred_type, pos)
}
}
// do not check the same type more than once
if mut inter_sym.info is ast.Interface {
for t in inter_sym.info.types {
@ -2815,9 +3055,17 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to
} else {
inter_sym.methods
}
// Verify methods
for imethod in imethods {
if method := typ_sym.find_method(imethod.name) {
// voidptr is an escape hatch, it should be allowed to be passed
if utyp != ast.voidptr_type {
// Verify methods
for imethod in imethods {
method := typ_sym.find_method(imethod.name) or {
typ_sym.find_method_with_generic_parent(imethod.name) or {
c.error("`$styp` doesn't implement method `$imethod.name` of interface `$inter_sym.name`",
pos)
continue
}
}
msg := c.table.is_same_method(imethod, method)
if msg.len > 0 {
sig := c.table.fn_signature(imethod, skip_receiver: true)
@ -2826,12 +3074,6 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to
pos)
return false
}
continue
}
// voidptr is an escape hatch, it should be allowed to be passed
if utyp != ast.voidptr_type {
c.error("`$styp` doesn't implement method `$imethod.name` of interface `$inter_sym.name`",
pos)
}
}
// Verify fields
@ -6114,7 +6356,7 @@ fn (mut c Checker) smartcast_if_conds(node ast.Expr, mut scope ast.Scope) {
c.smartcast_if_conds(node.right, mut scope)
} else if node.op == .key_is {
right_expr := node.right
right_type := match right_expr {
mut right_type := match right_expr {
ast.TypeNode {
right_expr.typ
}
@ -6126,9 +6368,10 @@ fn (mut c Checker) smartcast_if_conds(node ast.Expr, mut scope ast.Scope) {
ast.Type(0)
}
}
right_type = c.unwrap_generic(right_type)
if right_type != ast.Type(0) {
left_sym := c.table.get_type_symbol(node.left_type)
expr_type := c.expr(node.left)
expr_type := c.unwrap_generic(c.expr(node.left))
if left_sym.kind == .interface_ {
c.type_implements(right_type, expr_type, node.pos)
} else if !c.check_types(right_type, expr_type) {

View File

@ -0,0 +1,14 @@
vlib/v/checker/tests/generic_sumtype_invalid_variant.vv:5:7: error: `MultiGeneric<bool,int,string>` has no variant `u64`
3 | fn main() {
4 | mut m := MultiGeneric<bool, int, string>(true)
5 | if m is u64 {
| ~~
6 | println('hi')
7 | }
vlib/v/checker/tests/generic_sumtype_invalid_variant.vv:8:7: error: `MultiGeneric<bool,int,string>` has no variant `X`
6 | println('hi')
7 | }
8 | if m is X {
| ~~
9 | println('hi again')
10 | }

View File

@ -0,0 +1,11 @@
type MultiGeneric<X, Y, Z> = X | Y | Z
fn main() {
mut m := MultiGeneric<bool, int, string>(true)
if m is u64 {
println('hi')
}
if m is X {
println('hi again')
}
}

View File

@ -1187,7 +1187,14 @@ pub fn (mut f Fmt) interface_decl(node ast.InterfaceDecl) {
f.write('interface ')
f.write_language_prefix(node.language)
name := node.name.after('.')
f.write('$name {')
f.write(name)
if node.generic_types.len > 0 {
f.write('<')
gtypes := node.generic_types.map(f.table.type_to_str(it)).join(', ')
f.write(gtypes)
f.write('>')
}
f.write(' {')
if node.fields.len > 0 || node.methods.len > 0 || node.pos.line_nr < node.pos.last_line {
f.writeln('')
}
@ -1370,7 +1377,15 @@ pub fn (mut f Fmt) sum_type_decl(node ast.SumTypeDecl) {
if node.is_pub {
f.write('pub ')
}
f.write('type $node.name = ')
f.write('type $node.name')
if node.generic_types.len > 0 {
f.write('<')
gtypes := node.generic_types.map(f.table.type_to_str(it)).join(', ')
f.write(gtypes)
f.write('>')
}
f.write(' = ')
mut sum_type_names := []string{}
for t in node.variants {
sum_type_names << f.table.type_to_str_using_aliases(t.typ, f.mod2alias)

View File

@ -117,7 +117,7 @@ fn (mut g Gen) gen_assert_single_expr(expr ast.Expr, typ ast.Type) {
}
}
ast.TypeNode {
sym := g.table.get_type_symbol(typ)
sym := g.table.get_type_symbol(g.unwrap_generic(typ))
g.write(ctoslit('$sym.name'))
}
else {

View File

@ -20,7 +20,7 @@ fn (mut g Gen) gen_sumtype_equality_fn(left_type ast.Type) string {
fn_builder.writeln('\tif (a._typ != b._typ) { return false; }')
for typ in info.variants {
variant := g.unwrap(typ)
fn_builder.writeln('\tif (a._typ == $typ) {')
fn_builder.writeln('\tif (a._typ == $variant.typ) {')
name := '_$variant.sym.cname'
if variant.sym.kind == .string {
fn_builder.writeln('\t\treturn string__eq(*a.$name, *b.$name);')

View File

@ -324,6 +324,11 @@ fn (mut g Gen) gen_str_for_interface(info ast.Interface, styp string, str_fn_nam
if styp.ends_with('*') {
clean_interface_v_type_name = '&' + clean_interface_v_type_name.replace('*', '')
}
if clean_interface_v_type_name.contains('_T_') {
clean_interface_v_type_name =
clean_interface_v_type_name.replace('Array_', '[]').replace('_T_', '<').replace('_', ', ') +
'>'
}
clean_interface_v_type_name = util.strip_main_name(clean_interface_v_type_name)
fn_builder.writeln('static string indent_${str_fn_name}($styp x, int indent_count) { /* gen_str_for_interface */')
for typ in info.types {
@ -378,6 +383,11 @@ fn (mut g Gen) gen_str_for_union_sum_type(info ast.SumType, styp string, str_fn_
if styp.ends_with('*') {
clean_sum_type_v_type_name = '&' + clean_sum_type_v_type_name.replace('*', '')
}
if clean_sum_type_v_type_name.contains('_T_') {
clean_sum_type_v_type_name =
clean_sum_type_v_type_name.replace('Array_', '[]').replace('_T_', '<').replace('_', ', ') +
'>'
}
clean_sum_type_v_type_name = util.strip_main_name(clean_sum_type_v_type_name)
fn_builder.writeln('\tswitch(x._typ) {')
for typ in info.variants {

View File

@ -519,6 +519,9 @@ pub fn (mut g Gen) write_typeof_functions() {
for typ in g.table.type_symbols {
if typ.kind == .sum_type {
sum_info := typ.info as ast.SumType
if sum_info.is_generic {
continue
}
g.writeln('static char * v_typeof_sumtype_${typ.cname}(int sidx) { /* $typ.name */ ')
if g.pref.build_mode == .build_module {
g.writeln('\t\tif( sidx == _v_type_idx_${typ.cname}() ) return "${util.strip_main_name(typ.name)}";')
@ -541,6 +544,9 @@ pub fn (mut g Gen) write_typeof_functions() {
g.writeln('}')
} else if typ.kind == .interface_ {
inter_info := typ.info as ast.Interface
if inter_info.is_generic {
continue
}
g.writeln('static char * v_typeof_interface_${typ.cname}(int sidx) { /* $typ.name */ ')
for t in inter_info.types {
subtype := g.table.get_type_symbol(t)
@ -739,23 +745,27 @@ fn (mut g Gen) cc_type(typ ast.Type, is_prefix_struct bool) string {
sym := g.table.get_type_symbol(g.unwrap_generic(typ))
mut styp := sym.cname
// TODO: this needs to be removed; cgen shouldn't resolve generic types (job of checker)
if mut sym.info is ast.Struct {
if sym.info.is_generic {
mut sgtyps := '_T'
for gt in sym.info.generic_types {
gts := g.table.get_type_symbol(g.unwrap_generic(gt))
sgtyps += '_$gts.cname'
match mut sym.info {
ast.Struct, ast.Interface, ast.SumType {
if sym.info.is_generic {
mut sgtyps := '_T'
for gt in sym.info.generic_types {
gts := g.table.get_type_symbol(g.unwrap_generic(gt))
sgtyps += '_$gts.cname'
}
styp += sgtyps
}
styp += sgtyps
}
} else if mut sym.info is ast.MultiReturn {
// TODO: this doesn't belong here, but makes it working for now
mut cname := 'multi_return'
for mr_typ in sym.info.types {
mr_type_sym := g.table.get_type_symbol(g.unwrap_generic(mr_typ))
cname += '_$mr_type_sym.cname'
ast.MultiReturn {
// TODO: this doesn't belong here, but makes it working for now
mut cname := 'multi_return'
for mr_typ in sym.info.types {
mr_type_sym := g.table.get_type_symbol(g.unwrap_generic(mr_typ))
cname += '_$mr_type_sym.cname'
}
return cname
}
return cname
else {}
}
if is_prefix_struct && styp.starts_with('C__') {
styp = styp[3..]
@ -889,7 +899,10 @@ pub fn (mut g Gen) write_alias_typesymbol_declaration(sym ast.TypeSymbol) {
pub fn (mut g Gen) write_interface_typesymbol_declaration(sym ast.TypeSymbol) {
info := sym.info as ast.Interface
struct_name := c_name(sym.name)
if info.is_generic {
return
}
struct_name := c_name(sym.cname)
g.type_definitions.writeln('typedef struct $struct_name $struct_name;')
g.type_definitions.writeln('struct $struct_name {')
g.type_definitions.writeln('\tunion {')
@ -1852,20 +1865,27 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ
g.write('.msg))')
return
}
if exp_sym.kind == .interface_ && got_type_raw.idx() != expected_type.idx()
if exp_sym.info is ast.Interface && got_type_raw.idx() != expected_type.idx()
&& !expected_type.has_flag(.optional) {
if expr is ast.StructInit && !got_type.is_ptr() {
g.inside_cast_in_heap++
got_styp := g.cc_type(got_type.to_ptr(), true)
exp_styp := g.cc_type(expected_type, true)
fname := 'I_${got_styp}_to_Interface_$exp_styp'
// TODO: why does cc_type even add this in the first place?
exp_styp := exp_sym.cname
mut fname := 'I_${got_styp}_to_Interface_$exp_styp'
if exp_sym.info.is_generic {
fname = g.generic_fn_name(exp_sym.info.concrete_types, fname, false)
}
g.call_cfn_for_casting_expr(fname, expr, expected_is_ptr, exp_styp, true,
got_styp)
g.inside_cast_in_heap--
} else {
got_styp := g.cc_type(got_type, true)
exp_styp := g.cc_type(expected_type, true)
fname := 'I_${got_styp}_to_Interface_$exp_styp'
exp_styp := exp_sym.cname
mut fname := '/*$exp_sym*/I_${got_styp}_to_Interface_$exp_styp'
if exp_sym.info.is_generic {
fname = g.generic_fn_name(exp_sym.info.concrete_types, fname, false)
}
g.call_cfn_for_casting_expr(fname, expr, expected_is_ptr, exp_styp, got_is_ptr,
got_styp)
}
@ -3421,8 +3441,9 @@ fn (mut g Gen) expr(node ast.Expr) {
// match sum Type
// g.write('/* Type */')
// type_idx := node.typ.idx()
sym := g.table.get_type_symbol(node.typ)
sidx := g.type_sidx(node.typ)
typ := g.unwrap_generic(node.typ)
sym := g.table.get_type_symbol(typ)
sidx := g.type_sidx(typ)
// g.write('$type_idx /* $sym.name */')
g.write('$sidx /* $sym.name */')
}
@ -3839,7 +3860,7 @@ fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var str
} else if sym.kind == .interface_ {
if branch.exprs[sumtype_index] is ast.TypeNode {
typ := branch.exprs[sumtype_index] as ast.TypeNode
branch_sym := g.table.get_type_symbol(typ.typ)
branch_sym := g.table.get_type_symbol(g.unwrap_generic(typ.typ))
g.write('${dot_or_ptr}_typ == _${sym.cname}_${branch_sym.cname}_index')
} else if branch.exprs[sumtype_index] is ast.None && sym.name == 'IError' {
g.write('${dot_or_ptr}_typ == _IError_None___index')
@ -4248,7 +4269,7 @@ fn (mut g Gen) ident(node ast.Ident) {
}
}
for i, typ in v.smartcasts {
cast_sym := g.table.get_type_symbol(typ)
cast_sym := g.table.get_type_symbol(g.unwrap_generic(typ))
mut is_ptr := false
if i == 0 {
g.write(name)
@ -5452,6 +5473,9 @@ fn (mut g Gen) write_types(types []ast.TypeSymbol) {
}
}
ast.SumType {
if typ.info.is_generic {
continue
}
g.typedefs.writeln('typedef struct $name $name;')
g.type_definitions.writeln('')
g.type_definitions.writeln('// Union sum type $name = ')
@ -6135,6 +6159,9 @@ fn (mut g Gen) interface_table() string {
continue
}
inter_info := ityp.info as ast.Interface
if inter_info.is_generic {
continue
}
// interface_name is for example Speaker
interface_name := ityp.cname
// generate a struct that references interface methods
@ -6242,13 +6269,41 @@ static inline $interface_name I_${cctype}_to_Interface_${interface_name}($cctype
}
}
}
for _, method in st_sym.methods {
mut methods := st_sym.methods
match st_sym.info {
ast.Struct, ast.Interface, ast.SumType {
if st_sym.info.parent_type.has_flag(.generic) {
parent_sym := g.table.get_type_symbol(st_sym.info.parent_type)
for method in parent_sym.methods {
if method.name in methodidx {
methods << st_sym.find_method_with_generic_parent(method.name) or {
continue
}
}
}
}
}
else {}
}
for method in methods {
mut name := method.name
if inter_info.parent_type.has_flag(.generic) {
parent_sym := g.table.get_type_symbol(inter_info.parent_type)
match mut parent_sym.info {
ast.Struct, ast.Interface, ast.SumType {
name = g.generic_fn_name(parent_sym.info.concrete_types, method.name,
false)
}
else {}
}
}
if method.name !in methodidx {
// a method that is not part of the interface should be just skipped
continue
}
// .speak = Cat_speak
mut method_call := '${cctype}_$method.name'
mut method_call := '${cctype}_$name'
if !method.params[0].typ.is_ptr() {
// inline void Cat_speak_Interface_Animal_method_wrapper(Cat c) { return Cat_speak(*c); }
iwpostfix := '_Interface_${interface_name}_method_wrapper'

View File

@ -146,7 +146,7 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
for concrete_types in g.table.fn_generic_types[node.name] {
if g.pref.is_verbose {
syms := concrete_types.map(g.table.get_type_symbol(it))
the_type := syms.map(node.name).join(', ')
the_type := syms.map(it.name).join(', ')
println('gen fn `$node.name` for type `$the_type`')
}
g.table.cur_concrete_types = concrete_types

View File

@ -1856,11 +1856,7 @@ fn (p &Parser) is_typename(t token.Token) bool {
// 10. otherwise, it's not generic
// see also test_generic_detection in vlib/v/tests/generics_test.v
fn (p &Parser) is_generic_call() bool {
lit0_is_capital := if p.tok.kind != .eof && p.tok.lit.len > 0 {
p.tok.lit[0].is_capital()
} else {
false
}
lit0_is_capital := p.tok.kind != .eof && p.tok.lit.len > 0 && p.tok.lit[0].is_capital()
if lit0_is_capital || p.peek_tok.kind != .lt {
return false
}
@ -1901,6 +1897,45 @@ fn (p &Parser) is_generic_call() bool {
return false
}
const valid_tokens_inside_types = [token.Kind.lsbr, .rsbr, .name, .dot, .comma, .key_fn, .lt]
fn (mut p Parser) is_generic_cast() bool {
if !p.tok.can_start_type(ast.builtin_type_names) {
return false
}
mut i := 0
mut level := 0
mut lt_count := 0
for {
i++
tok := p.peek_token(i)
if tok.kind == .lt {
lt_count++
level++
} else if tok.kind == .gt {
level--
}
if lt_count > 0 && level == 0 {
break
}
if i > 20 || tok.kind !in parser.valid_tokens_inside_types {
return false
}
}
next_tok := p.peek_token(i + 1)
// `next_tok` is the token following the closing `>` of the generic type: MyType<int>{
// ^
// if `next_tok` is a left paren, then the full expression looks something like
// `Foo<string>(` or `Foo<mod.Type>(`, which are valid type casts - return true
if next_tok.kind == .lpar {
return true
}
// any other token is not a valid generic cast, however
return false
}
pub fn (mut p Parser) name_expr() ast.Expr {
prev_tok_kind := p.prev_tok.kind
mut node := ast.empty_expr()
@ -2041,6 +2076,8 @@ pub fn (mut p Parser) name_expr() ast.Expr {
false
}
is_optional := p.tok.kind == .question
is_generic_call := p.is_generic_call()
is_generic_cast := p.is_generic_cast()
// p.warn('name expr $p.tok.lit $p.peek_tok.str()')
same_line := p.tok.line_nr == p.peek_tok.line_nr
// `(` must be on same line as name token otherwise it's a ParExpr
@ -2053,8 +2090,8 @@ pub fn (mut p Parser) name_expr() ast.Expr {
p.defer_vars << ident
}
}
} else if p.peek_tok.kind == .lpar
|| (is_optional && p.peek_token(2).kind == .lpar) || p.is_generic_call() {
} else if p.peek_tok.kind == .lpar || is_generic_call || is_generic_cast
|| (is_optional && p.peek_token(2).kind == .lpar) {
// foo(), foo<int>() or type() cast
mut name := if is_optional { p.peek_tok.lit } else { p.tok.lit }
if mod.len > 0 {
@ -2064,7 +2101,7 @@ pub fn (mut p Parser) name_expr() ast.Expr {
// type cast. TODO: finish
// if name in ast.builtin_type_names {
if (!known_var && (name in p.table.type_idxs || name_w_mod in p.table.type_idxs)
&& name !in ['C.stat', 'C.sigaction']) || is_mod_cast
&& name !in ['C.stat', 'C.sigaction']) || is_mod_cast || is_generic_cast
|| (language == .v && name[0].is_capital()) {
// MainLetter(x) is *always* a cast, as long as it is not `C.`
// TODO handle C.stat()
@ -3077,6 +3114,7 @@ fn (mut p Parser) type_decl() ast.TypeDecl {
return ast.AliasTypeDecl{}
}
mut sum_variants := []ast.TypeNode{}
generic_types := p.parse_generic_type_list()
p.check(.assign)
mut type_pos := p.tok.position()
mut comments := []ast.Comment{}
@ -3132,6 +3170,8 @@ fn (mut p Parser) type_decl() ast.TypeDecl {
mod: p.mod
info: ast.SumType{
variants: variant_types
is_generic: generic_types.len > 0
generic_types: generic_types
}
is_public: is_pub
})
@ -3141,6 +3181,7 @@ fn (mut p Parser) type_decl() ast.TypeDecl {
typ: typ
is_pub: is_pub
variants: sum_variants
generic_types: generic_types
pos: decl_pos
comments: comments
}

View File

@ -47,18 +47,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
name_pos)
return ast.StructDecl{}
}
mut generic_types := []ast.Type{}
if p.tok.kind == .lt {
p.next()
for {
generic_types << p.parse_type()
if p.tok.kind != .comma {
break
}
p.next()
}
p.check(.gt)
}
generic_types := p.parse_generic_type_list()
no_body := p.tok.kind != .lcbr
if language == .v && no_body {
p.error('`$p.tok.lit` lacks body')
@ -456,6 +445,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
p.check_for_impure_v(language, name_pos)
modless_name := p.check_name()
interface_name := p.prepend_mod(modless_name).clone()
generic_types := p.parse_generic_type_list()
// println('interface decl $interface_name')
p.check(.lcbr)
pre_comments := p.eat_comments({})
@ -473,6 +463,8 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
mod: p.mod
info: ast.Interface{
types: []
is_generic: generic_types.len > 0
generic_types: generic_types
}
)
if reg_idx == -1 {
@ -622,6 +614,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
is_pub: is_pub
pos: pos
pre_comments: pre_comments
generic_types: generic_types
mut_pos: mut_pos
name_pos: name_pos
}

View File

@ -0,0 +1,65 @@
interface Gettable<T> {
get() T
}
struct Animal<T> {
metadata T
}
fn (a Animal<T>) get<T>() T {
return a.metadata
}
// different struct implementing the same interface:
struct Mineral<T> {
value T
}
fn (m Mineral<T>) get<T>() T {
return m.value
}
////
fn extract<T>(xs []Gettable<T>) []T {
return xs.map(it.get())
}
fn extract_basic<T>(xs Gettable<T>) T {
return xs.get()
}
fn test_extract() {
a := Animal<int>{123}
b := Animal<int>{456}
c := Mineral<int>{789}
arr := [Gettable<int>(a), Gettable<int>(b), Gettable<int>(c)]
assert typeof(arr).name == '[]Gettable<int>'
x := extract<int>(arr)
assert x == [123, 456, 789]
}
// fn test_extract_multiple_instance_types() {
// a := Animal<string>{'123'}
// b := Animal<string>{'456'}
// c := Mineral<string>{'789'}
// arr := [Gettable<string>(a), Gettable<string>(b), Gettable<string>(c)]
// assert typeof(arr).name == '[]Gettable<string>'
// x := extract<string>(arr)
// assert x == ['123', '456', '789']
// }
fn test_extract_basic() {
a := Animal<int>{123}
b := Animal<int>{456}
c := Mineral<int>{789}
aa := extract_basic(a)
bb := extract_basic(b)
cc := extract_basic(c)
assert '$aa | $bb | $cc' == '123 | 456 | 789'
}

View File

@ -0,0 +1,78 @@
struct None {}
// not named `Option` to avoid conflicts with the built-in type:
type MyOption<T> = Error | None | T
fn unwrap_if<T>(o MyOption<T>) T {
if o is T {
return o
}
panic('no value')
}
fn unwrap_match<T>(o MyOption<T>) string {
match o {
None {
return 'none'
}
Error {
return 'none'
}
T {
return 'value'
}
}
}
fn test_generic_sumtype_unwrapping() {
y := MyOption<bool>(false)
assert unwrap_if(y) == false
assert unwrap_match(y) == 'value'
}
fn test_generic_sumtype_auto_str() {
x := MyOption<string>('hi')
y := MyOption<bool>(None{})
assert '$x, $y' == "MyOption<string>('hi'), MyOption<bool>(None{})"
}
struct Foo<T> {
x T
}
struct Bar<T> {
x T
}
type MyType<T> = Bar<T> | Foo<T>
fn test_generic_struct_members() {
// TODO: this is currently needed to properly resolve that variant's type:
_ = Bar<string>{''}
f := Foo<string>{'hi'}
t := MyType<string>(f)
assert t.type_name() == 'Foo<string>'
// accessing a field common to all variants, just like with a normal sumtype:
assert t.x == 'hi'
}
type MultiGeneric<X, Y, Z> = X | Y | Z
fn test_multi_generic_type() {
mut m := MultiGeneric<bool, int, string>(1234)
m = 'hi'
match m {
bool {
assert false
}
int {
assert false
}
string {
return
}
}
assert false
}

View File

@ -479,9 +479,9 @@ pub fn (kind Kind) is_infix() bool {
// Pass ast.builtin_type_names
// Note: can't import table here due to circular module dependency
pub fn (tok &Token) can_start_type(builtin_type_names []string) bool {
pub fn (tok &Token) can_start_type(builtin_types []string) bool {
match tok.kind {
.name { return tok.lit[0].is_capital() || tok.lit in builtin_type_names }
.name { return (tok.lit.len > 0 && tok.lit[0].is_capital()) || tok.lit in builtin_types }
// Note: return type (T1, T2) should be handled elsewhere
.amp, .key_fn, .lsbr, .question { return true }
else {}