mirror of
https://github.com/vlang/v.git
synced 2025-09-17 03:17:25 -04:00
checker: add error when initializing sumtype with struct as first type (#22067)
This commit is contained in:
parent
76d37f6595
commit
d58cd9c672
@ -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 {
|
||||
@ -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) {
|
||||
|
21
vlib/v/checker/tests/sumtype_init_with_ref_fields_err.out
Normal file
21
vlib/v/checker/tests/sumtype_init_with_ref_fields_err.out
Normal file
@ -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
vlib/v/checker/tests/sumtype_init_with_ref_fields_err.vv
Normal file
11
vlib/v/checker/tests/sumtype_init_with_ref_fields_err.vv
Normal file
@ -0,0 +1,11 @@
|
||||
struct Node {
|
||||
parent &Node
|
||||
left &Node
|
||||
right &Node
|
||||
}
|
||||
type Sumtype = Node | int
|
||||
|
||||
fn main() {
|
||||
s := Sumtype{}
|
||||
dump(s)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user