checker/gen: fix generic struct init (#8322)

pull/8515/head
Daniel Däschle 2021-02-02 14:42:00 +01:00 committed by GitHub
parent 58b37519e0
commit d477e525bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 211 additions and 75 deletions

View File

@ -90,7 +90,7 @@ mut:
struct WebhookServer {
vweb.Context
mut:
gen_vc &GenVC
gen_vc &GenVC = 0 // initialized in init_once
}
// storage for flag options

View File

@ -264,6 +264,7 @@ pub:
pos token.Position
is_short bool
pub mut:
unresolved bool
pre_comments []Comment
typ table.Type
update_expr Expr

72
vlib/v/ast/init.v 100644
View File

@ -0,0 +1,72 @@
module ast
import v.table
pub fn resolve_init(node StructInit, typ table.Type, t &table.Table) Expr {
type_sym := t.get_type_symbol(typ)
if type_sym.kind == .array {
array_info := type_sym.info as table.Array
mut has_len := false
mut has_cap := false
mut has_default := false
mut len_expr := Expr{}
mut cap_expr := Expr{}
mut default_expr := Expr{}
mut exprs := []Expr{}
for field in node.fields {
match field.name {
'len' {
has_len = true
len_expr = field.expr
}
'cap' {
has_cap = true
len_expr = field.expr
}
'default' {
has_default = true
len_expr = field.expr
}
else {
exprs << field.expr
}
}
}
return ArrayInit{
// TODO: mod is not being set for now, we could need this in future
// mod: mod
pos: node.pos
typ: typ
elem_type: array_info.elem_type
has_len: has_len
has_cap: has_cap
has_default: has_default
len_expr: len_expr
cap_expr: cap_expr
default_expr: default_expr
exprs: exprs
}
} else if type_sym.kind == .map {
map_info := type_sym.info as table.Map
mut keys := []Expr{}
mut vals := []Expr{}
for field in node.fields {
keys << StringLiteral{
val: field.name
}
vals << field.expr
}
return MapInit{
typ: typ
key_type: map_info.key_type
value_type: map_info.value_type
keys: keys
vals: vals
}
}
// struct / other (sumtype?)
return StructInit{
...node
unresolved: false
}
}

View File

@ -460,6 +460,7 @@ pub fn (mut c Checker) infer_fn_types(f table.Fn, mut call_expr ast.CallExpr) {
}
if typ == table.void_type {
c.error('could not infer generic type `$gt_name` in call to `$f.name`', call_expr.pos)
return
}
if c.pref.is_verbose {
s := c.table.type_to_str(typ)

View File

@ -508,7 +508,8 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type {
if struct_init.typ == 0 {
c.error('unknown type', struct_init.pos)
}
type_sym := c.table.get_type_symbol(struct_init.typ)
utyp := c.unwrap_generic(struct_init.typ)
type_sym := c.table.get_type_symbol(utyp)
if type_sym.kind == .sum_type && struct_init.fields.len == 1 {
sexpr := struct_init.fields[0].expr.str()
c.error('cast to sum type using `${type_sym.name}($sexpr)` not `$type_sym.name{$sexpr}`',
@ -517,15 +518,16 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type {
if type_sym.kind == .interface_ {
c.error('cannot instantiate interface `$type_sym.name`', struct_init.pos)
}
if type_sym.kind == .alias {
info := type_sym.info as table.Alias
if info.parent_type.is_number() {
if type_sym.info is table.Alias {
if type_sym.info.parent_type.is_number() {
c.error('cannot instantiate number type alias `$type_sym.name`', struct_init.pos)
return table.void_type
}
}
if !type_sym.is_public && type_sym.kind != .placeholder && type_sym.mod != c.mod
&& type_sym.language != .c {
// allow init structs from generic if they're private except the type is from builtin module
if !type_sym.is_public && type_sym.kind != .placeholder && type_sym.language != .c
&& (type_sym.mod != c.mod && !(struct_init.typ.has_flag(.generic)
&& type_sym.mod != 'builtin')) {
c.error('type `$type_sym.name` is private', struct_init.pos)
}
if type_sym.kind == .struct_ {
@ -3349,14 +3351,11 @@ fn (mut c Checker) stmts(stmts []ast.Stmt) {
pub fn (c &Checker) unwrap_generic(typ table.Type) table.Type {
if typ.has_flag(.generic) {
sym := c.table.get_type_symbol(typ)
mut idx := 0
for i, generic_param in c.cur_fn.generic_params {
if generic_param.name == sym.name {
idx = i
break
return c.cur_generic_types[i].derive(typ).clear_flag(.generic)
}
}
return c.cur_generic_types[idx].derive(typ).clear_flag(.generic)
}
return typ
}
@ -3583,6 +3582,9 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type {
return c.string_inter_lit(mut node)
}
ast.StructInit {
if node.unresolved {
return c.expr(ast.resolve_init(node, c.unwrap_generic(node.typ), c.table))
}
return c.struct_init(mut node)
}
ast.Type {

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/check_generic_int_init.vv:2:9: error: type `int` is private
1 | fn test<T>() T {
2 | return T{}
| ~~~
3 | }
4 |

View File

@ -0,0 +1,7 @@
fn test<T>() T {
return T{}
}
fn main() {
_ := test<int>()
}

View File

@ -2721,55 +2721,7 @@ fn (mut g Gen) expr(node ast.Expr) {
g.match_expr(node)
}
ast.MapInit {
key_typ_str := g.typ(node.key_type)
value_typ_str := g.typ(node.value_type)
value_typ := g.table.get_type_symbol(node.value_type)
key_typ := g.table.get_type_symbol(node.key_type)
hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_typ)
size := node.vals.len
mut shared_styp := '' // only needed for shared &[]{...}
mut styp := ''
is_amp := g.is_amp
g.is_amp = false
if is_amp {
g.out.go_back(1) // delete the `&` already generated in `prefix_expr()
}
if g.is_shared {
mut shared_typ := node.typ.set_flag(.shared_f)
shared_styp = g.typ(shared_typ)
g.writeln('($shared_styp*)__dup_shared_map(&($shared_styp){.val = ')
} else if is_amp {
styp = g.typ(node.typ)
g.write('($styp*)memdup(ADDR($styp, ')
}
if size > 0 {
if value_typ.kind == .function {
g.write('new_map_init_2($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof(voidptr), _MOV(($key_typ_str[$size]){')
} else {
g.write('new_map_init_2($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof($value_typ_str), _MOV(($key_typ_str[$size]){')
}
for expr in node.keys {
g.expr(expr)
g.write(', ')
}
if value_typ.kind == .function {
g.write('}), _MOV((voidptr[$size]){')
} else {
g.write('}), _MOV(($value_typ_str[$size]){')
}
for expr in node.vals {
g.expr(expr)
g.write(', ')
}
g.write('}))')
} else {
g.write('new_map_2(sizeof($key_typ_str), sizeof($value_typ_str), $hash_fn, $key_eq_fn, $clone_fn, $free_fn)')
}
if g.is_shared {
g.write('}, sizeof($shared_styp))')
} else if is_amp {
g.write('), sizeof($styp))')
}
g.map_init(node)
}
ast.None {
g.write('opt_none()')
@ -2863,8 +2815,12 @@ fn (mut g Gen) expr(node ast.Expr) {
g.string_inter_literal(node)
}
ast.StructInit {
// `user := User{name: 'Bob'}`
g.struct_init(node)
if node.unresolved {
g.expr(ast.resolve_init(node, g.unwrap_generic(node.typ), g.table))
} else {
// `user := User{name: 'Bob'}`
g.struct_init(node)
}
}
ast.SelectorExpr {
g.selector_expr(node)
@ -3645,6 +3601,58 @@ fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var str
}
}
fn (mut g Gen) map_init(node ast.MapInit) {
key_typ_str := g.typ(node.key_type)
value_typ_str := g.typ(node.value_type)
value_typ := g.table.get_type_symbol(node.value_type)
key_typ := g.table.get_type_symbol(node.key_type)
hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_typ)
size := node.vals.len
mut shared_styp := '' // only needed for shared &[]{...}
mut styp := ''
is_amp := g.is_amp
g.is_amp = false
if is_amp {
g.out.go_back(1) // delete the `&` already generated in `prefix_expr()
}
if g.is_shared {
mut shared_typ := node.typ.set_flag(.shared_f)
shared_styp = g.typ(shared_typ)
g.writeln('($shared_styp*)__dup_shared_map(&($shared_styp){.val = ')
} else if is_amp {
styp = g.typ(node.typ)
g.write('($styp*)memdup(ADDR($styp, ')
}
if size > 0 {
if value_typ.kind == .function {
g.write('new_map_init_2($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof(voidptr), _MOV(($key_typ_str[$size]){')
} else {
g.write('new_map_init_2($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof($value_typ_str), _MOV(($key_typ_str[$size]){')
}
for expr in node.keys {
g.expr(expr)
g.write(', ')
}
if value_typ.kind == .function {
g.write('}), _MOV((voidptr[$size]){')
} else {
g.write('}), _MOV(($value_typ_str[$size]){')
}
for expr in node.vals {
g.expr(expr)
g.write(', ')
}
g.write('}))')
} else {
g.write('new_map_2(sizeof($key_typ_str), sizeof($value_typ_str), $hash_fn, $key_eq_fn, $clone_fn, $free_fn)')
}
if g.is_shared {
g.write('}, sizeof($shared_styp))')
} else if is_amp {
g.write('), sizeof($styp))')
}
}
fn (mut g Gen) select_expr(node ast.SelectExpr) {
is_expr := node.is_expr || g.inside_ternary > 0
cur_line := if is_expr {

View File

@ -354,14 +354,11 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
pub fn (g &Gen) unwrap_generic(typ table.Type) table.Type {
if typ.has_flag(.generic) {
sym := g.table.get_type_symbol(typ)
mut idx := 0
for i, generic_param in g.cur_fn.generic_params {
if generic_param.name == sym.name {
idx = i
break
return g.cur_generic_types[i].derive(typ).clear_flag(.generic)
}
}
return g.cur_generic_types[idx].derive(typ).clear_flag(.generic)
}
return typ
}

View File

@ -558,6 +558,13 @@ fn (mut g JsGen) expr(node ast.Expr) {
g.write("string('$text')")
}
ast.StructInit {
// TODO: once generic fns/unwrap_generic is implemented
// if node.unresolved {
// g.expr(ast.resolve_init(node, g.unwrap_generic(node.typ), g.table))
// } else {
// // `user := User{name: 'Bob'}`
// g.gen_struct_init(node)
// }
// `user := User{name: 'Bob'}`
g.gen_struct_init(node)
}
@ -1037,11 +1044,7 @@ fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) {
g.inc_indent()
g.write('return `$js_name {')
for i, field in node.fields {
g.write(if i == 0 {
' '
} else {
', '
})
g.write(if i == 0 { ' ' } else { ', ' })
match g.typ(field.typ).split('.').last() {
'string' { g.write('$field.name: "\${this["$field.name"].toString()}"') }
else { g.write('$field.name: \${this["$field.name"].toString()} ') }

View File

@ -409,7 +409,8 @@ fn (mut p Parser) struct_init(short_syntax bool) ast.StructInit {
p.check(.rcbr)
}
p.is_amp = saved_is_amp
node := ast.StructInit{
return ast.StructInit{
unresolved: typ.has_flag(.generic)
typ: typ
fields: fields
update_expr: update_expr
@ -419,7 +420,6 @@ fn (mut p Parser) struct_init(short_syntax bool) ast.StructInit {
is_short: no_keys
pre_comments: pre_comments
}
return node
}
fn (mut p Parser) interface_decl() ast.InterfaceDecl {

View File

@ -44,10 +44,27 @@ fn (v Foo) new<T>() T {
fn test_generic_method_with_map_type() {
foo := Foo{}
assert foo.new<map[string]string>() == map[string]string{}
mut a := foo.new<map[string]string>()
assert a == map[string]string{}
assert a.len == 0
a['a'] = 'a'
assert a.len == 1
assert a['a'] == 'a'
}
fn test_generic_method_with_array_type() {
foo := Foo{}
assert foo.new<[]string>() == []string{}
mut a := foo.new<[]string>()
assert a == []string{}
assert a.len == 0
a << 'a'
assert a.len == 1
assert a[0] == 'a'
}
fn test_generic_method_with_struct_type() {
foo := Foo{}
mut a := foo.new<Person>()
a.name = 'a'
assert a.name == 'a'
}

View File

@ -386,3 +386,25 @@ fn test_multi_generic_args() {
assert multi_generic_args("Super", 2021)
}
fn new<T>() T {
return T{}
}
fn test_generic_init() {
// array init
mut a := new<[]string>()
assert a.len == 0
a << 'a'
assert a.len == 1
assert a[0] == 'a'
// map init
mut b := new<map[string]string>()
assert b.len == 0
b['b'] = 'b'
assert b.len == 1
assert b['b'] == 'b'
// struct init
mut c := new<User>()
c.name = 'c'
assert c.name == 'c'
}