mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
402 lines
14 KiB
V
402 lines
14 KiB
V
// 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
|
||
}
|
||
}
|
||
}
|
||
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)
|
||
}
|
||
}
|
||
}
|
||
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.position())
|
||
}
|
||
}
|
||
// 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)
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn (mut c Checker) struct_init(mut node ast.StructInit) ast.Type {
|
||
if node.typ == ast.void_type {
|
||
// Short syntax `({foo: bar})`
|
||
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 {
|
||
if struct_sym.info.generic_types.len > 0 && struct_sym.info.concrete_types.len == 0
|
||
&& c.table.cur_concrete_types.len == 0 {
|
||
c.error('generic struct init must specify type parameter, e.g. Foo<int>',
|
||
node.pos)
|
||
}
|
||
} 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 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() {
|
||
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_public && 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 && 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 {
|
||
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 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[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()
|
||
&& !expr_type.is_number() {
|
||
c.error('reference field must be initialized with reference',
|
||
field.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 {
|
||
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 field in info.fields {
|
||
if field.has_default_expr || field.name in inited_fields {
|
||
continue
|
||
}
|
||
if field.typ.is_ptr() && !field.typ.has_flag(.shared_f) && !node.has_update_expr
|
||
&& !c.pref.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.position())
|
||
} 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.position())
|
||
}
|
||
}
|
||
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.position())
|
||
}
|
||
}
|
||
return node.typ
|
||
}
|