v/vlib/v/checker/struct.v

532 lines
18 KiB
V
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Copyright (c) 2019-2022 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
pub fn (mut c Checker) struct_decl(mut node ast.StructDecl) {
if node.language == .v && !c.is_builtin_mod {
c.check_valid_pascal_case(node.name, 'struct name', node.pos)
}
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 {
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
}
}
}
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)
}
}
for i, field in node.fields {
if field.typ == ast.any_type {
c.error('struct field cannot be the `any` type, use generics instead',
field.type_pos)
}
c.ensure_type_exists(field.typ, field.type_pos) or { return }
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)
}
}
field_sym := c.table.sym(field.typ)
if field_sym.kind == .function {
fn_info := field_sym.info as ast.FnType
c.ensure_type_exists(fn_info.func.return_type, fn_info.func.return_type_pos) or {
return
}
for param in fn_info.func.params {
c.ensure_type_exists(param.typ, param.type_pos) or { return }
}
}
}
if sym.kind == .struct_ {
info := sym.info as ast.Struct
if info.is_heap && !field.typ.is_ptr() {
struct_sym.info.is_heap = true
}
}
if field.has_default_expr {
c.expected_type = field.typ
mut field_expr_type := c.expr(field.default_expr)
if !field.typ.has_flag(.optional) {
c.check_expr_opt_call(field.default_expr, field_expr_type)
}
struct_sym.info.fields[i].default_expr_typ = field_expr_type
c.check_expected(field_expr_type, field.typ) or {
if sym.kind == .interface_
&& c.type_implements(field_expr_type, field.typ, field.pos) {
if !field_expr_type.is_ptr() && !field_expr_type.is_pointer()
&& !c.inside_unsafe {
field_expr_type_sym := c.table.sym(field_expr_type)
if field_expr_type_sym.kind != .interface_ {
c.mark_as_referenced(mut &node.fields[i].default_expr,
true)
}
}
} else {
c.error('incompatible initializer for field `$field.name`: $err.msg()',
field.default_expr.pos())
}
}
// Check for unnecessary inits like ` = 0` and ` = ''`
if field.typ.is_ptr() {
continue
}
if field.default_expr is 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)
}
} else if field.default_expr is ast.StringLiteral {
if field.default_expr.val == '' {
c.warn("unnecessary default value of '': struct fields are zeroed by default",
field.default_expr.pos)
}
} else if field.default_expr is ast.BoolLiteral {
if field.default_expr.val == false {
c.warn('unnecessary default value `false`: struct fields are zeroed by default',
field.default_expr.pos)
}
}
}
}
if node.generic_types.len == 0 && has_generic_types {
c.error('generic struct declaration must specify the generic type names, e.g. Foo<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
}
}
pub fn (mut c Checker) struct_init(mut node ast.StructInit) ast.Type {
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)
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 {
if c.table.cur_concrete_types.len == 0 {
c.error('generic struct init must specify type parameter, e.g. Foo<int>',
node.pos)
} else 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 && !isnil(c.table.cur_fn) {
for gtyp in node.generic_types {
gtyp_name := c.table.sym(gtyp).name
if 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 == node.generic_types.len
&& struct_sym.info.generic_types != node.generic_types {
c.table.replace_generic_type(node.typ, node.generic_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
}
}
// register generic struct type when current fn is generic fn
if !isnil(c.table.cur_fn) && 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)
}
c.ensure_type_exists(node.typ, node.pos) or {}
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 != .placeholder {
c.error('cannot initialize builtin type `$type_sym.name`', node.pos)
}
}
if type_sym.kind == .sum_type && node.fields.len == 1 {
sexpr := node.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 !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')) {
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[0].name == '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 && !isnil(c.table.cur_fn) && c.table.cur_fn.generic_names.len == 0 {
c.error('unknown struct `$type_sym.name`', node.pos)
return 0
}
match type_sym.kind {
.placeholder {
c.error('unknown struct: $type_sym.name', node.pos)
return ast.void_type
}
// 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.is_short {
exp_len := info.fields.len
got_len := node.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.is_short {
info_fields_sorted = info.fields.clone()
info_fields_sorted.sort(a.i < b.i)
}
mut inited_fields := []string{}
for i, mut field in node.fields {
mut field_info := ast.StructField{}
mut field_name := ''
if node.is_short {
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.fields[i].name = field_name
} else {
field_name = field.name
mut exists := true
field_info = c.table.find_field_with_embeds(type_sym, field_name) or {
exists = false
ast.StructField{}
}
if !exists {
c.error('unknown field `$field.name` in struct literal of type `$type_sym.name`',
field.pos)
continue
}
if field_name in inited_fields {
c.error('duplicate field name in struct literal: `$field_name`',
field.pos)
continue
}
}
mut expr_type := ast.Type(0)
mut expected_type := ast.Type(0)
inited_fields << field_name
field_type_sym := c.table.sym(field_info.typ)
expected_type = field_info.typ
c.expected_type = expected_type
expr_type = c.expr(field.expr)
if !field_info.typ.has_flag(.optional) {
expr_type = c.check_expr_opt_call(field.expr, expr_type)
}
expr_type_sym := c.table.sym(expr_type)
if field_type_sym.kind == .interface_ {
if c.type_implements(expr_type, field_info.typ, field.pos) {
if !expr_type.is_ptr() && !expr_type.is_pointer()
&& expr_type_sym.kind != .interface_ && !c.inside_unsafe {
c.mark_as_referenced(mut &field.expr, true)
}
}
} else if expr_type != ast.void_type && expr_type_sym.kind != .placeholder {
c.check_expected(c.unwrap_generic(expr_type), c.unwrap_generic(field_info.typ)) or {
c.error('cannot assign to field `$field_info.name`: $err.msg()',
field.pos)
}
}
if field_info.typ.has_flag(.shared_f) {
if !expr_type.has_flag(.shared_f) && expr_type.is_ptr() {
c.error('`shared` field must be initialized with `shared` or value',
field.pos)
}
} else {
if field_info.typ.is_ptr() && !expr_type.is_ptr() && !expr_type.is_pointer()
&& field.expr.str() != '0' {
c.error('reference field must be initialized with reference',
field.pos)
}
}
if field_type_sym.kind == .function && field_type_sym.language == .v {
pos := field.expr.pos()
if mut field.expr is ast.AnonFn {
if field.expr.decl.no_body {
c.error('cannot initialize the fn field with anonymous fn that does not have a body',
pos)
}
}
}
node.fields[i].typ = expr_type
node.fields[i].expected_type = field_info.typ
if field_info.typ.has_flag(.optional) {
c.error('field `$field_info.name` is optional, but initialization of optional fields currently unsupported',
field.pos)
}
if expr_type.is_ptr() && expected_type.is_ptr() {
if mut field.expr is ast.Ident {
if mut field.expr.obj is ast.Var {
mut obj := unsafe { &field.expr.obj }
if c.fn_scope != voidptr(0) {
obj = c.fn_scope.find_var(obj.name) or { obj }
}
if obj.is_stack_obj && !c.inside_unsafe {
sym := c.table.sym(obj.typ.set_nr_muls(0))
if !sym.is_heap() && !c.pref.translated && !c.file.is_translated {
suggestion := if sym.kind == .struct_ {
'declaring `$sym.name` as `[heap]`'
} else {
'wrapping the `$sym.name` object in a `struct` declared as `[heap]`'
}
c.error('`$field.expr.name` cannot be assigned outside `unsafe` blocks as it might refer to an object stored on stack. Consider ${suggestion}.',
field.expr.pos)
}
}
}
}
}
}
// Check uninitialized refs/sum types
for i, field in info.fields {
if field.name in inited_fields {
continue
}
if field.has_default_expr {
if field.default_expr_typ == 0 {
if 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 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) && !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)
}
// Do not allow empty uninitialized interfaces
sym := c.table.sym(field.typ)
mut has_noinit := false
for attr in field.attrs {
if attr.name == 'noinit' {
has_noinit = true
break
}
}
if sym.kind == .interface_ && (!has_noinit && sym.language != .js) {
// 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.is_short && !node.has_update_expr {
mut found := false
for init_field in node.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)
}
}
}
}
else {}
}
if node.has_update_expr {
update_type := c.expr(node.update_expr)
node.update_expr_type = update_type
if c.table.type_kind(update_type) != .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.error('struct `$from_sym.name` is not compatible with struct `$to_sym.name`',
node.update_expr.pos())
}
}
if !node.update_expr.is_lvalue() {
// cgen will repeat `update_expr` for each field
// so enforce an lvalue for efficiency
c.error('expression is not an lvalue', node.update_expr.pos())
}
}
return node.typ
}