Skip to content

Commit

Permalink
checker: add error when initializing sumtype with struct as first type (
Browse files Browse the repository at this point in the history
  • Loading branch information
felipensp authored Aug 17, 2024
1 parent 76d37f6 commit d58cd9c
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 134 deletions.
280 changes: 146 additions & 134 deletions vlib/v/checker/struct.v
Original file line number Diff line number Diff line change
Expand Up @@ -772,142 +772,19 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.',
init_field.expr.pos.extend(init_field.expr.expr.pos()))
}
}
// Check uninitialized refs/sum types
// The variable `fields` contains two parts, the first part is the same as info.fields,
// 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.
mut fields := c.table.struct_fields(type_sym)
mut checked_types := []ast.Type{}

for i, mut field in fields {
if field.name in inited_fields {
if c.mod != type_sym.mod {
if !field.is_pub {
parts := type_sym.name.split('.')
for init_field in node.init_fields {
if field.name == init_field.name {
mod_type := if parts.len > 1 {
parts#[-2..].join('.')
} else {
parts.last()
}
c.error('cannot access private field `${field.name}` on `${mod_type}`',
init_field.pos)
break
}
}
}
if field.is_deprecated {
for init_field in node.init_fields {
if field.name == init_field.name {
c.deprecate('field', field.name, field.attrs, init_field.pos)
break
}
}
}
}
continue
}
sym := c.table.sym(field.typ)
if field.name != '' && field.name[0].is_capital() && sym.info is ast.Struct {
// struct embeds
continue
}
if field.has_default_expr {
if i < info.fields.len && field.default_expr_typ == 0 {
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)
}
} else if field.default_expr.is_nil() {
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
} else if type_sym.info is ast.Struct && type_sym.info.is_anon {
c.expected_type = field.typ
field.default_expr_typ = c.expr(mut field.default_expr)
info.fields[i].default_expr_typ = field.default_expr_typ
}
}
}
continue
}
if field.typ.is_ptr() && !field.typ.has_flag(.shared_f)
&& !field.typ.has_flag(.option) && !node.has_update_expr && !c.pref.translated
&& !c.file.is_translated {
c.error('reference field `${type_sym.name}.${field.name}` must be initialized',
node.pos)
continue
}
if !field.typ.has_flag(.option) {
if sym.kind == .struct_ {
c.check_ref_fields_initialized(sym, mut checked_types, '${type_sym.name}.${field.name}',
node.pos)
} else if sym.kind == .alias {
parent_sym := c.table.sym((sym.info as ast.Alias).parent_type)
if parent_sym.kind == .struct_ {
c.check_ref_fields_initialized(parent_sym, mut checked_types,
'${type_sym.name}.${field.name}', node.pos)
}
}
}
// Do not allow empty uninitialized interfaces
if sym.kind == .interface_ && !node.has_update_expr && !field.typ.has_flag(.option)
&& sym.language != .js && !field.attrs.contains('noinit') {
// TODO: should be an error instead, but first `ui` needs updating.
c.note('interface field `${type_sym.name}.${field.name}` must be initialized',
node.pos)
}
// Do not allow empty uninitialized sum types
/*
sym := c.table.sym(field.typ)
if sym.kind == .sum_type {
c.warn('sum type field `${type_sym.name}.$field.name` must be initialized',
node.pos)
}
*/
// Check for `@[required]` struct attr
if !node.no_keys && !node.has_update_expr && field.attrs.contains('required')
&& node.init_fields.all(it.name != field.name) {
c.error('field `${type_sym.name}.${field.name}` must be initialized',
node.pos)
}
if !node.has_update_expr && !field.has_default_expr && !field.typ.is_ptr()
&& !field.typ.has_flag(.option) {
field_final_sym := c.table.final_sym(field.typ)
if field_final_sym.kind == .struct_ {
mut zero_struct_init := ast.StructInit{
pos: node.pos
typ: field.typ
}
if field.is_part_of_union {
if field.name in inited_fields {
// fields that are part of an union, should only be checked, when they are explicitly initialised
c.struct_init(mut zero_struct_init, true, mut inited_fields)
}
} else {
c.struct_init(mut zero_struct_init, true, mut inited_fields)
}
}
}
}
for embed in info.embeds {
mut zero_struct_init := ast.StructInit{
pos: node.pos
typ: embed
}
c.struct_init(mut zero_struct_init, true, mut inited_fields)
}
c.check_uninitialized_struct_fields_and_embeds(node, type_sym, mut info, mut
inited_fields)
// println('>> checked_types.len: $checked_types.len | checked_types: $checked_types | type_sym: $type_sym.name ')
}
.sum_type {
first_typ := (type_sym.info as ast.SumType).variants[0]
first_sym := c.table.final_sym(first_typ)
if first_sym.kind == .struct_ {
mut info := first_sym.info as ast.Struct
c.check_uninitialized_struct_fields_and_embeds(node, first_sym, mut info, mut
inited_fields)
}
}
else {}
}
if node.has_update_expr {
Expand Down Expand Up @@ -960,6 +837,141 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.',
return node.typ
}

// Check uninitialized refs/sum types
// The variable `fields` contains two parts, the first part is the same as info.fields,
// 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.
fn (mut c Checker) check_uninitialized_struct_fields_and_embeds(node ast.StructInit, type_sym ast.TypeSymbol, mut info ast.Struct, mut inited_fields []string) {
mut fields := c.table.struct_fields(type_sym)
mut checked_types := []ast.Type{}

for i, mut field in fields {
if field.name in inited_fields {
if c.mod != type_sym.mod {
if !field.is_pub {
parts := type_sym.name.split('.')
for init_field in node.init_fields {
if field.name == init_field.name {
mod_type := if parts.len > 1 {
parts#[-2..].join('.')
} else {
parts.last()
}
c.error('cannot access private field `${field.name}` on `${mod_type}`',
init_field.pos)
break
}
}
}
if field.is_deprecated {
for init_field in node.init_fields {
if field.name == init_field.name {
c.deprecate('field', field.name, field.attrs, init_field.pos)
break
}
}
}
}
continue
}
sym := c.table.sym(field.typ)
if field.name != '' && field.name[0].is_capital() && sym.info is ast.Struct {
// struct embeds
continue
}
if field.has_default_expr {
if i < info.fields.len && field.default_expr_typ == 0 {
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)
}
} else if field.default_expr.is_nil() {
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
} else if type_sym.info is ast.Struct && type_sym.info.is_anon {
c.expected_type = field.typ
field.default_expr_typ = c.expr(mut field.default_expr)
info.fields[i].default_expr_typ = field.default_expr_typ
}
}
}
continue
}
if field.typ.is_ptr() && !field.typ.has_flag(.shared_f) && !field.typ.has_flag(.option)
&& !node.has_update_expr && !c.pref.translated && !c.file.is_translated {
c.error('reference field `${type_sym.name}.${field.name}` must be initialized',
node.pos)
continue
}
if !field.typ.has_flag(.option) {
if sym.kind == .struct_ {
c.check_ref_fields_initialized(sym, mut checked_types, '${type_sym.name}.${field.name}',
node.pos)
} else if sym.kind == .alias {
parent_sym := c.table.sym((sym.info as ast.Alias).parent_type)
if parent_sym.kind == .struct_ {
c.check_ref_fields_initialized(parent_sym, mut checked_types, '${type_sym.name}.${field.name}',
node.pos)
}
}
}
// Do not allow empty uninitialized interfaces
if sym.kind == .interface_ && !node.has_update_expr && !field.typ.has_flag(.option)
&& sym.language != .js && !field.attrs.contains('noinit') {
// TODO: should be an error instead, but first `ui` needs updating.
c.note('interface field `${type_sym.name}.${field.name}` must be initialized',
node.pos)
}
// Do not allow empty uninitialized sum types
/*
sym := c.table.sym(field.typ)
if sym.kind == .sum_type {
c.warn('sum type field `${type_sym.name}.$field.name` must be initialized',
node.pos)
}
*/
// Check for `@[required]` struct attr
if !node.no_keys && !node.has_update_expr && field.attrs.contains('required')
&& node.init_fields.all(it.name != field.name) {
c.error('field `${type_sym.name}.${field.name}` must be initialized', node.pos)
}
if !node.has_update_expr && !field.has_default_expr && !field.typ.is_ptr()
&& !field.typ.has_flag(.option) {
field_final_sym := c.table.final_sym(field.typ)
if field_final_sym.kind == .struct_ {
mut zero_struct_init := ast.StructInit{
pos: node.pos
typ: field.typ
}
if field.is_part_of_union {
if field.name in inited_fields {
// fields that are part of an union, should only be checked, when they are explicitly initialised
c.struct_init(mut zero_struct_init, true, mut inited_fields)
}
} else {
c.struct_init(mut zero_struct_init, true, mut inited_fields)
}
}
}
}

for embed in info.embeds {
mut zero_struct_init := ast.StructInit{
pos: node.pos
typ: embed
}
c.struct_init(mut zero_struct_init, true, mut inited_fields)
}
}

// Recursively check whether the struct type field is initialized
fn (mut c Checker) check_ref_fields_initialized(struct_sym &ast.TypeSymbol, mut checked_types []ast.Type,
linked_name string, pos &token.Pos) {
Expand Down
21 changes: 21 additions & 0 deletions vlib/v/checker/tests/sumtype_init_with_ref_fields_err.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
vlib/v/checker/tests/sumtype_init_with_ref_fields_err.vv:9:10: error: reference field `Node.parent` must be initialized
7 |
8 | fn main() {
9 | s := Sumtype{}
| ~~~~~~~~~
10 | dump(s)
11 | }
vlib/v/checker/tests/sumtype_init_with_ref_fields_err.vv:9:10: error: reference field `Node.left` must be initialized
7 |
8 | fn main() {
9 | s := Sumtype{}
| ~~~~~~~~~
10 | dump(s)
11 | }
vlib/v/checker/tests/sumtype_init_with_ref_fields_err.vv:9:10: error: reference field `Node.right` must be initialized
7 |
8 | fn main() {
9 | s := Sumtype{}
| ~~~~~~~~~
10 | dump(s)
11 | }
11 changes: 11 additions & 0 deletions vlib/v/checker/tests/sumtype_init_with_ref_fields_err.vv
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
struct Node {
parent &Node
left &Node
right &Node
}
type Sumtype = Node | int

fn main() {
s := Sumtype{}
dump(s)
}

0 comments on commit d58cd9c

Please sign in to comment.