checker: extract containers.v, struct.v, for.v from checker.v (#13012)
parent
41078bc438
commit
64f1ea6fe9
|
@ -514,394 +514,6 @@ pub fn (mut c Checker) expand_iface_embeds(idecl &ast.InterfaceDecl, level int,
|
|||
return ares
|
||||
}
|
||||
|
||||
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 := c.table.find_type(node.name) or { ast.invalid_type_symbol }
|
||||
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 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
|
||||
}
|
||||
|
||||
fn (mut c Checker) check_div_mod_by_zero(expr ast.Expr, op_kind token.Kind) {
|
||||
match mut expr {
|
||||
ast.FloatLiteral {
|
||||
|
@ -2120,194 +1732,6 @@ pub fn (mut c Checker) enum_decl(mut node ast.EnumDecl) {
|
|||
}
|
||||
}
|
||||
|
||||
fn (mut c Checker) check_array_init_para_type(para string, expr ast.Expr, pos token.Position) {
|
||||
sym := c.table.sym(c.expr(expr))
|
||||
if sym.kind !in [.int, .int_literal] {
|
||||
c.error('array $para needs to be an int', pos)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut c Checker) ensure_sumtype_array_has_default_value(node ast.ArrayInit) {
|
||||
sym := c.table.sym(node.elem_type)
|
||||
if sym.kind == .sum_type && !node.has_default {
|
||||
c.error('cannot initialize sum type array without default value', node.pos)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut c Checker) array_init(mut node ast.ArrayInit) ast.Type {
|
||||
mut elem_type := ast.void_type
|
||||
// []string - was set in parser
|
||||
if node.typ != ast.void_type {
|
||||
if node.exprs.len == 0 {
|
||||
if node.has_cap {
|
||||
c.check_array_init_para_type('cap', node.cap_expr, node.pos)
|
||||
}
|
||||
if node.has_len {
|
||||
c.check_array_init_para_type('len', node.len_expr, node.pos)
|
||||
}
|
||||
}
|
||||
if node.has_default {
|
||||
default_expr := node.default_expr
|
||||
default_typ := c.check_expr_opt_call(default_expr, c.expr(default_expr))
|
||||
c.check_expected(default_typ, node.elem_type) or {
|
||||
c.error(err.msg, default_expr.position())
|
||||
}
|
||||
}
|
||||
if node.has_len {
|
||||
if node.has_len && !node.has_default {
|
||||
elem_type_sym := c.table.sym(node.elem_type)
|
||||
if elem_type_sym.kind == .interface_ {
|
||||
c.error('cannot instantiate an array of interfaces without also giving a default `init:` value',
|
||||
node.len_expr.position())
|
||||
}
|
||||
}
|
||||
c.ensure_sumtype_array_has_default_value(node)
|
||||
}
|
||||
c.ensure_type_exists(node.elem_type, node.elem_type_pos) or {}
|
||||
if node.typ.has_flag(.generic) && c.table.cur_fn.generic_names.len == 0 {
|
||||
c.error('generic struct cannot use in non-generic function', node.pos)
|
||||
}
|
||||
return node.typ
|
||||
}
|
||||
if node.is_fixed {
|
||||
c.ensure_sumtype_array_has_default_value(node)
|
||||
c.ensure_type_exists(node.elem_type, node.elem_type_pos) or {}
|
||||
}
|
||||
// a = []
|
||||
if node.exprs.len == 0 {
|
||||
// a := fn_returing_opt_array() or { [] }
|
||||
if c.expected_type == ast.void_type && c.expected_or_type != ast.void_type {
|
||||
c.expected_type = c.expected_or_type
|
||||
}
|
||||
mut type_sym := c.table.sym(c.expected_type)
|
||||
if type_sym.kind != .array || type_sym.array_info().elem_type == ast.void_type {
|
||||
c.error('array_init: no type specified (maybe: `[]Type{}` instead of `[]`)',
|
||||
node.pos)
|
||||
return ast.void_type
|
||||
}
|
||||
// TODO: seperate errors once bug is fixed with `x := if expr { ... } else { ... }`
|
||||
// if c.expected_type == ast.void_type {
|
||||
// c.error('array_init: use `[]Type{}` instead of `[]`', node.pos)
|
||||
// return ast.void_type
|
||||
// }
|
||||
array_info := type_sym.array_info()
|
||||
node.elem_type = array_info.elem_type
|
||||
// clear optional flag incase of: `fn opt_arr ?[]int { return [] }`
|
||||
return c.expected_type.clear_flag(.optional)
|
||||
}
|
||||
// [1,2,3]
|
||||
if node.exprs.len > 0 && node.elem_type == ast.void_type {
|
||||
mut expected_value_type := ast.void_type
|
||||
mut expecting_interface_array := false
|
||||
if c.expected_type != 0 {
|
||||
expected_value_type = c.table.value_type(c.expected_type)
|
||||
if c.table.sym(expected_value_type).kind == .interface_ {
|
||||
// Array of interfaces? (`[dog, cat]`) Save the interface type (`Animal`)
|
||||
expecting_interface_array = true
|
||||
}
|
||||
}
|
||||
// expecting_interface_array := c.expected_type != 0 &&
|
||||
// c.table.sym(c.table.value_type(c.expected_type)).kind == .interface_
|
||||
//
|
||||
// if expecting_interface_array {
|
||||
// println('ex $c.expected_type')
|
||||
// }
|
||||
for i, mut expr in node.exprs {
|
||||
typ := c.check_expr_opt_call(expr, c.expr(expr))
|
||||
node.expr_types << typ
|
||||
// The first element's type
|
||||
if expecting_interface_array {
|
||||
if i == 0 {
|
||||
elem_type = expected_value_type
|
||||
c.expected_type = elem_type
|
||||
c.type_implements(typ, elem_type, expr.position())
|
||||
}
|
||||
if !typ.is_ptr() && !typ.is_pointer() && !c.inside_unsafe {
|
||||
typ_sym := c.table.sym(typ)
|
||||
if typ_sym.kind != .interface_ {
|
||||
c.mark_as_referenced(mut &expr, true)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
// The first element's type
|
||||
if i == 0 {
|
||||
if expr.is_auto_deref_var() {
|
||||
elem_type = c.table.mktyp(typ.deref())
|
||||
} else {
|
||||
elem_type = c.table.mktyp(typ)
|
||||
}
|
||||
c.expected_type = elem_type
|
||||
continue
|
||||
}
|
||||
if expr !is ast.TypeNode {
|
||||
c.check_expected(typ, elem_type) or {
|
||||
c.error('invalid array element: $err.msg', expr.position())
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.is_fixed {
|
||||
idx := c.table.find_or_register_array_fixed(elem_type, node.exprs.len, ast.empty_expr())
|
||||
if elem_type.has_flag(.generic) {
|
||||
node.typ = ast.new_type(idx).set_flag(.generic)
|
||||
} else {
|
||||
node.typ = ast.new_type(idx)
|
||||
}
|
||||
} else {
|
||||
idx := c.table.find_or_register_array(elem_type)
|
||||
if elem_type.has_flag(.generic) {
|
||||
node.typ = ast.new_type(idx).set_flag(.generic)
|
||||
} else {
|
||||
node.typ = ast.new_type(idx)
|
||||
}
|
||||
}
|
||||
node.elem_type = elem_type
|
||||
} else if node.is_fixed && node.exprs.len == 1 && node.elem_type != ast.void_type {
|
||||
// [50]byte
|
||||
mut fixed_size := i64(0)
|
||||
init_expr := node.exprs[0]
|
||||
c.expr(init_expr)
|
||||
match init_expr {
|
||||
ast.IntegerLiteral {
|
||||
fixed_size = init_expr.val.int()
|
||||
}
|
||||
ast.Ident {
|
||||
if init_expr.obj is ast.ConstField {
|
||||
if comptime_value := c.eval_comptime_const_expr(init_expr.obj.expr,
|
||||
0)
|
||||
{
|
||||
fixed_size = comptime_value.i64() or { fixed_size }
|
||||
}
|
||||
} else {
|
||||
c.error('non-constant array bound `$init_expr.name`', init_expr.pos)
|
||||
}
|
||||
}
|
||||
ast.InfixExpr {
|
||||
if comptime_value := c.eval_comptime_const_expr(init_expr, 0) {
|
||||
fixed_size = comptime_value.i64() or { fixed_size }
|
||||
}
|
||||
}
|
||||
else {
|
||||
c.error('expecting `int` for fixed size', node.pos)
|
||||
}
|
||||
}
|
||||
if fixed_size <= 0 {
|
||||
c.error('fixed size cannot be zero or negative (fixed_size: $fixed_size)',
|
||||
init_expr.position())
|
||||
}
|
||||
idx := c.table.find_or_register_array_fixed(node.elem_type, int(fixed_size), init_expr)
|
||||
if node.elem_type.has_flag(.generic) {
|
||||
node.typ = ast.new_type(idx).set_flag(.generic)
|
||||
} else {
|
||||
node.typ = ast.new_type(idx)
|
||||
}
|
||||
if node.has_default {
|
||||
c.expr(node.default_expr)
|
||||
}
|
||||
}
|
||||
return node.typ
|
||||
}
|
||||
|
||||
[inline]
|
||||
fn (mut c Checker) check_loop_label(label string, pos token.Position) {
|
||||
if label.len == 0 {
|
||||
|
@ -2521,173 +1945,6 @@ fn (mut c Checker) branch_stmt(node ast.BranchStmt) {
|
|||
}
|
||||
}
|
||||
|
||||
fn (mut c Checker) for_c_stmt(node ast.ForCStmt) {
|
||||
c.in_for_count++
|
||||
prev_loop_label := c.loop_label
|
||||
if node.has_init {
|
||||
c.stmt(node.init)
|
||||
}
|
||||
c.expr(node.cond)
|
||||
if node.has_inc {
|
||||
c.stmt(node.inc)
|
||||
}
|
||||
c.check_loop_label(node.label, node.pos)
|
||||
c.stmts(node.stmts)
|
||||
c.loop_label = prev_loop_label
|
||||
c.in_for_count--
|
||||
}
|
||||
|
||||
fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) {
|
||||
c.in_for_count++
|
||||
prev_loop_label := c.loop_label
|
||||
typ := c.expr(node.cond)
|
||||
typ_idx := typ.idx()
|
||||
if node.key_var.len > 0 && node.key_var != '_' {
|
||||
c.check_valid_snake_case(node.key_var, 'variable name', node.pos)
|
||||
}
|
||||
if node.val_var.len > 0 && node.val_var != '_' {
|
||||
c.check_valid_snake_case(node.val_var, 'variable name', node.pos)
|
||||
}
|
||||
if node.is_range {
|
||||
high_type := c.expr(node.high)
|
||||
high_type_idx := high_type.idx()
|
||||
if typ_idx in ast.integer_type_idxs && high_type_idx !in ast.integer_type_idxs {
|
||||
c.error('range types do not match', node.cond.position())
|
||||
} else if typ_idx in ast.float_type_idxs || high_type_idx in ast.float_type_idxs {
|
||||
c.error('range type can not be float', node.cond.position())
|
||||
} else if typ_idx == ast.bool_type_idx || high_type_idx == ast.bool_type_idx {
|
||||
c.error('range type can not be bool', node.cond.position())
|
||||
} else if typ_idx == ast.string_type_idx || high_type_idx == ast.string_type_idx {
|
||||
c.error('range type can not be string', node.cond.position())
|
||||
}
|
||||
if high_type in [ast.int_type, ast.int_literal_type] {
|
||||
node.val_type = typ
|
||||
} else {
|
||||
node.val_type = high_type
|
||||
}
|
||||
node.high_type = high_type
|
||||
node.scope.update_var_type(node.val_var, node.val_type)
|
||||
} else {
|
||||
sym := c.table.final_sym(typ)
|
||||
if sym.kind == .struct_ {
|
||||
// iterators
|
||||
next_fn := sym.find_method_with_generic_parent('next') or {
|
||||
c.error('a struct must have a `next()` method to be an iterator', node.cond.position())
|
||||
return
|
||||
}
|
||||
if !next_fn.return_type.has_flag(.optional) {
|
||||
c.error('iterator method `next()` must return an optional', node.cond.position())
|
||||
}
|
||||
// the receiver
|
||||
if next_fn.params.len != 1 {
|
||||
c.error('iterator method `next()` must have 0 parameters', node.cond.position())
|
||||
}
|
||||
mut val_type := next_fn.return_type.clear_flag(.optional)
|
||||
if node.val_is_mut {
|
||||
val_type = val_type.ref()
|
||||
}
|
||||
node.cond_type = typ
|
||||
node.kind = sym.kind
|
||||
node.val_type = val_type
|
||||
node.scope.update_var_type(node.val_var, val_type)
|
||||
} else if sym.kind == .string && node.val_is_mut {
|
||||
c.error('string type is immutable, it cannot be changed', node.pos)
|
||||
} else {
|
||||
if sym.kind == .map && !(node.key_var.len > 0 && node.val_var.len > 0) {
|
||||
c.error(
|
||||
'declare a key and a value variable when ranging a map: `for key, val in map {`\n' +
|
||||
'use `_` if you do not need the variable', node.pos)
|
||||
}
|
||||
if node.key_var.len > 0 {
|
||||
key_type := match sym.kind {
|
||||
.map { sym.map_info().key_type }
|
||||
else { ast.int_type }
|
||||
}
|
||||
node.key_type = key_type
|
||||
node.scope.update_var_type(node.key_var, key_type)
|
||||
}
|
||||
mut value_type := c.table.value_type(typ)
|
||||
if value_type == ast.void_type || typ.has_flag(.optional) {
|
||||
if typ != ast.void_type {
|
||||
c.error('for in: cannot index `${c.table.type_to_str(typ)}`', node.cond.position())
|
||||
}
|
||||
}
|
||||
if node.val_is_mut {
|
||||
value_type = value_type.ref()
|
||||
match node.cond {
|
||||
ast.Ident {
|
||||
if node.cond.obj is ast.Var {
|
||||
obj := node.cond.obj as ast.Var
|
||||
if !obj.is_mut {
|
||||
c.error('`$obj.name` is immutable, it cannot be changed',
|
||||
node.cond.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
ast.ArrayInit {
|
||||
c.error('array literal is immutable, it cannot be changed', node.cond.pos)
|
||||
}
|
||||
ast.MapInit {
|
||||
c.error('map literal is immutable, it cannot be changed', node.cond.pos)
|
||||
}
|
||||
ast.SelectorExpr {
|
||||
root_ident := node.cond.root_ident() or { node.cond.expr as ast.Ident }
|
||||
if !(root_ident.obj as ast.Var).is_mut {
|
||||
c.error('field `$node.cond.field_name` is immutable, it cannot be changed',
|
||||
node.cond.pos)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
node.cond_type = typ
|
||||
node.kind = sym.kind
|
||||
node.val_type = value_type
|
||||
node.scope.update_var_type(node.val_var, value_type)
|
||||
}
|
||||
}
|
||||
c.check_loop_label(node.label, node.pos)
|
||||
c.stmts(node.stmts)
|
||||
c.loop_label = prev_loop_label
|
||||
c.in_for_count--
|
||||
}
|
||||
|
||||
fn (mut c Checker) for_stmt(mut node ast.ForStmt) {
|
||||
c.in_for_count++
|
||||
prev_loop_label := c.loop_label
|
||||
c.expected_type = ast.bool_type
|
||||
typ := c.expr(node.cond)
|
||||
if !node.is_inf && typ.idx() != ast.bool_type_idx && !c.pref.translated {
|
||||
c.error('non-bool used as for condition', node.pos)
|
||||
}
|
||||
if node.cond is ast.InfixExpr {
|
||||
infix := node.cond
|
||||
if infix.op == .key_is {
|
||||
if infix.left in [ast.Ident, ast.SelectorExpr] && infix.right is ast.TypeNode {
|
||||
is_variable := if mut infix.left is ast.Ident {
|
||||
infix.left.kind == .variable
|
||||
} else {
|
||||
true
|
||||
}
|
||||
left_type := c.expr(infix.left)
|
||||
left_sym := c.table.sym(left_type)
|
||||
if is_variable {
|
||||
if left_sym.kind in [.sum_type, .interface_] {
|
||||
c.smartcast(infix.left, infix.left_type, infix.right.typ, mut
|
||||
node.scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: update loop var type
|
||||
// how does this work currenly?
|
||||
c.check_loop_label(node.label, node.pos)
|
||||
c.stmts(node.stmts)
|
||||
c.loop_label = prev_loop_label
|
||||
c.in_for_count--
|
||||
}
|
||||
|
||||
fn (mut c Checker) global_decl(mut node ast.GlobalDecl) {
|
||||
for mut field in node.fields {
|
||||
c.check_valid_snake_case(field.name, 'global name', field.pos)
|
||||
|
@ -4518,95 +3775,6 @@ pub fn (mut c Checker) check_dup_keys(node &ast.MapInit, i int) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn (mut c Checker) map_init(mut node ast.MapInit) ast.Type {
|
||||
// `map = {}`
|
||||
if node.keys.len == 0 && node.vals.len == 0 && node.typ == 0 {
|
||||
sym := c.table.sym(c.expected_type)
|
||||
if sym.kind == .map {
|
||||
info := sym.map_info()
|
||||
node.typ = c.expected_type
|
||||
node.key_type = info.key_type
|
||||
node.value_type = info.value_type
|
||||
return node.typ
|
||||
} else {
|
||||
if sym.kind == .struct_ {
|
||||
c.error('`{}` can not be used for initialising empty structs any more. Use `${c.table.type_to_str(c.expected_type)}{}` instead.',
|
||||
node.pos)
|
||||
} else {
|
||||
c.error('invalid empty map initialisation syntax, use e.g. map[string]int{} instead',
|
||||
node.pos)
|
||||
}
|
||||
return ast.void_type
|
||||
}
|
||||
}
|
||||
// `x := map[string]string` - set in parser
|
||||
if node.typ != 0 {
|
||||
info := c.table.sym(node.typ).map_info()
|
||||
c.ensure_type_exists(info.key_type, node.pos) or {}
|
||||
c.ensure_type_exists(info.value_type, node.pos) or {}
|
||||
node.key_type = info.key_type
|
||||
node.value_type = info.value_type
|
||||
return node.typ
|
||||
}
|
||||
if node.keys.len > 0 && node.vals.len > 0 {
|
||||
mut key0_type := ast.void_type
|
||||
mut val0_type := ast.void_type
|
||||
use_expected_type := c.expected_type != ast.void_type && !c.inside_const
|
||||
&& c.table.sym(c.expected_type).kind == .map
|
||||
if use_expected_type {
|
||||
sym := c.table.sym(c.expected_type)
|
||||
info := sym.map_info()
|
||||
key0_type = c.unwrap_generic(info.key_type)
|
||||
val0_type = c.unwrap_generic(info.value_type)
|
||||
} else {
|
||||
// `{'age': 20}`
|
||||
key0_type = c.table.mktyp(c.expr(node.keys[0]))
|
||||
if node.keys[0].is_auto_deref_var() {
|
||||
key0_type = key0_type.deref()
|
||||
}
|
||||
val0_type = c.table.mktyp(c.expr(node.vals[0]))
|
||||
if node.vals[0].is_auto_deref_var() {
|
||||
val0_type = val0_type.deref()
|
||||
}
|
||||
}
|
||||
mut same_key_type := true
|
||||
for i, key in node.keys {
|
||||
if i == 0 && !use_expected_type {
|
||||
continue
|
||||
}
|
||||
val := node.vals[i]
|
||||
c.expected_type = key0_type
|
||||
key_type := c.expr(key)
|
||||
c.expected_type = val0_type
|
||||
val_type := c.expr(val)
|
||||
if !c.check_types(key_type, key0_type) || (i == 0 && key_type.is_number()
|
||||
&& key0_type.is_number() && key0_type != c.table.mktyp(key_type)) {
|
||||
msg := c.expected_msg(key_type, key0_type)
|
||||
c.error('invalid map key: $msg', key.position())
|
||||
same_key_type = false
|
||||
}
|
||||
if !c.check_types(val_type, val0_type) || (i == 0 && val_type.is_number()
|
||||
&& val0_type.is_number() && val0_type != c.table.mktyp(val_type)) {
|
||||
msg := c.expected_msg(val_type, val0_type)
|
||||
c.error('invalid map value: $msg', val.position())
|
||||
}
|
||||
}
|
||||
if same_key_type {
|
||||
for i in 1 .. node.keys.len {
|
||||
c.check_dup_keys(node, i)
|
||||
}
|
||||
}
|
||||
key0_type = c.unwrap_generic(key0_type)
|
||||
val0_type = c.unwrap_generic(val0_type)
|
||||
mut map_type := ast.new_type(c.table.find_or_register_map(key0_type, val0_type))
|
||||
node.typ = map_type
|
||||
node.key_type = key0_type
|
||||
node.value_type = val0_type
|
||||
return map_type
|
||||
}
|
||||
return node.typ
|
||||
}
|
||||
|
||||
// call this *before* calling error or warn
|
||||
pub fn (mut c Checker) add_error_detail(s string) {
|
||||
c.error_details << s
|
||||
|
|
|
@ -0,0 +1,283 @@
|
|||
// Copyright (c) 2019-2021 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.token
|
||||
|
||||
pub fn (mut c Checker) array_init(mut node ast.ArrayInit) ast.Type {
|
||||
mut elem_type := ast.void_type
|
||||
// []string - was set in parser
|
||||
if node.typ != ast.void_type {
|
||||
if node.exprs.len == 0 {
|
||||
if node.has_cap {
|
||||
c.check_array_init_para_type('cap', node.cap_expr, node.pos)
|
||||
}
|
||||
if node.has_len {
|
||||
c.check_array_init_para_type('len', node.len_expr, node.pos)
|
||||
}
|
||||
}
|
||||
if node.has_default {
|
||||
default_expr := node.default_expr
|
||||
default_typ := c.check_expr_opt_call(default_expr, c.expr(default_expr))
|
||||
c.check_expected(default_typ, node.elem_type) or {
|
||||
c.error(err.msg, default_expr.position())
|
||||
}
|
||||
}
|
||||
if node.has_len {
|
||||
if node.has_len && !node.has_default {
|
||||
elem_type_sym := c.table.sym(node.elem_type)
|
||||
if elem_type_sym.kind == .interface_ {
|
||||
c.error('cannot instantiate an array of interfaces without also giving a default `init:` value',
|
||||
node.len_expr.position())
|
||||
}
|
||||
}
|
||||
c.ensure_sumtype_array_has_default_value(node)
|
||||
}
|
||||
c.ensure_type_exists(node.elem_type, node.elem_type_pos) or {}
|
||||
if node.typ.has_flag(.generic) && c.table.cur_fn.generic_names.len == 0 {
|
||||
c.error('generic struct cannot use in non-generic function', node.pos)
|
||||
}
|
||||
return node.typ
|
||||
}
|
||||
if node.is_fixed {
|
||||
c.ensure_sumtype_array_has_default_value(node)
|
||||
c.ensure_type_exists(node.elem_type, node.elem_type_pos) or {}
|
||||
}
|
||||
// a = []
|
||||
if node.exprs.len == 0 {
|
||||
// a := fn_returing_opt_array() or { [] }
|
||||
if c.expected_type == ast.void_type && c.expected_or_type != ast.void_type {
|
||||
c.expected_type = c.expected_or_type
|
||||
}
|
||||
mut type_sym := c.table.sym(c.expected_type)
|
||||
if type_sym.kind != .array || type_sym.array_info().elem_type == ast.void_type {
|
||||
c.error('array_init: no type specified (maybe: `[]Type{}` instead of `[]`)',
|
||||
node.pos)
|
||||
return ast.void_type
|
||||
}
|
||||
// TODO: seperate errors once bug is fixed with `x := if expr { ... } else { ... }`
|
||||
// if c.expected_type == ast.void_type {
|
||||
// c.error('array_init: use `[]Type{}` instead of `[]`', node.pos)
|
||||
// return ast.void_type
|
||||
// }
|
||||
array_info := type_sym.array_info()
|
||||
node.elem_type = array_info.elem_type
|
||||
// clear optional flag incase of: `fn opt_arr ?[]int { return [] }`
|
||||
return c.expected_type.clear_flag(.optional)
|
||||
}
|
||||
// [1,2,3]
|
||||
if node.exprs.len > 0 && node.elem_type == ast.void_type {
|
||||
mut expected_value_type := ast.void_type
|
||||
mut expecting_interface_array := false
|
||||
if c.expected_type != 0 {
|
||||
expected_value_type = c.table.value_type(c.expected_type)
|
||||
if c.table.sym(expected_value_type).kind == .interface_ {
|
||||
// Array of interfaces? (`[dog, cat]`) Save the interface type (`Animal`)
|
||||
expecting_interface_array = true
|
||||
}
|
||||
}
|
||||
// expecting_interface_array := c.expected_type != 0 &&
|
||||
// c.table.sym(c.table.value_type(c.expected_type)).kind == .interface_
|
||||
//
|
||||
// if expecting_interface_array {
|
||||
// println('ex $c.expected_type')
|
||||
// }
|
||||
for i, mut expr in node.exprs {
|
||||
typ := c.check_expr_opt_call(expr, c.expr(expr))
|
||||
node.expr_types << typ
|
||||
// The first element's type
|
||||
if expecting_interface_array {
|
||||
if i == 0 {
|
||||
elem_type = expected_value_type
|
||||
c.expected_type = elem_type
|
||||
c.type_implements(typ, elem_type, expr.position())
|
||||
}
|
||||
if !typ.is_ptr() && !typ.is_pointer() && !c.inside_unsafe {
|
||||
typ_sym := c.table.sym(typ)
|
||||
if typ_sym.kind != .interface_ {
|
||||
c.mark_as_referenced(mut &expr, true)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
// The first element's type
|
||||
if i == 0 {
|
||||
if expr.is_auto_deref_var() {
|
||||
elem_type = c.table.mktyp(typ.deref())
|
||||
} else {
|
||||
elem_type = c.table.mktyp(typ)
|
||||
}
|
||||
c.expected_type = elem_type
|
||||
continue
|
||||
}
|
||||
if expr !is ast.TypeNode {
|
||||
c.check_expected(typ, elem_type) or {
|
||||
c.error('invalid array element: $err.msg', expr.position())
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.is_fixed {
|
||||
idx := c.table.find_or_register_array_fixed(elem_type, node.exprs.len, ast.empty_expr())
|
||||
if elem_type.has_flag(.generic) {
|
||||
node.typ = ast.new_type(idx).set_flag(.generic)
|
||||
} else {
|
||||
node.typ = ast.new_type(idx)
|
||||
}
|
||||
} else {
|
||||
idx := c.table.find_or_register_array(elem_type)
|
||||
if elem_type.has_flag(.generic) {
|
||||
node.typ = ast.new_type(idx).set_flag(.generic)
|
||||
} else {
|
||||
node.typ = ast.new_type(idx)
|
||||
}
|
||||
}
|
||||
node.elem_type = elem_type
|
||||
} else if node.is_fixed && node.exprs.len == 1 && node.elem_type != ast.void_type {
|
||||
// [50]byte
|
||||
mut fixed_size := i64(0)
|
||||
init_expr := node.exprs[0]
|
||||
c.expr(init_expr)
|
||||
match init_expr {
|
||||
ast.IntegerLiteral {
|
||||
fixed_size = init_expr.val.int()
|
||||
}
|
||||
ast.Ident {
|
||||
if init_expr.obj is ast.ConstField {
|
||||
if comptime_value := c.eval_comptime_const_expr(init_expr.obj.expr,
|
||||
0)
|
||||
{
|
||||
fixed_size = comptime_value.i64() or { fixed_size }
|
||||
}
|
||||
} else {
|
||||
c.error('non-constant array bound `$init_expr.name`', init_expr.pos)
|
||||
}
|
||||
}
|
||||
ast.InfixExpr {
|
||||
if comptime_value := c.eval_comptime_const_expr(init_expr, 0) {
|
||||
fixed_size = comptime_value.i64() or { fixed_size }
|
||||
}
|
||||
}
|
||||
else {
|
||||
c.error('expecting `int` for fixed size', node.pos)
|
||||
}
|
||||
}
|
||||
if fixed_size <= 0 {
|
||||
c.error('fixed size cannot be zero or negative (fixed_size: $fixed_size)',
|
||||
init_expr.position())
|
||||
}
|
||||
idx := c.table.find_or_register_array_fixed(node.elem_type, int(fixed_size), init_expr)
|
||||
if node.elem_type.has_flag(.generic) {
|
||||
node.typ = ast.new_type(idx).set_flag(.generic)
|
||||
} else {
|
||||
node.typ = ast.new_type(idx)
|
||||
}
|
||||
if node.has_default {
|
||||
c.expr(node.default_expr)
|
||||
}
|
||||
}
|
||||
return node.typ
|
||||
}
|
||||
|
||||
fn (mut c Checker) check_array_init_para_type(para string, expr ast.Expr, pos token.Position) {
|
||||
sym := c.table.sym(c.expr(expr))
|
||||
if sym.kind !in [.int, .int_literal] {
|
||||
c.error('array $para needs to be an int', pos)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut c Checker) ensure_sumtype_array_has_default_value(node ast.ArrayInit) {
|
||||
sym := c.table.sym(node.elem_type)
|
||||
if sym.kind == .sum_type && !node.has_default {
|
||||
c.error('cannot initialize sum type array without default value', node.pos)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut c Checker) map_init(mut node ast.MapInit) ast.Type {
|
||||
// `map = {}`
|
||||
if node.keys.len == 0 && node.vals.len == 0 && node.typ == 0 {
|
||||
sym := c.table.sym(c.expected_type)
|
||||
if sym.kind == .map {
|
||||
info := sym.map_info()
|
||||
node.typ = c.expected_type
|
||||
node.key_type = info.key_type
|
||||
node.value_type = info.value_type
|
||||
return node.typ
|
||||
} else {
|
||||
if sym.kind == .struct_ {
|
||||
c.error('`{}` can not be used for initialising empty structs any more. Use `${c.table.type_to_str(c.expected_type)}{}` instead.',
|
||||
node.pos)
|
||||
} else {
|
||||
c.error('invalid empty map initialisation syntax, use e.g. map[string]int{} instead',
|
||||
node.pos)
|
||||
}
|
||||
return ast.void_type
|
||||
}
|
||||
}
|
||||
// `x := map[string]string` - set in parser
|
||||
if node.typ != 0 {
|
||||
info := c.table.sym(node.typ).map_info()
|
||||
c.ensure_type_exists(info.key_type, node.pos) or {}
|
||||
c.ensure_type_exists(info.value_type, node.pos) or {}
|
||||
node.key_type = info.key_type
|
||||
node.value_type = info.value_type
|
||||
return node.typ
|
||||
}
|
||||
if node.keys.len > 0 && node.vals.len > 0 {
|
||||
mut key0_type := ast.void_type
|
||||
mut val0_type := ast.void_type
|
||||
use_expected_type := c.expected_type != ast.void_type && !c.inside_const
|
||||
&& c.table.sym(c.expected_type).kind == .map
|
||||
if use_expected_type {
|
||||
sym := c.table.sym(c.expected_type)
|
||||
info := sym.map_info()
|
||||
key0_type = c.unwrap_generic(info.key_type)
|
||||
val0_type = c.unwrap_generic(info.value_type)
|
||||
} else {
|
||||
// `{'age': 20}`
|
||||
key0_type = c.table.mktyp(c.expr(node.keys[0]))
|
||||
if node.keys[0].is_auto_deref_var() {
|
||||
key0_type = key0_type.deref()
|
||||
}
|
||||
val0_type = c.table.mktyp(c.expr(node.vals[0]))
|
||||
if node.vals[0].is_auto_deref_var() {
|
||||
val0_type = val0_type.deref()
|
||||
}
|
||||
}
|
||||
mut same_key_type := true
|
||||
for i, key in node.keys {
|
||||
if i == 0 && !use_expected_type {
|
||||
continue
|
||||
}
|
||||
val := node.vals[i]
|
||||
c.expected_type = key0_type
|
||||
key_type := c.expr(key)
|
||||
c.expected_type = val0_type
|
||||
val_type := c.expr(val)
|
||||
if !c.check_types(key_type, key0_type) || (i == 0 && key_type.is_number()
|
||||
&& key0_type.is_number() && key0_type != c.table.mktyp(key_type)) {
|
||||
msg := c.expected_msg(key_type, key0_type)
|
||||
c.error('invalid map key: $msg', key.position())
|
||||
same_key_type = false
|
||||
}
|
||||
if !c.check_types(val_type, val0_type) || (i == 0 && val_type.is_number()
|
||||
&& val0_type.is_number() && val0_type != c.table.mktyp(val_type)) {
|
||||
msg := c.expected_msg(val_type, val0_type)
|
||||
c.error('invalid map value: $msg', val.position())
|
||||
}
|
||||
}
|
||||
if same_key_type {
|
||||
for i in 1 .. node.keys.len {
|
||||
c.check_dup_keys(node, i)
|
||||
}
|
||||
}
|
||||
key0_type = c.unwrap_generic(key0_type)
|
||||
val0_type = c.unwrap_generic(val0_type)
|
||||
mut map_type := ast.new_type(c.table.find_or_register_map(key0_type, val0_type))
|
||||
node.typ = map_type
|
||||
node.key_type = key0_type
|
||||
node.value_type = val0_type
|
||||
return map_type
|
||||
}
|
||||
return node.typ
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
// Copyright (c) 2019-2021 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
|
||||
|
||||
fn (mut c Checker) for_c_stmt(node ast.ForCStmt) {
|
||||
c.in_for_count++
|
||||
prev_loop_label := c.loop_label
|
||||
if node.has_init {
|
||||
c.stmt(node.init)
|
||||
}
|
||||
c.expr(node.cond)
|
||||
if node.has_inc {
|
||||
c.stmt(node.inc)
|
||||
}
|
||||
c.check_loop_label(node.label, node.pos)
|
||||
c.stmts(node.stmts)
|
||||
c.loop_label = prev_loop_label
|
||||
c.in_for_count--
|
||||
}
|
||||
|
||||
fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) {
|
||||
c.in_for_count++
|
||||
prev_loop_label := c.loop_label
|
||||
typ := c.expr(node.cond)
|
||||
typ_idx := typ.idx()
|
||||
if node.key_var.len > 0 && node.key_var != '_' {
|
||||
c.check_valid_snake_case(node.key_var, 'variable name', node.pos)
|
||||
}
|
||||
if node.val_var.len > 0 && node.val_var != '_' {
|
||||
c.check_valid_snake_case(node.val_var, 'variable name', node.pos)
|
||||
}
|
||||
if node.is_range {
|
||||
high_type := c.expr(node.high)
|
||||
high_type_idx := high_type.idx()
|
||||
if typ_idx in ast.integer_type_idxs && high_type_idx !in ast.integer_type_idxs {
|
||||
c.error('range types do not match', node.cond.position())
|
||||
} else if typ_idx in ast.float_type_idxs || high_type_idx in ast.float_type_idxs {
|
||||
c.error('range type can not be float', node.cond.position())
|
||||
} else if typ_idx == ast.bool_type_idx || high_type_idx == ast.bool_type_idx {
|
||||
c.error('range type can not be bool', node.cond.position())
|
||||
} else if typ_idx == ast.string_type_idx || high_type_idx == ast.string_type_idx {
|
||||
c.error('range type can not be string', node.cond.position())
|
||||
}
|
||||
if high_type in [ast.int_type, ast.int_literal_type] {
|
||||
node.val_type = typ
|
||||
} else {
|
||||
node.val_type = high_type
|
||||
}
|
||||
node.high_type = high_type
|
||||
node.scope.update_var_type(node.val_var, node.val_type)
|
||||
} else {
|
||||
sym := c.table.final_sym(typ)
|
||||
if sym.kind == .struct_ {
|
||||
// iterators
|
||||
next_fn := sym.find_method_with_generic_parent('next') or {
|
||||
c.error('a struct must have a `next()` method to be an iterator', node.cond.position())
|
||||
return
|
||||
}
|
||||
if !next_fn.return_type.has_flag(.optional) {
|
||||
c.error('iterator method `next()` must return an optional', node.cond.position())
|
||||
}
|
||||
// the receiver
|
||||
if next_fn.params.len != 1 {
|
||||
c.error('iterator method `next()` must have 0 parameters', node.cond.position())
|
||||
}
|
||||
mut val_type := next_fn.return_type.clear_flag(.optional)
|
||||
if node.val_is_mut {
|
||||
val_type = val_type.ref()
|
||||
}
|
||||
node.cond_type = typ
|
||||
node.kind = sym.kind
|
||||
node.val_type = val_type
|
||||
node.scope.update_var_type(node.val_var, val_type)
|
||||
} else if sym.kind == .string && node.val_is_mut {
|
||||
c.error('string type is immutable, it cannot be changed', node.pos)
|
||||
} else {
|
||||
if sym.kind == .map && !(node.key_var.len > 0 && node.val_var.len > 0) {
|
||||
c.error(
|
||||
'declare a key and a value variable when ranging a map: `for key, val in map {`\n' +
|
||||
'use `_` if you do not need the variable', node.pos)
|
||||
}
|
||||
if node.key_var.len > 0 {
|
||||
key_type := match sym.kind {
|
||||
.map { sym.map_info().key_type }
|
||||
else { ast.int_type }
|
||||
}
|
||||
node.key_type = key_type
|
||||
node.scope.update_var_type(node.key_var, key_type)
|
||||
}
|
||||
mut value_type := c.table.value_type(typ)
|
||||
if value_type == ast.void_type || typ.has_flag(.optional) {
|
||||
if typ != ast.void_type {
|
||||
c.error('for in: cannot index `${c.table.type_to_str(typ)}`', node.cond.position())
|
||||
}
|
||||
}
|
||||
if node.val_is_mut {
|
||||
value_type = value_type.ref()
|
||||
match node.cond {
|
||||
ast.Ident {
|
||||
if node.cond.obj is ast.Var {
|
||||
obj := node.cond.obj as ast.Var
|
||||
if !obj.is_mut {
|
||||
c.error('`$obj.name` is immutable, it cannot be changed',
|
||||
node.cond.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
ast.ArrayInit {
|
||||
c.error('array literal is immutable, it cannot be changed', node.cond.pos)
|
||||
}
|
||||
ast.MapInit {
|
||||
c.error('map literal is immutable, it cannot be changed', node.cond.pos)
|
||||
}
|
||||
ast.SelectorExpr {
|
||||
root_ident := node.cond.root_ident() or { node.cond.expr as ast.Ident }
|
||||
if !(root_ident.obj as ast.Var).is_mut {
|
||||
c.error('field `$node.cond.field_name` is immutable, it cannot be changed',
|
||||
node.cond.pos)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
node.cond_type = typ
|
||||
node.kind = sym.kind
|
||||
node.val_type = value_type
|
||||
node.scope.update_var_type(node.val_var, value_type)
|
||||
}
|
||||
}
|
||||
c.check_loop_label(node.label, node.pos)
|
||||
c.stmts(node.stmts)
|
||||
c.loop_label = prev_loop_label
|
||||
c.in_for_count--
|
||||
}
|
||||
|
||||
fn (mut c Checker) for_stmt(mut node ast.ForStmt) {
|
||||
c.in_for_count++
|
||||
prev_loop_label := c.loop_label
|
||||
c.expected_type = ast.bool_type
|
||||
typ := c.expr(node.cond)
|
||||
if !node.is_inf && typ.idx() != ast.bool_type_idx && !c.pref.translated {
|
||||
c.error('non-bool used as for condition', node.pos)
|
||||
}
|
||||
if node.cond is ast.InfixExpr {
|
||||
infix := node.cond
|
||||
if infix.op == .key_is {
|
||||
if infix.left in [ast.Ident, ast.SelectorExpr] && infix.right is ast.TypeNode {
|
||||
is_variable := if mut infix.left is ast.Ident {
|
||||
infix.left.kind == .variable
|
||||
} else {
|
||||
true
|
||||
}
|
||||
left_type := c.expr(infix.left)
|
||||
left_sym := c.table.sym(left_type)
|
||||
if is_variable {
|
||||
if left_sym.kind in [.sum_type, .interface_] {
|
||||
c.smartcast(infix.left, infix.left_type, infix.right.typ, mut
|
||||
node.scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: update loop var type
|
||||
// how does this work currenly?
|
||||
c.check_loop_label(node.label, node.pos)
|
||||
c.stmts(node.stmts)
|
||||
c.loop_label = prev_loop_label
|
||||
c.in_for_count--
|
||||
}
|
|
@ -0,0 +1,393 @@
|
|||
// Copyright (c) 2019-2021 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 := c.table.find_type(node.name) or { ast.invalid_type_symbol }
|
||||
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 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
|
||||
}
|
Loading…
Reference in New Issue