v/vlib/v/checker/struct.v

835 lines
30 KiB
V
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module checker
import v.ast
import v.util
fn (mut c Checker) struct_decl(mut node ast.StructDecl) {
util.timing_start(@METHOD)
defer {
util.timing_measure_cumulative(@METHOD)
}
mut struct_sym, struct_typ_idx := c.table.find_sym_and_type_idx(node.name)
mut has_generic_types := false
if mut struct_sym.info is ast.Struct {
if node.language == .v && !c.is_builtin_mod && !struct_sym.info.is_anon {
c.check_valid_pascal_case(node.name, 'struct name', node.pos)
}
for embed in node.embeds {
if embed.typ.has_flag(.generic) {
has_generic_types = true
}
embed_sym := c.table.sym(embed.typ)
if embed_sym.kind != .struct_ {
c.error('`${embed_sym.name}` is not a struct', embed.pos)
} else {
info := embed_sym.info as ast.Struct
if info.is_heap && !embed.typ.is_ptr() {
struct_sym.info.is_heap = true
}
}
// Ensure each generic type of the embed was declared in the struct's definition
if node.generic_types.len > 0 && embed.typ.has_flag(.generic) {
embed_generic_names := c.table.generic_type_names(embed.typ)
node_generic_names := node.generic_types.map(c.table.type_to_str(it))
for name in embed_generic_names {
if name !in node_generic_names {
struct_generic_names := node_generic_names.join(', ')
c.error('generic type name `${name}` is not mentioned in struct `${node.name}[${struct_generic_names}]`',
embed.pos)
}
}
}
}
if struct_sym.info.is_minify {
node.fields.sort_with_compare(minify_sort_fn)
struct_sym.info.fields.sort_with_compare(minify_sort_fn)
}
for attr in node.attrs {
if attr.name == 'typedef' && node.language != .c {
c.error('`typedef` attribute can only be used with C structs', node.pos)
}
}
// Update .default_expr_typ for all fields in the struct:
util.timing_start('Checker.struct setting default_expr_typ')
old_expected_type := c.expected_type
for mut field in node.fields {
// when the field has the same type that the struct itself (recursive)
if field.typ.clear_flag(.option).set_nr_muls(0) == struct_typ_idx {
for mut symfield in struct_sym.info.fields {
if symfield.name == field.name {
// only ?&Struct is allowed to be recursive
if field.typ.is_ptr() {
symfield.is_recursive = true
} else {
c.error('recursive struct is only possible with optional pointer (e.g. ?&${c.table.type_to_str(field.typ.clear_flag(.option))})',
node.pos)
}
}
}
}
// Do not allow uninitialized `fn` fields, or force `?fn`
// (allow them in `C.` structs)
if !c.is_builtin_mod && node.language == .v {
sym := c.table.sym(field.typ)
if sym.kind == .function {
if !field.typ.has_flag(.option) && !field.has_default_expr
&& field.attrs.filter(it.name == 'required').len == 0 {
error_msg := 'uninitialized `fn` struct fields are not allowed, since they can result in segfaults; use `?fn` or `[required]` or initialize the field with `=` (if you absolutely want to have unsafe function pointers, use `= unsafe { nil }`)'
c.note(error_msg, field.pos)
}
}
}
if field.has_default_expr {
c.expected_type = field.typ
field.default_expr_typ = c.expr(mut field.default_expr)
for mut symfield in struct_sym.info.fields {
if symfield.name == field.name {
symfield.default_expr_typ = field.default_expr_typ
break
}
}
}
}
c.expected_type = old_expected_type
util.timing_measure_cumulative('Checker.struct setting default_expr_typ')
for i, field in node.fields {
if field.typ.has_flag(.result) {
c.error('struct field does not support storing Result', field.option_pos)
}
if !c.ensure_type_exists(field.typ, field.type_pos) {
continue
}
if !c.ensure_generic_type_specify_type_names(field.typ, field.type_pos) {
continue
}
if field.typ.has_flag(.generic) {
has_generic_types = true
}
if node.language == .v {
c.check_valid_snake_case(field.name, 'field name', field.pos)
}
sym := c.table.sym(field.typ)
for j in 0 .. i {
if field.name == node.fields[j].name {
c.error('field name `${field.name}` duplicate', field.pos)
}
}
if field.typ != 0 {
if !field.typ.is_ptr() {
if c.table.unaliased_type(field.typ) == struct_typ_idx {
c.error('field `${field.name}` is part of `${node.name}`, they can not both have the same type',
field.type_pos)
}
}
}
if sym.kind == .struct_ {
info := sym.info as ast.Struct
if info.is_heap && !field.typ.is_ptr() {
struct_sym.info.is_heap = true
}
for ct in info.concrete_types {
ct_sym := c.table.sym(ct)
if ct_sym.kind == .placeholder {
c.error('unknown type `${ct_sym.name}`', field.type_pos)
}
}
}
if sym.kind == .multi_return {
c.error('cannot use multi return as field type', field.type_pos)
}
if sym.kind == .none_ {
c.error('cannot use `none` as field type', field.type_pos)
}
if sym.kind == .map {
info := sym.map_info()
if info.value_type.has_flag(.result) {
c.error('cannot use Result type as map value type', field.type_pos)
}
}
if field.has_default_expr {
c.expected_type = field.typ
if !field.typ.has_flag(.option) && !field.typ.has_flag(.result) {
c.check_expr_opt_call(field.default_expr, field.default_expr_typ)
}
interface_implemented := sym.kind == .interface_
&& c.type_implements(field.default_expr_typ, field.typ, field.pos)
c.check_expected(field.default_expr_typ, field.typ) or {
if sym.kind == .interface_ && interface_implemented {
if !c.inside_unsafe && !field.default_expr_typ.is_any_kind_of_pointer() {
if c.table.sym(field.default_expr_typ).kind != .interface_ {
c.mark_as_referenced(mut &node.fields[i].default_expr,
true)
}
}
} else if c.table.final_sym(field.typ).kind == .function
&& field.default_expr_typ.is_pointer() {
continue
} else {
c.error('incompatible initializer for field `${field.name}`: ${err.msg()}',
field.default_expr.pos())
}
}
if field.default_expr.is_nil() {
if !field.typ.is_any_kind_of_pointer()
&& c.table.sym(field.typ).kind != .function {
c.error('cannot assign `nil` to a non-pointer field', field.type_pos)
}
}
// Check for unnecessary inits like ` = 0` and ` = ''`
if field.typ.is_ptr() {
if field.default_expr is ast.IntegerLiteral {
if !c.inside_unsafe && !c.is_builtin_mod && field.default_expr.val == '0' {
c.error('default value of `0` for references can only be used inside `unsafe`',
field.default_expr.pos)
}
}
if field.typ.has_flag(.option) && field.default_expr is ast.None {
c.warn('unnecessary default value of `none`: struct fields are zeroed by default',
field.default_expr.pos)
}
continue
}
if field.typ in ast.unsigned_integer_type_idxs {
if field.default_expr is ast.IntegerLiteral {
if field.default_expr.val[0] == `-` {
c.error('cannot assign negative value to unsigned integer type',
field.default_expr.pos)
}
}
}
if field.typ.has_flag(.option) {
if field.default_expr is ast.None {
c.warn('unnecessary default value of `none`: struct fields are zeroed by default',
field.default_expr.pos)
}
} else if field.typ.has_flag(.result) {
// struct field does not support result. Nothing to do
} else {
match field.default_expr {
ast.IntegerLiteral {
if field.default_expr.val == '0' {
c.warn('unnecessary default value of `0`: struct fields are zeroed by default',
field.default_expr.pos)
}
}
ast.StringLiteral {
if field.default_expr.val == '' {
c.warn("unnecessary default value of '': struct fields are zeroed by default",
field.default_expr.pos)
}
}
ast.BoolLiteral {
if field.default_expr.val == false {
c.warn('unnecessary default value `false`: struct fields are zeroed by default',
field.default_expr.pos)
}
}
else {}
}
}
}
// Ensure each generic type of the field was declared in the struct's definition
if node.generic_types.len > 0 && field.typ.has_flag(.generic) {
field_generic_names := c.table.generic_type_names(field.typ)
node_generic_names := node.generic_types.map(c.table.type_to_str(it))
for name in field_generic_names {
if name !in node_generic_names {
struct_generic_names := node_generic_names.join(', ')
c.error('generic type name `${name}` is not mentioned in struct `${node.name}[${struct_generic_names}]`',
field.type_pos)
}
}
}
}
if node.generic_types.len == 0 && has_generic_types {
c.error('generic struct `${node.name}` declaration must specify the generic type names, e.g. ${node.name}[T]',
node.pos)
}
}
}
fn minify_sort_fn(a &ast.StructField, b &ast.StructField) int {
if a.typ == b.typ {
return 0
}
// push all bool fields to the end of the struct
if a.typ == ast.bool_type_idx {
if b.typ == ast.bool_type_idx {
return 0
}
return 1
} else if b.typ == ast.bool_type_idx {
return -1
}
mut t := global_table
a_sym := t.sym(a.typ)
b_sym := t.sym(b.typ)
// push all non-flag enums to the end too, just before the bool fields
// TODO: support enums with custom field values as well
if a_sym.info is ast.Enum {
if !a_sym.info.is_flag && !a_sym.info.uses_exprs {
if b_sym.kind == .enum_ {
a_nr_vals := (a_sym.info as ast.Enum).vals.len
b_nr_vals := (b_sym.info as ast.Enum).vals.len
return if a_nr_vals > b_nr_vals {
-1
} else if a_nr_vals < b_nr_vals {
1
} else {
0
}
}
return 1
}
} else if b_sym.info is ast.Enum {
if !b_sym.info.is_flag && !b_sym.info.uses_exprs {
return -1
}
}
a_size, a_align := t.type_size(a.typ)
b_size, b_align := t.type_size(b.typ)
return if a_align > b_align {
-1
} else if a_align < b_align {
1
} else if a_size > b_size {
-1
} else if a_size < b_size {
1
} else {
0
}
}
fn (mut c Checker) struct_init(mut node ast.StructInit, is_field_zero_struct_init bool, mut inited_fields []string) ast.Type {
util.timing_start(@METHOD)
defer {
util.timing_measure_cumulative(@METHOD)
}
if node.typ == ast.void_type {
// short syntax `foo(key:val, key2:val2)`
if c.expected_type == ast.void_type {
c.error('unexpected short struct syntax', node.pos)
return ast.void_type
}
sym := c.table.sym(c.expected_type)
if sym.kind == .array {
node.typ = c.table.value_type(c.expected_type)
} else {
node.typ = c.expected_type
}
}
struct_sym := c.table.sym(node.typ)
mut old_inside_generic_struct_init := false
mut old_cur_struct_generic_types := []ast.Type{}
mut old_cur_struct_concrete_types := []ast.Type{}
if struct_sym.info is ast.Struct {
// check if the generic param types have been defined
for ct in struct_sym.info.concrete_types {
ct_sym := c.table.sym(ct)
if ct_sym.kind == .placeholder {
c.error('unknown type `${ct_sym.name}`', node.pos)
}
}
if struct_sym.info.generic_types.len > 0 && struct_sym.info.concrete_types.len == 0
&& !node.is_short_syntax && c.table.cur_concrete_types.len != 0
&& !is_field_zero_struct_init {
if node.generic_types.len == 0 {
c.error('generic struct init must specify type parameter, e.g. Foo[T]',
node.pos)
} else if node.generic_types.len > 0
&& node.generic_types.len != struct_sym.info.generic_types.len {
c.error('generic struct init expects ${struct_sym.info.generic_types.len} generic parameter, but got ${node.generic_types.len}',
node.pos)
} else if node.generic_types.len > 0 && c.table.cur_fn != unsafe { nil } {
for gtyp in node.generic_types {
if !gtyp.has_flag(.generic) {
continue
}
gtyp_name := c.table.sym(gtyp).name
if gtyp_name.len == 1 && gtyp_name !in c.table.cur_fn.generic_names {
cur_generic_names := '(' + c.table.cur_fn.generic_names.join(',') + ')'
c.error('generic struct init type parameter `${gtyp_name}` must be within the parameters `${cur_generic_names}` of the current generic function',
node.pos)
break
}
}
}
}
if node.generic_types.len > 0 && struct_sym.info.generic_types.len == 0 {
c.error('a non generic struct `${node.typ_str}` used like a generic struct',
node.name_pos)
}
if struct_sym.info.generic_types.len > 0
&& struct_sym.info.generic_types.len == struct_sym.info.concrete_types.len {
old_inside_generic_struct_init = c.inside_generic_struct_init
old_cur_struct_generic_types = c.cur_struct_generic_types.clone()
old_cur_struct_concrete_types = c.cur_struct_concrete_types.clone()
c.inside_generic_struct_init = true
c.cur_struct_generic_types = struct_sym.info.generic_types.clone()
c.cur_struct_concrete_types = struct_sym.info.concrete_types.clone()
defer {
c.inside_generic_struct_init = old_inside_generic_struct_init
c.cur_struct_generic_types = old_cur_struct_generic_types
c.cur_struct_concrete_types = old_cur_struct_concrete_types
}
}
} else if struct_sym.info is ast.Alias {
parent_sym := c.table.sym(struct_sym.info.parent_type)
// e.g. ´x := MyMapAlias{}´, should be a cast to alias type ´x := MyMapAlias(map[...]...)´
if parent_sym.kind == .map {
alias_str := c.table.type_to_str(node.typ)
map_str := c.table.type_to_str(struct_sym.info.parent_type)
c.error('direct map alias init is not possible, use `${alias_str}(${map_str}{})` instead',
node.pos)
return ast.void_type
}
} else if struct_sym.info is ast.FnType {
c.error('functions must be defined, not instantiated like structs', node.pos)
}
// register generic struct type when current fn is generic fn
if c.table.cur_fn != unsafe { nil } && c.table.cur_fn.generic_names.len > 0 {
c.table.unwrap_generic_type(node.typ, c.table.cur_fn.generic_names, c.table.cur_concrete_types)
}
if !is_field_zero_struct_init {
c.ensure_type_exists(node.typ, node.pos)
}
type_sym := c.table.sym(node.typ)
if !c.inside_unsafe && type_sym.kind == .sum_type {
c.note('direct sum type init (`x := SumType{}`) will be removed soon', node.pos)
}
// Make sure the first letter is capital, do not allow e.g. `x := string{}`,
// but `x := T{}` is ok.
if !c.is_builtin_mod && !c.inside_unsafe && type_sym.language == .v
&& c.table.cur_concrete_types.len == 0 {
pos := type_sym.name.last_index('.') or { -1 }
first_letter := type_sym.name[pos + 1]
if !first_letter.is_capital()
&& (type_sym.kind != .struct_ || !(type_sym.info as ast.Struct).is_anon)
&& type_sym.kind != .placeholder {
c.error('cannot initialize builtin type `${type_sym.name}`', node.pos)
}
if type_sym.kind == .enum_ && !c.pref.translated && !c.file.is_translated {
c.error('cannot initialize enums', node.pos)
}
}
if type_sym.kind == .sum_type && node.init_fields.len == 1 {
sexpr := node.init_fields[0].expr.str()
c.error('cast to sum type using `${type_sym.name}(${sexpr})` not `${type_sym.name}{${sexpr}}`',
node.pos)
}
if type_sym.kind == .interface_ && type_sym.language != .js {
c.error('cannot instantiate interface `${type_sym.name}`', node.pos)
}
if type_sym.info is ast.Alias {
if type_sym.info.parent_type.is_number() {
c.error('cannot instantiate number type alias `${type_sym.name}`', node.pos)
return ast.void_type
}
}
// allow init structs from generic if they're private except the type is from builtin module
if !node.has_update_expr && !type_sym.is_pub && type_sym.kind != .placeholder
&& type_sym.language != .c && (type_sym.mod != c.mod && !(node.typ.has_flag(.generic)
&& type_sym.mod != 'builtin')) && !is_field_zero_struct_init {
c.error('type `${type_sym.name}` is private', node.pos)
}
if type_sym.kind == .struct_ {
info := type_sym.info as ast.Struct
if info.attrs.len > 0 && info.attrs.contains('noinit') && type_sym.mod != c.mod {
c.error('struct `${type_sym.name}` is declared with a `[noinit]` attribute, so ' +
'it cannot be initialized with `${type_sym.name}{}`', node.pos)
}
}
if type_sym.name.len == 1 && c.table.cur_fn != unsafe { nil }
&& c.table.cur_fn.generic_names.len == 0 {
c.error('unknown struct `${type_sym.name}`', node.pos)
return ast.void_type
}
match type_sym.kind {
.placeholder {
c.error('unknown struct: ${type_sym.name}', node.pos)
return ast.void_type
}
.any {
// `T{ foo: 22 }`
for mut init_field in node.init_fields {
init_field.typ = c.expr(mut init_field.expr)
init_field.expected_type = init_field.typ
}
sym := c.table.sym(c.unwrap_generic(node.typ))
if sym.kind == .struct_ {
info := sym.info as ast.Struct
if node.no_keys && node.init_fields.len != info.fields.len {
fname := if info.fields.len != 1 { 'fields' } else { 'field' }
c.error('initializing struct `${sym.name}` needs `${info.fields.len}` ${fname}, but got `${node.init_fields.len}`',
node.pos)
}
}
}
// string & array are also structs but .kind of string/array
.struct_, .string, .array, .alias {
mut info := ast.Struct{}
if type_sym.kind == .alias {
info_t := type_sym.info as ast.Alias
sym := c.table.sym(info_t.parent_type)
if sym.kind == .placeholder { // pending import symbol did not resolve
c.error('unknown struct: ${type_sym.name}', node.pos)
return ast.void_type
}
if sym.kind == .struct_ {
info = sym.info as ast.Struct
} else {
c.error('alias type name: ${sym.name} is not struct type', node.pos)
}
} else {
info = type_sym.info as ast.Struct
}
if node.no_keys {
exp_len := info.fields.len
got_len := node.init_fields.len
if exp_len != got_len && !c.pref.translated {
// XTODO remove !translated check
amount := if exp_len < got_len { 'many' } else { 'few' }
c.error('too ${amount} fields in `${type_sym.name}` literal (expecting ${exp_len}, got ${got_len})',
node.pos)
}
}
mut info_fields_sorted := []ast.StructField{}
if node.no_keys {
info_fields_sorted = info.fields.clone()
info_fields_sorted.sort(a.i < b.i)
}
for i, mut init_field in node.init_fields {
mut field_info := ast.StructField{}
mut field_name := ''
if node.no_keys {
if i >= info.fields.len {
// It doesn't make sense to check for fields that don't exist.
// We should just stop here.
break
}
field_info = info_fields_sorted[i]
field_name = field_info.name
node.init_fields[i].name = field_name
} else {
field_name = init_field.name
mut exists := true
field_info = c.table.find_field_with_embeds(type_sym, field_name) or {
exists = false
ast.StructField{}
}
if !exists {
existing_fields := c.table.struct_fields(type_sym).map(it.name)
c.error(util.new_suggestion(init_field.name, existing_fields).say('unknown field `${init_field.name}` in struct literal of type `${type_sym.name}`'),
init_field.pos)
continue
}
if field_name in inited_fields {
c.error('duplicate field name in struct literal: `${field_name}`',
init_field.pos)
continue
}
}
mut got_type := ast.Type(0)
mut exp_type := ast.Type(0)
inited_fields << field_name
exp_type = field_info.typ
exp_type_sym := c.table.sym(exp_type)
c.expected_type = exp_type
got_type = c.expr(mut init_field.expr)
got_type_sym := c.table.sym(got_type)
if got_type == ast.void_type {
c.error('`${init_field.expr}` (no value) used as value', init_field.pos)
}
if !exp_type.has_flag(.option) {
got_type = c.check_expr_opt_call(init_field.expr, got_type)
if got_type.has_flag(.option) {
c.error('cannot assign an Option value to a non-option struct field',
init_field.pos)
} else if got_type.has_flag(.result) {
c.error('cannot assign a Result value to a non-option struct field',
init_field.pos)
}
}
if exp_type_sym.kind == .voidptr && got_type_sym.kind == .struct_
&& !got_type.is_ptr() {
c.error('allocate on the heap for use in other functions', init_field.pos)
}
if exp_type_sym.kind == .interface_ {
if c.type_implements(got_type, exp_type, init_field.pos) {
if !c.inside_unsafe && got_type_sym.kind != .interface_
&& !got_type.is_any_kind_of_pointer() {
c.mark_as_referenced(mut &init_field.expr, true)
}
}
} else if got_type != ast.void_type && got_type_sym.kind != .placeholder
&& !exp_type.has_flag(.generic) {
c.check_expected(c.unwrap_generic(got_type), c.unwrap_generic(exp_type)) or {
c.error('cannot assign to field `${field_info.name}`: ${err.msg()}',
init_field.pos)
}
}
if exp_type.has_flag(.shared_f) {
if !got_type.has_flag(.shared_f) && got_type.is_ptr() {
c.error('`shared` field must be initialized with `shared` or value',
init_field.pos)
}
} else {
if exp_type.is_ptr() && !got_type.is_any_kind_of_pointer()
&& init_field.expr.str() != '0' && !exp_type.has_flag(.option) {
c.error('reference field must be initialized with reference',
init_field.pos)
} else if exp_type.is_pointer() && !got_type.is_any_kind_of_pointer()
&& !got_type.is_int() {
got_typ_str := c.table.type_to_str(got_type)
exp_typ_str := c.table.type_to_str(exp_type)
c.error('cannot assign to field `${field_info.name}`: expected a pointer `${exp_typ_str}`, but got `${got_typ_str}`',
init_field.pos)
}
}
node.init_fields[i].typ = got_type
node.init_fields[i].expected_type = exp_type
if got_type.is_ptr() && exp_type.is_ptr() {
if mut init_field.expr is ast.Ident {
c.fail_if_stack_struct_action_outside_unsafe(mut init_field.expr,
'assigned')
}
}
if field_info.typ in ast.unsigned_integer_type_idxs {
if mut init_field.expr is ast.IntegerLiteral {
if init_field.expr.val[0] == `-` {
c.error('cannot assign negative value to unsigned integer type',
init_field.expr.pos)
}
}
}
if exp_type_sym.kind == .struct_ && !(exp_type_sym.info as ast.Struct).is_anon
&& mut init_field.expr is ast.StructInit {
if init_field.expr.is_anon {
c.error('cannot assign anonymous `struct` to a typed `struct`',
init_field.expr.pos)
}
}
// all the fields of initialized embedded struct are ignored, they are considered initialized
sym := c.table.sym(init_field.typ)
if init_field.name.len > 0 && init_field.name[0].is_capital()
&& sym.kind == .struct_ && sym.language == .v {
struct_fields := c.table.struct_fields(sym)
for struct_field in struct_fields {
inited_fields << struct_field.name
}
}
expected_type_sym := c.table.final_sym(init_field.expected_type)
if expected_type_sym.kind in [.string, .array, .map, .array_fixed, .chan, .struct_]
&& init_field.expr.is_nil() && !init_field.expected_type.is_ptr()
&& mut init_field.expr is ast.UnsafeExpr {
c.error('cannot assign `nil` to struct field `${init_field.name}` with type `${expected_type_sym.name}`',
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 {
continue
}
sym := c.table.sym(field.typ)
if field.name.len > 0 && field.name[0].is_capital() && sym.info is ast.Struct
&& sym.language == .v {
// 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
}
}
}
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.warn('reference field `${type_sym.name}.${field.name}` must be initialized',
node.pos)
continue
}
if sym.kind == .struct_ {
c.check_ref_fields_initialized(sym, mut checked_types, '${type_sym.name}.${field.name}',
node)
} 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)
}
}
// Do not allow empty uninitialized interfaces
mut has_noinit := false
for attr in field.attrs {
if attr.name == 'noinit' {
has_noinit = true
break
}
}
if !field.typ.has_flag(.option) && sym.kind == .interface_
&& (!has_noinit && sym.language != .js) && !node.has_update_expr {
// 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 field.attrs.contains('required') && !node.no_keys && !node.has_update_expr {
mut found := false
for init_field in node.init_fields {
if field.name == init_field.name {
found = true
break
}
}
if !found {
c.error('field `${type_sym.name}.${field.name}` must be initialized',
node.pos)
}
}
if !node.has_update_expr && !field.has_default_expr && field.name !in inited_fields
&& !field.typ.is_ptr() && !field.typ.has_flag(.option)
&& c.table.final_sym(field.typ).kind == .struct_ {
mut zero_struct_init := ast.StructInit{
pos: node.pos
typ: field.typ
}
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)
}
// println('>> checked_types.len: $checked_types.len | checked_types: $checked_types | type_sym: $type_sym.name ')
}
else {}
}
if node.has_update_expr {
update_type := c.expr(mut node.update_expr)
node.update_expr_type = update_type
if node.update_expr is ast.ComptimeSelector {
c.error('cannot use struct update syntax in compile time expressions', node.update_expr_pos)
} else if c.table.final_sym(update_type).kind != .struct_ {
s := c.table.type_to_str(update_type)
c.error('expected struct, found `${s}`', node.update_expr.pos())
} else if update_type != node.typ {
from_sym := c.table.sym(update_type)
to_sym := c.table.sym(node.typ)
from_info := from_sym.info as ast.Struct
to_info := to_sym.info as ast.Struct
// TODO this check is too strict
if !c.check_struct_signature(from_info, to_info)
|| !c.check_struct_signature_init_fields(from_info, to_info, node) {
c.error('struct `${from_sym.name}` is not compatible with struct `${to_sym.name}`',
node.update_expr.pos())
}
}
}
if struct_sym.info is ast.Struct {
if struct_sym.info.generic_types.len > 0 && struct_sym.info.concrete_types.len == 0
&& c.table.cur_concrete_types.len == 0 {
concrete_types := c.infer_struct_generic_types(node.typ, node)
if concrete_types.len > 0 {
generic_names := struct_sym.info.generic_types.map(c.table.sym(it).name)
node.typ = c.table.unwrap_generic_type(node.typ, generic_names, concrete_types)
}
}
}
return node.typ
}
// 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, node &ast.StructInit) {
if c.pref.translated || c.file.is_translated {
return
}
if struct_sym.kind == .struct_ && struct_sym.language == .c
&& (struct_sym.info as ast.Struct).is_typedef {
return
}
fields := c.table.struct_fields(struct_sym)
for field in fields {
sym := c.table.sym(field.typ)
if field.name.len > 0 && field.name[0].is_capital() && sym.info is ast.Struct
&& sym.language == .v {
// an embedded struct field
continue
}
if field.typ.is_ptr() && !field.typ.has_flag(.shared_f) && !field.typ.has_flag(.option)
&& !field.has_default_expr {
c.warn('reference field `${linked_name}.${field.name}` must be initialized (part of struct `${struct_sym.name}`)',
node.pos)
continue
}
if sym.kind == .struct_ {
if sym.language == .c && (sym.info as ast.Struct).is_typedef {
continue
}
if field.typ in checked_types {
continue
}
checked_types << field.typ
c.check_ref_fields_initialized(sym, mut checked_types, '${linked_name}.${field.name}',
node)
} else if sym.kind == .alias {
psym := c.table.sym((sym.info as ast.Alias).parent_type)
if psym.kind == .struct_ {
checked_types << field.typ
c.check_ref_fields_initialized(psym, mut checked_types, '${linked_name}.${field.name}',
node)
}
}
}
}