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
|
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) {
|
fn (mut c Checker) check_div_mod_by_zero(expr ast.Expr, op_kind token.Kind) {
|
||||||
match mut expr {
|
match mut expr {
|
||||||
ast.FloatLiteral {
|
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]
|
[inline]
|
||||||
fn (mut c Checker) check_loop_label(label string, pos token.Position) {
|
fn (mut c Checker) check_loop_label(label string, pos token.Position) {
|
||||||
if label.len == 0 {
|
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) {
|
fn (mut c Checker) global_decl(mut node ast.GlobalDecl) {
|
||||||
for mut field in node.fields {
|
for mut field in node.fields {
|
||||||
c.check_valid_snake_case(field.name, 'global name', field.pos)
|
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
|
// call this *before* calling error or warn
|
||||||
pub fn (mut c Checker) add_error_detail(s string) {
|
pub fn (mut c Checker) add_error_detail(s string) {
|
||||||
c.error_details << s
|
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