Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

checker: add error when initializing sumtype with struct as first type #22067

Merged
merged 3 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
Loading