v/vlib/v/checker/checker.v

3860 lines
121 KiB
V
Raw Normal View History

2020-01-23 21:04:46 +01:00
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
2020-08-27 06:46:18 +02:00
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module checker
2020-04-25 17:49:16 +02:00
import v.ast
import v.table
import v.token
import v.pref
import v.util
import v.errors
2020-02-19 19:54:36 +01:00
const (
max_nr_errors = 300
match_exhaustive_cutoff_limit = 10
enum_min = int(0x80000000)
enum_max = 0x7FFFFFFF
2020-02-19 19:54:36 +01:00
)
const (
valid_comp_if_os = ['windows', 'ios', 'mac', 'macos', 'mach', 'darwin', 'hpux', 'gnu',
'qnx', 'linux', 'freebsd', 'openbsd', 'netbsd', 'bsd', 'dragonfly', 'android', 'solaris', 'haiku',
'linux_or_macos',
]
valid_comp_if_compilers = ['gcc', 'tinyc', 'clang', 'mingw', 'msvc', 'cplusplus']
valid_comp_if_platforms = ['amd64', 'aarch64', 'x64', 'x32', 'little_endian', 'big_endian']
valid_comp_if_other = ['js', 'debug', 'test', 'glibc', 'prealloc', 'no_bounds_checking']
)
pub struct Checker {
2020-05-09 15:16:48 +02:00
pub mut:
table &table.Table
file ast.File
nr_errors int
nr_warnings int
errors []errors.Error
warnings []errors.Warning
error_lines []int // to avoid printing multiple errors for the same line
expected_type table.Type
2020-05-23 08:51:15 +02:00
cur_fn &ast.FnDecl // current function
const_decl string
const_deps []string
const_names []string
locked_names []string // vars that are currently locked
rlocked_names []string // vars that are currently read-locked
pref &pref.Preferences // Preferences shared from V struct
in_for_count int // if checker is currently in an for loop
2020-04-08 19:15:16 +02:00
// checked_ident string // to avoid infinit checker loops
returns bool
scope_returns bool
mod string // current module name
is_builtin_mod bool // are we in `builtin`?
inside_unsafe bool
skip_flags bool // should `#flag` and `#include` be skipped
cur_generic_type table.Type
mut:
expr_level int // to avoid infinit recursion segfaults due to compiler bugs
inside_sql bool // to handle sql table fields pseudo variables
cur_orm_ts table.TypeSymbol
error_details []string
}
pub fn new_checker(table &table.Table, pref &pref.Preferences) Checker {
2020-04-09 15:33:46 +02:00
return Checker{
table: table
pref: pref
2020-05-23 08:51:15 +02:00
cur_fn: 0
}
}
2020-04-25 17:49:16 +02:00
pub fn (mut c Checker) check(ast_file ast.File) {
c.file = ast_file
2020-04-30 09:33:12 +02:00
for i, ast_import in ast_file.imports {
2020-04-30 18:06:14 +02:00
for j in 0 .. i {
2020-04-30 09:33:12 +02:00
if ast_import.mod == ast_file.imports[j].mod {
c.error('module name `$ast_import.mod` duplicate', ast_import.pos)
}
}
}
for stmt in ast_file.stmts {
c.expr_level = 0
c.stmt(stmt)
}
c.check_scope_vars(c.file.scope)
}
pub fn (mut c Checker) check_scope_vars(sc &ast.Scope) {
for _, obj in sc.objects {
match obj {
ast.Var {
if !c.pref.is_repl {
if !obj.is_used && obj.name[0] != `_` {
if c.pref.is_prod {
c.error('unused variable: `$obj.name`', obj.pos)
} else {
c.warn('unused variable: `$obj.name`', obj.pos)
}
}
}
// TODO: fix all of these warnings
// if obj.is_mut && !obj.is_changed {
2020-06-24 14:44:06 +02:00
// c.warn('`$obj.name` is declared as mutable, but it was never changed', obj.pos)
// }
}
else {}
}
}
for _, child in sc.children {
c.check_scope_vars(child)
}
}
// not used right now
pub fn (mut c Checker) check2(ast_file ast.File) []errors.Error {
2020-02-20 17:05:16 +01:00
c.file = ast_file
for stmt in ast_file.stmts {
c.stmt(stmt)
}
return c.errors
}
2020-04-25 17:49:16 +02:00
pub fn (mut c Checker) check_files(ast_files []ast.File) {
mut has_main_mod_file := false
mut has_main_fn := false
mut files_from_main_module := []&ast.File{}
for i in 0 .. ast_files.len {
file := &ast_files[i]
c.check(file)
if file.mod.name == 'main' {
files_from_main_module << file
has_main_mod_file = true
2020-04-19 00:07:57 +02:00
if c.check_file_in_main(file) {
has_main_fn = true
}
}
}
if has_main_mod_file && !has_main_fn && files_from_main_module.len > 0 {
if c.pref.is_script && !c.pref.is_test {
mut first_main_file := files_from_main_module[0]
first_main_file.stmts << ast.FnDecl{
name: 'main.main'
mod: 'main'
file: first_main_file.path
return_type: table.void_type
}
has_main_fn = true
}
}
// Make sure fn main is defined in non lib builds
if c.pref.build_mode == .build_module || c.pref.is_test {
return
}
2020-04-18 17:46:23 +02:00
if c.pref.is_shared {
// shared libs do not need to have a main
return
}
if !has_main_mod_file {
2020-05-06 12:57:40 +02:00
c.error('project must include a `main` module or be a shared library (compile with `v -shared`)',
token.Position{})
} else if !has_main_fn {
c.error('function `main` must be declared in the main module', token.Position{})
}
}
2020-04-19 00:07:57 +02:00
const (
no_pub_in_main_warning = 'in module main cannot be declared public'
)
// do checks specific to files in main module
// returns `true` if a main function is in the file
2020-04-25 17:49:16 +02:00
fn (mut c Checker) check_file_in_main(file ast.File) bool {
2020-04-19 00:07:57 +02:00
mut has_main_fn := false
for stmt in file.stmts {
2020-04-19 00:07:57 +02:00
match stmt {
ast.ConstDecl {
2020-06-18 20:38:59 +02:00
if stmt.is_pub {
c.warn('const $no_pub_in_main_warning', stmt.pos)
2020-04-19 00:07:57 +02:00
}
}
/*
// TODO not a Stmt
2020-04-19 00:07:57 +02:00
ast.ConstField {
2020-06-18 20:38:59 +02:00
if stmt.is_pub {
c.warn('const field `$stmt.name` $no_pub_in_main_warning', stmt.pos)
2020-04-19 00:07:57 +02:00
}
}
*/
2020-04-19 00:07:57 +02:00
ast.EnumDecl {
2020-06-18 20:38:59 +02:00
if stmt.is_pub {
c.warn('enum `$stmt.name` $no_pub_in_main_warning', stmt.pos)
2020-04-19 00:07:57 +02:00
}
}
ast.FnDecl {
if stmt.name == 'main.main' {
2020-04-19 00:07:57 +02:00
has_main_fn = true
2020-06-18 20:38:59 +02:00
if stmt.is_pub {
c.error('function `main` cannot be declared public', stmt.pos)
2020-04-19 00:07:57 +02:00
}
2020-06-18 20:38:59 +02:00
if stmt.args.len > 0 {
c.error('function `main` cannot have arguments', stmt.pos)
}
2020-06-18 20:38:59 +02:00
if stmt.return_type != table.void_type {
c.error('function `main` cannot return values', stmt.pos)
}
2020-04-19 00:07:57 +02:00
} else {
2020-06-18 20:38:59 +02:00
if stmt.is_pub && !stmt.is_method {
c.warn('function `$stmt.name` $no_pub_in_main_warning', stmt.pos)
2020-04-19 00:07:57 +02:00
}
}
if stmt.return_type != table.void_type {
for attr in stmt.attrs {
if attr.is_ctdefine {
c.error('only functions that do NOT return values can have `[if $attr.name]` tags',
stmt.pos)
break
}
}
}
2020-04-19 00:07:57 +02:00
}
ast.StructDecl {
2020-06-18 20:38:59 +02:00
if stmt.is_pub {
c.warn('struct `$stmt.name` $no_pub_in_main_warning', stmt.pos)
2020-04-19 00:07:57 +02:00
}
}
ast.TypeDecl {
2020-06-18 20:38:59 +02:00
if stmt is ast.AliasTypeDecl {
if stmt.is_pub {
c.warn('type alias `$stmt.name` $no_pub_in_main_warning', stmt.pos)
2020-04-19 00:07:57 +02:00
}
2020-06-18 20:38:59 +02:00
} else if stmt is ast.SumTypeDecl {
if stmt.is_pub {
c.warn('sum type `$stmt.name` $no_pub_in_main_warning', stmt.pos)
2020-04-19 00:07:57 +02:00
}
2020-06-18 20:38:59 +02:00
} else if stmt is ast.FnTypeDecl {
if stmt.is_pub {
c.warn('type alias `$stmt.name` $no_pub_in_main_warning', stmt.pos)
2020-04-19 00:07:57 +02:00
}
}
}
2020-04-19 00:07:57 +02:00
else {}
}
}
2020-04-19 00:07:57 +02:00
return has_main_fn
}
2020-05-16 16:12:23 +02:00
fn (mut c Checker) check_valid_snake_case(name, identifier string, pos token.Position) {
if !c.pref.is_vweb && (name[0] == `_` || name.contains('._')) {
2020-05-16 16:12:23 +02:00
c.error('$identifier `$name` cannot start with `_`', pos)
}
2020-08-09 03:57:54 +02:00
if !c.pref.experimental && !c.pref.translated && util.contains_capital(name) {
2020-05-16 16:12:23 +02:00
c.error('$identifier `$name` cannot contain uppercase letters, use snake_case instead',
pos)
}
}
fn stripped_name(name string) string {
idx := name.last_index('.') or {
-1
}
return name[(idx + 1)..]
}
fn (mut c Checker) check_valid_pascal_case(name, identifier string, pos token.Position) {
sname := stripped_name(name)
2020-08-09 03:57:54 +02:00
if !sname[0].is_capital() && !c.pref.translated {
2020-05-16 16:12:23 +02:00
c.error('$identifier `$name` must begin with capital letter', pos)
}
}
pub fn (mut c Checker) type_decl(node ast.TypeDecl) {
match node {
ast.AliasTypeDecl {
// TODO Replace `c.file.mod.name != 'time'` by `it.language != .v` once available
2020-08-27 06:46:18 +02:00
if c.file.mod.name != 'time' && c.file.mod.name != 'builtin' {
2020-06-18 20:38:59 +02:00
c.check_valid_pascal_case(node.name, 'type alias', node.pos)
2020-05-16 16:12:23 +02:00
}
2020-06-18 20:38:59 +02:00
typ_sym := c.table.get_type_symbol(node.parent_type)
if typ_sym.kind == .placeholder {
2020-08-22 12:29:15 +02:00
c.error("type `$typ_sym.source_name` doesn't exist", node.pos)
} else if typ_sym.kind == .alias {
orig_sym := c.table.get_type_symbol((typ_sym.info as table.Alias).parent_type)
2020-08-22 12:29:15 +02:00
c.error('type `$typ_sym.str()` is an alias, use the original alias type `$orig_sym.source_name` instead',
node.pos)
} else if typ_sym.kind == .chan {
c.error('aliases of `chan` types are not allowed.', node.pos)
}
}
ast.FnTypeDecl {
2020-06-18 20:38:59 +02:00
c.check_valid_pascal_case(node.name, 'fn type', node.pos)
typ_sym := c.table.get_type_symbol(node.typ)
fn_typ_info := typ_sym.info as table.FnType
fn_info := fn_typ_info.func
ret_sym := c.table.get_type_symbol(fn_info.return_type)
if ret_sym.kind == .placeholder {
2020-08-22 12:29:15 +02:00
c.error("type `$ret_sym.source_name` doesn't exist", node.pos)
}
for arg in fn_info.args {
arg_sym := c.table.get_type_symbol(arg.typ)
if arg_sym.kind == .placeholder {
2020-08-22 12:29:15 +02:00
c.error("type `$arg_sym.source_name` doesn't exist", node.pos)
}
}
}
ast.SumTypeDecl {
2020-06-18 20:38:59 +02:00
c.check_valid_pascal_case(node.name, 'sum type', node.pos)
for typ in node.sub_types {
typ_sym := c.table.get_type_symbol(typ)
if typ_sym.kind == .placeholder {
2020-08-22 12:29:15 +02:00
c.error("type `$typ_sym.source_name` doesn't exist", node.pos)
} else if typ_sym.kind == .interface_ {
c.error('sum type cannot hold an interface', node.pos)
}
}
}
}
}
2020-05-16 16:12:23 +02:00
pub fn (mut c Checker) interface_decl(decl ast.InterfaceDecl) {
c.check_valid_pascal_case(decl.name, 'interface name', decl.pos)
for method in decl.methods {
c.check_valid_snake_case(method.name, 'method name', method.pos)
}
}
2020-04-25 17:49:16 +02:00
pub fn (mut c Checker) struct_decl(decl ast.StructDecl) {
if decl.language == .v && !c.is_builtin_mod {
2020-05-16 16:12:23 +02:00
c.check_valid_pascal_case(decl.name, 'struct name', decl.pos)
}
for i, field in decl.fields {
if !c.is_builtin_mod && decl.language == .v {
2020-05-16 16:12:23 +02:00
c.check_valid_snake_case(field.name, 'field name', field.pos)
}
2020-04-29 12:20:22 +02:00
for j in 0 .. i {
if field.name == decl.fields[j].name {
c.error('field name `$field.name` duplicate', field.pos)
}
}
2020-04-26 18:38:29 +02:00
sym := c.table.get_type_symbol(field.typ)
if sym.kind == .placeholder && decl.language != .c && !sym.name.starts_with('C.') {
2020-08-22 12:29:15 +02:00
c.error(util.new_suggestion(sym.source_name, c.table.known_type_names()).say('unknown type `$sym.source_name`'),
field.type_pos)
2020-04-26 18:38:29 +02:00
}
if sym.kind == .array {
array_info := sym.array_info()
elem_sym := c.table.get_type_symbol(array_info.elem_type)
if elem_sym.kind == .placeholder {
2020-08-22 12:29:15 +02:00
c.error(util.new_suggestion(elem_sym.source_name, c.table.known_type_names()).say('unknown type `$elem_sym.source_name`'),
field.type_pos)
}
}
2020-05-09 23:45:41 +02:00
if sym.kind == .struct_ {
2020-05-11 14:38:25 +02:00
info := sym.info as table.Struct
2020-05-09 23:45:41 +02:00
if info.is_ref_only && !field.typ.is_ptr() {
2020-08-22 12:29:15 +02:00
c.error('`$sym.source_name` type can only be used as a reference: `&$sym.source_name`',
field.type_pos)
2020-05-09 23:45:41 +02:00
}
}
if sym.kind == .map {
info := sym.map_info()
key_sym := c.table.get_type_symbol(info.key_type)
value_sym := c.table.get_type_symbol(info.value_type)
if key_sym.kind == .placeholder {
c.error('unknown type `$key_sym.source_name`', field.type_pos)
}
if value_sym.kind == .placeholder {
c.error('unknown type `$value_sym.source_name`', field.type_pos)
}
}
2020-04-26 18:38:29 +02:00
if field.has_default_expr {
c.expected_type = field.typ
field_expr_type := c.expr(field.default_expr)
2020-05-24 04:43:00 +02:00
if !c.check_types(field_expr_type, field.typ) {
2020-04-25 17:49:16 +02:00
field_expr_type_sym := c.table.get_type_symbol(field_expr_type)
2020-04-26 18:38:29 +02:00
field_type_sym := c.table.get_type_symbol(field.typ)
2020-06-27 16:41:29 +02:00
c.error('default expression for field `$field.name` ' +
2020-08-22 12:29:15 +02:00
'has type `$field_expr_type_sym.source_name`, but should be `$field_type_sym.source_name`',
field.default_expr.position())
}
// Check for unnecessary inits like ` = 0` and ` = ''`
2020-09-09 14:05:56 +02:00
if field.typ.is_ptr() {
continue
}
if field.default_expr is ast.IntegerLiteral as lit {
if lit.val == '0' {
c.warn('unnecessary default value of `0`: struct fields are zeroed by default',
lit.pos)
}
} else if field.default_expr is ast.StringLiteral as lit {
if lit.val == '' {
c.warn("unnecessary default value of '': struct fields are zeroed by default",
lit.pos)
}
} else if field.default_expr is ast.BoolLiteral as lit {
if lit.val == false {
c.warn('unnecessary default value `false`: struct fields are zeroed by default',
lit.pos)
}
}
}
}
}
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type {
// typ := c.table.find_type(struct_init.typ.typ.name) or {
2020-02-10 13:58:24 +01:00
// c.error('unknown struct: $struct_init.typ.typ.name', struct_init.pos)
// panic('')
// }
2020-04-01 23:23:20 +02:00
if struct_init.typ == table.void_type {
// Short syntax `({foo: bar})`
if c.expected_type == table.void_type {
c.error('unexpected short struct syntax', struct_init.pos)
return table.void_type
}
struct_init.typ = c.expected_type
}
2020-08-06 17:23:54 +02:00
if struct_init.typ == 0 {
c.error('unknown type', struct_init.pos)
}
type_sym := c.table.get_type_symbol(struct_init.typ)
if type_sym.kind == .sum_type && struct_init.fields.len == 1 {
sexpr := struct_init.fields[0].expr.str()
2020-08-22 12:29:15 +02:00
c.error('cast to sum type using `${type_sym.source_name}($sexpr)` not `$type_sym.source_name{$sexpr}`',
struct_init.pos)
}
if type_sym.kind == .interface_ {
2020-08-22 12:29:15 +02:00
c.error('cannot instantiate interface `$type_sym.source_name`', struct_init.pos)
}
if type_sym.kind == .alias {
2020-06-24 16:52:23 +02:00
info := type_sym.info as table.Alias
if info.parent_type.is_number() {
2020-08-22 12:29:15 +02:00
c.error('cannot instantiate number type alias `$type_sym.source_name`', struct_init.pos)
2020-06-24 16:52:23 +02:00
return table.void_type
}
}
2020-05-13 00:50:46 +02:00
if !type_sym.is_public && type_sym.kind != .placeholder && type_sym.mod != c.mod {
2020-08-22 12:29:15 +02:00
c.error('type `$type_sym.source_name` is private', struct_init.pos)
}
match type_sym.kind {
.placeholder {
2020-08-22 12:29:15 +02:00
c.error('unknown struct: $type_sym.source_name', struct_init.pos)
}
// string & array are also structs but .kind of string/array
.struct_, .string, .array, .alias {
mut info := table.Struct{}
if type_sym.kind == .alias {
info_t := type_sym.info as table.Alias
sym := c.table.get_type_symbol(info_t.parent_type)
if sym.kind == .placeholder { // pending import symbol did not resolve
2020-08-22 12:29:15 +02:00
c.error('unknown struct: $type_sym.source_name', struct_init.pos)
return table.void_type
}
if sym.kind != .struct_ {
2020-08-22 12:29:15 +02:00
c.error('alias type name: $sym.source_name is not struct type', struct_init.pos)
}
info = sym.info as table.Struct
} else {
info = type_sym.info as table.Struct
}
if struct_init.is_short {
exp_len := info.fields.len
got_len := struct_init.fields.len
if exp_len != got_len {
amount := if exp_len < got_len { 'many' } else { 'few' }
c.error('too $amount fields in `$type_sym.source_name` literal (expecting $exp_len, got $got_len)',
struct_init.pos)
}
}
mut inited_fields := []string{}
for i, field in struct_init.fields {
mut info_field := table.Field{}
mut field_name := ''
if struct_init.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
}
info_field = info.fields[i]
field_name = info_field.name
struct_init.fields[i].name = field_name
} else {
field_name = field.name
mut exists := false
for f in info.fields {
if f.name == field_name {
info_field = f
exists = true
break
}
}
if !exists {
2020-08-22 12:29:15 +02:00
c.error('unknown field `$field.name` in struct literal of type `$type_sym.source_name`',
2020-04-25 17:49:16 +02:00
field.pos)
continue
}
if field_name in inited_fields {
2020-06-24 14:44:06 +02:00
c.error('duplicate field name in struct literal: `$field_name`',
field.pos)
continue
}
}
inited_fields << field_name
c.expected_type = info_field.typ
expr_type := c.expr(field.expr)
expr_type_sym := c.table.get_type_symbol(expr_type)
field_type_sym := c.table.get_type_symbol(info_field.typ)
2020-06-25 17:12:32 +02:00
if !c.check_types(expr_type, info_field.typ) && expr_type != table.void_type &&
expr_type_sym.kind != .placeholder {
2020-08-22 12:29:15 +02:00
c.error('cannot assign $expr_type_sym.kind `$expr_type_sym.source_name` as `$field_type_sym.source_name` for field `$info_field.name`',
2020-04-25 17:49:16 +02:00
field.pos)
}
2020-05-28 05:50:57 +02:00
if info_field.typ.is_ptr() && !expr_type.is_ptr() && !expr_type.is_pointer() &&
!expr_type.is_number() {
c.error('ref', field.pos)
}
struct_init.fields[i].typ = expr_type
struct_init.fields[i].expected_type = info_field.typ
}
// Check uninitialized refs
for field in info.fields {
if field.has_default_expr || field.name in inited_fields {
continue
}
2020-08-09 03:57:54 +02:00
if field.typ.is_ptr() && !c.pref.translated {
2020-08-22 12:29:15 +02:00
c.warn('reference field `${type_sym.source_name}.$field.name` must be initialized',
2020-04-07 18:51:39 +02:00
struct_init.pos)
}
// Check for `[required]` struct attr
if field.attrs.contains('required') && !struct_init.is_short {
mut found := false
for init_field in struct_init.fields {
if field.name == init_field.name {
found = true
break
}
}
if !found {
c.error('field `${type_sym.source_name}.$field.name` is required',
struct_init.pos)
}
}
}
}
else {}
}
return struct_init.typ
}
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type {
2020-02-20 12:16:18 +01:00
// println('checker: infix expr(op $infix_expr.op.str())')
former_expected_type := c.expected_type
defer {
c.expected_type = former_expected_type
}
c.expected_type = table.void_type
mut left_type := c.expr(infix_expr.left)
// left_type = c.unwrap_genric(c.expr(infix_expr.left))
2020-03-07 00:34:14 +01:00
infix_expr.left_type = left_type
c.expected_type = left_type
right_type := c.expr(infix_expr.right)
// right_type = c.unwrap_genric(c.expr(infix_expr.right))
2020-03-15 00:46:08 +01:00
infix_expr.right_type = right_type
right := c.table.get_type_symbol(right_type)
left := c.table.get_type_symbol(left_type)
left_default := c.table.get_type_symbol(c.table.mktyp(left_type))
left_pos := infix_expr.left.position()
right_pos := infix_expr.right.position()
if (left_type.is_ptr() || left.is_pointer()) &&
infix_expr.op in [.plus, .minus] && !c.inside_unsafe {
c.warn('pointer arithmetic is only allowed in `unsafe` blocks', left_pos)
}
mut return_type := left_type
2020-04-25 20:28:49 +02:00
// Single side check
// Place these branches according to ops' usage frequency to accelerate.
// TODO: First branch includes ops where single side check is not needed, or needed but hasn't been implemented.
// TODO: Some of the checks are not single side. Should find a better way to organize them.
2020-04-25 20:28:49 +02:00
match infix_expr.op {
2020-05-12 19:38:43 +02:00
// .eq, .ne, .gt, .lt, .ge, .le, .and, .logical_or, .dot, .key_as, .right_shift {}
2020-04-25 20:28:49 +02:00
.key_in, .not_in {
match right.kind {
.array {
2020-08-27 06:46:18 +02:00
elem_type := right.array_info().elem_type
right_sym := c.table.get_type_symbol(c.table.mktyp(right.array_info().elem_type))
2020-08-27 06:46:18 +02:00
// if left_default.kind != right_sym.kind {
if !c.check_types(left_type, elem_type) {
2020-08-22 12:29:15 +02:00
c.error('the data type on the left of `$infix_expr.op.str()` (`$left.name`) does not match the array item type (`$right_sym.source_name`)',
2020-05-18 15:43:46 +02:00
infix_expr.pos)
2020-04-25 20:28:49 +02:00
}
}
.map {
key_sym := c.table.get_type_symbol(c.table.mktyp(right.map_info().key_type))
if left_default.kind != key_sym.kind {
2020-08-22 12:29:15 +02:00
c.error('the data type on the left of `$infix_expr.op.str()` (`$left.name`) does not match the map key type `$key_sym.source_name`',
2020-05-18 15:43:46 +02:00
infix_expr.pos)
2020-04-25 21:51:44 +02:00
}
}
2020-04-25 20:28:49 +02:00
.string {
if left.kind != .string {
c.error('the data type on the left of `$infix_expr.op.str()` must be a string (is `$left.name`)',
2020-05-18 15:43:46 +02:00
infix_expr.pos)
2020-04-25 20:28:49 +02:00
}
2020-04-25 21:51:44 +02:00
}
2020-04-25 20:28:49 +02:00
else {
2020-05-18 15:43:46 +02:00
c.error('`$infix_expr.op.str()` can only be used with an array/map/string',
infix_expr.pos)
2020-04-25 20:28:49 +02:00
}
}
2020-04-25 20:28:49 +02:00
return table.bool_type
2020-02-19 19:54:36 +01:00
}
.plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe { // binary operators that expect matching types
if left.kind in [.array, .array_fixed, .map, .struct_] {
if left.has_method(infix_expr.op.str()) {
return_type = left_type
} else {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
c.error('mismatched types `$left_name` and `$right_name`', left_pos)
}
} else if right.kind in [.array, .array_fixed, .map, .struct_] {
if right.has_method(infix_expr.op.str()) {
return_type = right_type
} else {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
c.error('mismatched types `$left_name` and `$right_name`', right_pos)
}
} else {
promoted_type := c.promote(c.table.unalias_num_type(left_type), c.table.unalias_num_type(right_type))
if promoted_type.idx() == table.void_type_idx {
left_name := c.table.type_to_str(left_type)
right_name := c.table.type_to_str(right_type)
c.error('mismatched types `$left_name` and `$right_name`', infix_expr.pos)
} else if promoted_type.is_float() {
if infix_expr.op in [.mod, .xor, .amp, .pipe] {
side := if left_type == promoted_type { 'left' } else { 'right' }
pos := if left_type == promoted_type { left_pos } else { right_pos }
2020-05-28 05:50:57 +02:00
name := if left_type == promoted_type { left.name } else { right.name }
if infix_expr.op == .mod {
2020-06-24 14:44:06 +02:00
c.error('float modulo not allowed, use math.fmod() instead',
pos)
} else {
2020-06-18 20:38:59 +02:00
c.error('$side type of `$infix_expr.op.str()` cannot be non-integer type $name',
2020-05-28 05:50:57 +02:00
pos)
}
}
}
if infix_expr.op in [.div, .mod] {
match infix_expr.right as infix_right {
ast.FloatLiteral {
if infix_right.val.f64() == 0.0 {
oper := if infix_expr.op == .div { 'division' } else { 'modulo' }
c.error('$oper by zero', infix_right.pos)
}
}
ast.IntegerLiteral {
if infix_right.val.int() == 0 {
oper := if infix_expr.op == .div { 'division' } else { 'modulo' }
c.error('$oper by zero', infix_right.pos)
}
}
else {}
}
}
return_type = promoted_type
}
2020-04-25 20:28:49 +02:00
}
.gt, .lt, .ge, .le {
if left.kind in [.array, .array_fixed] && right.kind in [.array, .array_fixed] {
c.error('only `==` and `!=` are defined on arrays', infix_expr.pos)
}
}
2020-04-25 20:28:49 +02:00
.left_shift {
if left.kind == .array {
// `array << elm`
infix_expr.auto_locked, _ = c.fail_if_immutable(infix_expr.left)
left_value_type := c.table.value_type(left_type)
left_value_sym := c.table.get_type_symbol(left_value_type)
if left_value_sym.kind == .interface_ {
if right.kind != .array {
// []Animal << Cat
c.type_implements(right_type, left_value_type, right_pos)
} else {
// []Animal << Cat
2020-05-28 05:50:57 +02:00
c.type_implements(c.table.value_type(right_type), left_value_type,
right_pos)
}
return table.void_type
}
2020-04-25 20:28:49 +02:00
// the expressions have different types (array_x and x)
2020-05-24 04:43:00 +02:00
if c.check_types(right_type, left_value_type) { // , right_type) {
2020-04-25 20:28:49 +02:00
// []T << T
return table.void_type
}
2020-06-27 16:41:29 +02:00
if right.kind == .array &&
c.check_types(left_value_type, c.table.value_type(right_type)) {
2020-04-25 20:28:49 +02:00
// []T << []T
return table.void_type
}
2020-08-22 12:29:15 +02:00
c.error('cannot append `$right.source_name` to `$left.source_name`', right_pos)
2020-04-25 20:28:49 +02:00
return table.void_type
} else {
return c.check_shift(left_type, right_type, left_pos, right_pos)
2020-04-25 06:14:17 +02:00
}
}
.right_shift {
return c.check_shift(left_type, right_type, left_pos, right_pos)
}
2020-06-02 16:18:12 +02:00
.key_is, .not_is {
2020-04-25 20:28:49 +02:00
type_expr := infix_expr.right as ast.Type
typ_sym := c.table.get_type_symbol(type_expr.typ)
if typ_sym.kind == .placeholder {
2020-08-22 12:29:15 +02:00
c.error('$infix_expr.op.str(): type `$typ_sym.source_name` does not exist',
type_expr.pos)
2020-04-25 20:28:49 +02:00
}
if left.kind !in [.interface_, .sum_type] {
2020-06-17 00:59:33 +02:00
c.error('`$infix_expr.op.str()` can only be used with interfaces and sum types',
infix_expr.pos)
}
2020-04-25 20:28:49 +02:00
return table.bool_type
2020-04-20 14:49:26 +02:00
}
.arrow { // `chan <- elem`
if left.kind == .chan {
chan_info := left.chan_info()
elem_type := chan_info.elem_type
if !c.check_types(right_type, elem_type) {
c.error('cannot push `$right.name` on `$left.name`', right_pos)
}
if chan_info.is_mut {
// TODO: The error message of the following could be more specific...
c.fail_if_immutable(infix_expr.right)
}
if elem_type.is_ptr() && !right_type.is_ptr() {
c.error('cannon push non-reference `$right.source_name` on `$left.source_name`',
right_pos)
}
} else {
c.error('cannot push on non-channel `$left.name`', left_pos)
}
return table.void_type
}
2020-06-02 22:21:40 +02:00
else {
// use `()` to make the boolean expression clear error
// for example: `(a && b) || c` instead of `a && b || c`
if infix_expr.op in [.logical_or, .and] {
if infix_expr.left is ast.InfixExpr {
e := infix_expr.left as ast.InfixExpr
if e.op in [.logical_or, .and] && e.op != infix_expr.op {
c.error('use `()` to make the boolean expression clear', infix_expr.pos)
}
}
}
}
2020-04-19 22:29:45 +02:00
}
2020-04-25 20:28:49 +02:00
// TODO: Absorb this block into the above single side check block to accelerate.
if left_type == table.bool_type && infix_expr.op !in [.eq, .ne, .logical_or, .and] {
2020-04-25 21:51:44 +02:00
c.error('bool types only have the following operators defined: `==`, `!=`, `||`, and `&&`',
2020-04-14 04:12:24 +02:00
infix_expr.pos)
2020-06-24 14:44:06 +02:00
} else if left_type == table.string_type &&
infix_expr.op !in [.plus, .eq, .ne, .lt, .gt, .le, .ge] {
2020-04-14 04:12:24 +02:00
// TODO broken !in
c.error('string types only have the following operators defined: `==`, `!=`, `<`, `>`, `<=`, `>=`, and `+`',
2020-04-14 04:12:24 +02:00
infix_expr.pos)
}
2020-04-25 20:28:49 +02:00
// Dual sides check (compatibility check)
2020-08-11 00:51:15 +02:00
if !c.symmetric_check(right_type, left_type) && !c.pref.translated {
2020-04-20 14:49:26 +02:00
// for type-unresolved consts
if left_type == table.void_type || right_type == table.void_type {
return table.void_type
}
2020-04-25 17:49:16 +02:00
c.error('infix expr: cannot use `$right.name` (right expression) as `$left.name`',
infix_expr.pos)
2020-04-20 14:49:26 +02:00
}
2020-07-16 19:40:14 +02:00
/*
if (infix_expr.left is ast.InfixExpr &&
(infix_expr.left as ast.InfixExpr).op == .inc) ||
(infix_expr.right is ast.InfixExpr && (infix_expr.right as ast.InfixExpr).op == .inc) {
c.warn('`++` and `--` are statements, not expressions', infix_expr.pos)
}
*/
return if infix_expr.op.is_relational() {
table.bool_type
} else {
return_type
}
}
// returns name and position of variable that needs write lock
fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) {
mut to_lock := '' // name of variable that needs lock
mut pos := token.Position{} // and its position
mut explicit_lock_needed := false
match mut expr {
ast.CastExpr {
// TODO
return '', pos
}
ast.Ident {
if expr.obj is ast.Var {
mut v := expr.obj as ast.Var
2020-08-09 03:57:54 +02:00
if !v.is_mut && !c.pref.translated {
2020-06-18 20:38:59 +02:00
c.error('`$expr.name` is immutable, declare it with `mut` to make it mutable',
expr.pos)
}
v.is_changed = true
if v.typ.share() == .shared_t {
if expr.name !in c.locked_names {
to_lock = expr.name
pos = expr.pos
}
}
2020-06-18 20:38:59 +02:00
} else if expr.name in c.const_names {
c.error('cannot modify constant `$expr.name`', expr.pos)
}
}
ast.IndexExpr {
to_lock, pos = c.fail_if_immutable(expr.left)
}
ast.ParExpr {
to_lock, pos = c.fail_if_immutable(expr.expr)
}
ast.PrefixExpr {
to_lock, pos = c.fail_if_immutable(expr.right)
}
ast.SelectorExpr {
// retrieve table.Field
2020-06-18 20:38:59 +02:00
if expr.expr_type == 0 {
c.error('0 type in SelectorExpr', expr.pos)
return '', pos
}
mut typ_sym := c.table.get_type_symbol(c.unwrap_generic(expr.expr_type))
if typ_sym.kind == .alias {
alias_info := typ_sym.info as table.Alias
typ_sym = c.table.get_type_symbol(alias_info.parent_type)
}
match typ_sym.kind {
.struct_ {
struct_info := typ_sym.info as table.Struct
2020-06-18 20:38:59 +02:00
field_info := struct_info.find_field(expr.field_name) or {
type_str := c.table.type_to_str(expr.expr_type)
c.error('unknown field `${type_str}.$expr.field_name`', expr.pos)
return '', pos
}
2020-08-09 03:57:54 +02:00
if !field_info.is_mut && !c.pref.translated {
2020-06-18 20:38:59 +02:00
type_str := c.table.type_to_str(expr.expr_type)
c.error('field `$expr.field_name` of struct `$type_str` is immutable',
expr.pos)
}
to_lock, pos = c.fail_if_immutable(expr.expr)
if to_lock != '' {
// No automatic lock for struct access
explicit_lock_needed = true
}
}
.array, .string {
// This should only happen in `builtin`
// TODO Remove `crypto.rand` when possible (see vlib/crypto/rand/rand.v,
// if `c_array_to_bytes_tmp` doesn't exist, then it's safe to remove it)
if c.file.mod.name !in ['builtin', 'crypto.rand'] {
2020-06-18 20:38:59 +02:00
c.error('`$typ_sym.kind` can not be modified', expr.pos)
}
}
else {
2020-06-18 20:38:59 +02:00
c.error('unexpected symbol `$typ_sym.kind`', expr.pos)
}
}
}
ast.CallExpr {
// TODO: should only work for builtin method
if expr.name == 'slice' {
to_lock, pos = c.fail_if_immutable(expr.left)
if to_lock != '' {
// No automatic lock for array slicing (yet(?))
explicit_lock_needed = true
}
} else {
c.error('cannot use function call as mut', expr.pos)
}
}
ast.ArrayInit {
return '', pos
}
else {
c.error('unexpected expression `${typeof(expr)}`', expr.position())
}
}
if explicit_lock_needed {
c.error('`$to_lock` is `shared` and needs explicit lock for `${typeof(expr)}`',
pos)
to_lock = ''
}
return to_lock, pos
}
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) call_expr(mut call_expr ast.CallExpr) table.Type {
c.stmts(call_expr.or_block.stmts)
2020-09-05 12:00:35 +02:00
typ := if call_expr.is_method { c.call_method(call_expr) } else { c.call_fn(call_expr) }
// autofree
free_tmp_arg_vars := c.pref.autofree && c.pref.experimental && !c.is_builtin_mod &&
call_expr.args.len > 0 && !call_expr.args[0].typ.has_flag(.optional)
if free_tmp_arg_vars {
for i, arg in call_expr.args {
if arg.typ != table.string_type {
continue
}
if arg.expr is ast.Ident || arg.expr is ast.StringLiteral {
continue
}
call_expr.args[i].is_tmp_autofree = true
}
}
2020-09-05 12:00:35 +02:00
return typ
}
fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ table.Type, call_expr ast.CallExpr) {
elem_sym := c.table.get_type_symbol(elem_typ)
match call_expr.args[0].expr as arg_expr {
ast.AnonFn {
if arg_expr.decl.args.len > 1 {
c.error('function needs exactly 1 argument', call_expr.pos)
} else if is_map &&
(arg_expr.decl.return_type != elem_typ || arg_expr.decl.args[0].typ != elem_typ) {
2020-08-22 12:29:15 +02:00
c.error('type mismatch, should use `fn(a $elem_sym.source_name) $elem_sym.source_name {...}`',
2020-06-17 00:59:33 +02:00
call_expr.pos)
} else if !is_map &&
(arg_expr.decl.return_type != table.bool_type || arg_expr.decl.args[0].typ != elem_typ) {
2020-08-22 12:29:15 +02:00
c.error('type mismatch, should use `fn(a $elem_sym.source_name) bool {...}`',
2020-06-24 14:44:06 +02:00
call_expr.pos)
}
}
ast.Ident {
if arg_expr.kind == .function {
func := c.table.find_fn(arg_expr.name) or {
c.error('$arg_expr.name is not exist', arg_expr.pos)
return
}
if func.args.len > 1 {
c.error('function needs exactly 1 argument', call_expr.pos)
} else if is_map && (func.return_type != elem_typ || func.args[0].typ != elem_typ) {
2020-08-22 12:29:15 +02:00
c.error('type mismatch, should use `fn(a $elem_sym.source_name) $elem_sym.source_name {...}`',
2020-06-17 00:59:33 +02:00
call_expr.pos)
} else if !is_map &&
(func.return_type != table.bool_type || func.args[0].typ != elem_typ) {
2020-08-22 12:29:15 +02:00
c.error('type mismatch, should use `fn(a $elem_sym.source_name) bool {...}`',
2020-06-17 00:59:33 +02:00
call_expr.pos)
}
}
}
else {}
}
}
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
left_type := c.expr(call_expr.left)
is_generic := left_type.has_flag(.generic)
call_expr.left_type = left_type
2020-05-28 01:19:03 +02:00
left_type_sym := c.table.get_type_symbol(c.unwrap_generic(left_type))
method_name := call_expr.name
2020-06-23 11:39:58 +02:00
if left_type.has_flag(.optional) {
c.error('optional type cannot be called directly', call_expr.left.position())
return table.void_type
}
// TODO: remove this for actual methods, use only for compiler magic
2020-05-01 00:29:54 +02:00
// FIXME: Argument count != 1 will break these
2020-06-24 14:44:06 +02:00
if left_type_sym.kind == .array &&
2020-08-12 05:54:51 +02:00
method_name in ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort'] {
mut elem_typ := table.void_type
2020-08-12 05:54:51 +02:00
is_filter_map := method_name in ['filter', 'map']
is_sort := method_name == 'sort'
if is_filter_map || is_sort {
array_info := left_type_sym.info as table.Array
mut scope := c.file.scope.innermost(call_expr.pos.pos)
2020-08-12 05:54:51 +02:00
if is_filter_map {
scope.update_var_type('it', array_info.elem_type)
} else if is_sort {
2020-08-21 20:17:55 +02:00
c.fail_if_immutable(call_expr.left)
2020-08-12 05:54:51 +02:00
scope.update_var_type('a', array_info.elem_type)
scope.update_var_type('b', array_info.elem_type)
// Verify `.sort(a < b)`
if call_expr.args.len > 0 {
if call_expr.args[0].expr !is ast.InfixExpr {
c.error('`.sort()` requires a `<` or `>` comparison as the first and only argument' +
'\ne.g. `users.sort(a.id < b.id)`', call_expr.pos)
}
}
2020-08-12 05:54:51 +02:00
}
elem_typ = array_info.elem_type
}
2020-05-02 15:26:58 +02:00
// map/filter are supposed to have 1 arg only
2020-05-01 00:29:54 +02:00
mut arg_type := left_type
for arg in call_expr.args {
2020-05-01 00:29:54 +02:00
arg_type = c.expr(arg.expr)
}
call_expr.return_type = left_type
2020-05-01 00:29:54 +02:00
call_expr.receiver_type = left_type
2020-05-02 15:26:58 +02:00
if method_name == 'map' {
// check fn
c.check_map_and_filter(true, elem_typ, call_expr)
arg_sym := c.table.get_type_symbol(arg_type)
// FIXME: match expr failed for now
mut ret_type := 0
match arg_sym.info as info {
table.FnType { ret_type = info.func.return_type }
else { ret_type = arg_type }
}
call_expr.return_type = c.table.find_or_register_array(ret_type, 1, c.mod)
} else if method_name == 'filter' {
// check fn
c.check_map_and_filter(false, elem_typ, call_expr)
2020-05-01 00:29:54 +02:00
} else if method_name == 'clone' {
// need to return `array_xxx` instead of `array`
// in ['clone', 'str'] {
2020-04-25 09:08:53 +02:00
call_expr.receiver_type = left_type.to_ptr()
// call_expr.return_type = call_expr.receiver_type
2020-08-20 16:29:40 +02:00
} else if method_name == 'sort' {
call_expr.return_type = table.void_type
}
2020-05-01 00:29:54 +02:00
return call_expr.return_type
2020-05-26 02:09:26 +02:00
} else if left_type_sym.kind == .map && method_name == 'clone' {
call_expr.return_type = left_type
call_expr.receiver_type = left_type.to_ptr()
return call_expr.return_type
2020-07-14 18:55:44 +02:00
} else if left_type_sym.kind == .array && method_name in ['first', 'last', 'pop'] {
info := left_type_sym.info as table.Array
call_expr.return_type = info.elem_type
2020-07-14 18:55:44 +02:00
if method_name == 'pop' {
call_expr.receiver_type = left_type.to_ptr()
} else {
call_expr.receiver_type = left_type
}
2020-05-01 00:29:54 +02:00
return call_expr.return_type
} else if left_type_sym.kind == .array && method_name in ['insert', 'prepend'] {
array_info := left_type_sym.info as table.Array
elem_sym := c.table.get_type_symbol(array_info.elem_type)
arg_expr := if method_name == 'insert' { call_expr.args[1].expr } else { call_expr.args[0].expr }
arg_sym := c.table.get_type_symbol(c.expr(arg_expr))
if arg_sym.kind == .array {
info := arg_sym.info as table.Array
sym := c.table.get_type_symbol(info.elem_type)
2020-06-24 14:44:06 +02:00
if sym.kind != elem_sym.kind &&
((elem_sym.kind == .int && sym.kind != .any_int) ||
(elem_sym.kind == .f64 && sym.kind != .any_float)) {
2020-08-22 12:29:15 +02:00
c.error('type mismatch, should use `$elem_sym.source_name[]`', arg_expr.position())
}
} else {
2020-06-24 14:44:06 +02:00
if arg_sym.kind != elem_sym.kind &&
((elem_sym.kind == .int && arg_sym.kind != .any_int) ||
(elem_sym.kind == .f64 && arg_sym.kind != .any_float)) {
2020-08-22 12:29:15 +02:00
c.error('type mismatch, should use `$elem_sym.source_name`', arg_expr.position())
}
}
}
if method := c.table.type_find_method(left_type_sym, method_name) {
if !method.is_pub && !c.is_builtin_mod && !c.pref.is_test && left_type_sym.mod != c.mod &&
left_type_sym.mod != '' { // method.mod != c.mod {
// If a private method is called outside of the module
// its receiver type is defined in, show an error.
2020-04-25 17:49:16 +02:00
// println('warn $method_name lef.mod=$left_type_sym.mod c.mod=$c.mod')
2020-08-22 12:29:15 +02:00
c.error('method `${left_type_sym.source_name}.$method_name` is private', call_expr.pos)
}
if method.args[0].is_mut {
c.fail_if_immutable(call_expr.left)
2020-06-06 16:05:16 +02:00
// call_expr.is_mut = true
}
2020-06-24 14:44:06 +02:00
if method.return_type == table.void_type &&
2020-06-27 16:41:29 +02:00
method.ctdefine.len > 0 && method.ctdefine !in c.pref.compile_defines {
call_expr.should_be_skipped = true
}
2020-04-25 17:49:16 +02:00
nr_args := if method.args.len == 0 { 0 } else { method.args.len - 1 }
min_required_args := method.args.len - if method.is_variadic && method.args.len > 1 { 2 } else { 1 }
if call_expr.args.len < min_required_args {
2020-08-22 12:29:15 +02:00
c.error('too few arguments in call to `${left_type_sym.source_name}.$method_name` ($call_expr.args.len instead of $min_required_args)',
call_expr.pos)
2020-04-22 20:20:49 +02:00
} else if !method.is_variadic && call_expr.args.len > nr_args {
2020-08-22 12:29:15 +02:00
c.error('too many arguments in call to `${left_type_sym.source_name}.$method_name` ($call_expr.args.len instead of $nr_args)',
call_expr.pos)
return method.return_type
}
// if method_name == 'clone' {
// println('CLONE nr args=$method.args.len')
// }
// call_expr.args << method.args[0].typ
// call_expr.exp_arg_types << method.args[0].typ
for i, arg in call_expr.args {
2020-05-04 21:03:18 +02:00
exp_arg_typ := if method.is_variadic && i >= method.args.len - 1 { method.args[method.args.len -
1].typ } else { method.args[i + 1].typ }
exp_arg_sym := c.table.get_type_symbol(exp_arg_typ)
2020-05-04 17:32:40 +02:00
c.expected_type = exp_arg_typ
got_arg_typ := c.expr(arg.expr)
call_expr.args[i].typ = got_arg_typ
2020-06-24 14:44:06 +02:00
if method.is_variadic && got_arg_typ.has_flag(.variadic) && call_expr.args.len - 1 > i {
c.error('when forwarding a varg variable, it must be the final argument',
call_expr.pos)
}
if exp_arg_sym.kind == .interface_ {
c.type_implements(got_arg_typ, exp_arg_typ, arg.expr.position())
continue
}
2020-05-24 04:43:00 +02:00
if !c.check_types(got_arg_typ, exp_arg_typ) {
2020-05-04 17:32:40 +02:00
got_arg_sym := c.table.get_type_symbol(got_arg_typ)
// str method, allow type with str method if fn arg is string
// if exp_arg_sym.kind == .string && got_arg_sym.has_method('str') {
2020-06-24 14:44:06 +02:00
// continue
// }
2020-07-22 19:33:43 +02:00
// same ancestor? let it be
if exp_arg_sym.parent_idx == got_arg_sym.parent_idx {
if got_arg_sym.parent_idx != 0 {
continue
}
2020-07-22 19:33:43 +02:00
}
if got_arg_typ != table.void_type {
2020-08-22 12:29:15 +02:00
c.error('cannot use type `$got_arg_sym.source_name` as type `$exp_arg_sym.source_name` in argument ${i+1} to `${left_type_sym.source_name}.$method_name`',
call_expr.pos)
}
2020-05-04 17:32:40 +02:00
}
}
if method.is_unsafe && !c.inside_unsafe {
2020-08-22 12:29:15 +02:00
c.warn('method `${left_type_sym.source_name}.$method_name` must be called from an `unsafe` block',
call_expr.pos)
}
// TODO: typ optimize.. this node can get processed more than once
2020-04-03 15:18:17 +02:00
if call_expr.expected_arg_types.len == 0 {
for i in 1 .. method.args.len {
call_expr.expected_arg_types << method.args[i].typ
}
}
2020-05-28 05:50:57 +02:00
if is_generic {
// We need the receiver to be T in cgen.
// TODO: cant we just set all these to the concrete type in checker? then no need in gen
call_expr.receiver_type = left_type.derive(method.args[0].typ).set_flag(.generic)
2020-05-28 05:50:57 +02:00
} else {
call_expr.receiver_type = method.args[0].typ
}
call_expr.return_type = method.return_type
return method.return_type
}
// TODO: str methods
if method_name == 'str' {
if left_type_sym.kind == .interface_ {
2020-08-22 12:29:15 +02:00
iname := left_type_sym.source_name
c.error('interface `$iname` does not have a .str() method. Use typeof() instead',
call_expr.pos)
}
call_expr.receiver_type = left_type
call_expr.return_type = table.string_type
2020-07-04 13:45:11 +02:00
if call_expr.args.len > 0 {
c.error('.str() method calls should have no arguments', call_expr.pos)
}
return table.string_type
}
// call struct field fn type
// TODO: can we use SelectorExpr for all? this dosent really belong here
if field := c.table.struct_find_field(left_type_sym, method_name) {
field_type_sym := c.table.get_type_symbol(field.typ)
if field_type_sym.kind == .function {
// call_expr.is_method = false
call_expr.is_field = true
info := field_type_sym.info as table.FnType
call_expr.return_type = info.func.return_type
// TODO: check args (do it once for all of the above)
for arg in call_expr.args {
c.expr(arg.expr)
}
return info.func.return_type
}
}
if left_type != table.void_type {
suggestion := util.new_suggestion(method_name, left_type_sym.methods.map(it.name))
2020-08-22 12:29:15 +02:00
c.error(suggestion.say('unknown method: `${left_type_sym.source_name}.$method_name`'),
call_expr.pos)
}
return table.void_type
}
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
fn_name := call_expr.name
2020-04-28 07:21:50 +02:00
if fn_name == 'main' {
c.error('the `main` function cannot be called in the program', call_expr.pos)
}
if fn_name == 'typeof' {
// TODO: impl typeof properly (probably not going to be a fn call)
return table.string_type
}
if call_expr.generic_type.has_flag(.generic) {
if c.mod != '' {
// Need to prepend the module when adding a generic type to a function
// `fn_gen_types['mymod.myfn'] == ['string', 'int']`
c.table.register_fn_gen_type(c.mod + '.' + fn_name, c.cur_generic_type)
} else {
c.table.register_fn_gen_type(fn_name, c.cur_generic_type)
}
// call_expr.generic_type = c.unwrap_generic(call_expr.generic_type)
}
2020-04-12 17:49:31 +02:00
// if c.fileis('json_test.v') {
// println(fn_name)
// }
if fn_name == 'json.encode' {
2020-05-04 16:46:36 +02:00
} else if fn_name == 'json.decode' {
expr := call_expr.args[0].expr
2020-07-08 15:46:58 +02:00
if expr !is ast.Type {
2020-05-05 14:19:31 +02:00
typ := typeof(expr)
c.error('json.decode: first argument needs to be a type, got `$typ`', call_expr.pos)
2020-05-04 16:46:36 +02:00
return table.void_type
}
c.expected_type = table.string_type
call_expr.args[1].typ = c.expr(call_expr.args[1].expr)
2020-05-13 21:59:05 +02:00
if call_expr.args[1].typ != table.string_type {
c.error('json.decode: second argument needs to be a string', call_expr.pos)
}
2020-05-04 16:46:36 +02:00
typ := expr as ast.Type
2020-05-21 22:35:43 +02:00
ret_type := typ.typ.set_flag(.optional)
call_expr.return_type = ret_type
return ret_type
2020-05-01 12:02:31 +02:00
}
// look for function in format `mod.fn` or `fn` (builtin)
mut f := table.Fn{}
mut found := false
mut found_in_args := false
// anon fn direct call
if call_expr.left is ast.AnonFn {
// it was set to anon for checker errors, clear for gen
call_expr.name = ''
c.expr(call_expr.left)
anon_fn := call_expr.left as ast.AnonFn
anon_fn_sym := c.table.get_type_symbol(anon_fn.typ)
f = (anon_fn_sym.info as table.FnType).func
found = true
}
// try prefix with current module as it would have never gotten prefixed
if !found && !fn_name.contains('.') && call_expr.mod != 'builtin' {
name_prefixed := '${call_expr.mod}.$fn_name'
if f1 := c.table.find_fn(name_prefixed) {
call_expr.name = name_prefixed
found = true
f = f1
}
}
// already prefixed (mod.fn) or C/builtin/main
if !found {
if f1 := c.table.find_fn(fn_name) {
found = true
f = f1
}
}
if c.pref.is_script && !found {
os_name := 'os.$fn_name'
if f1 := c.table.find_fn(os_name) {
call_expr.name = os_name
found = true
f = f1
}
}
// check for arg (var) of fn type
if !found {
scope := c.file.scope.innermost(call_expr.pos.pos)
2020-04-15 01:45:27 +02:00
if v := scope.find_var(fn_name) {
if v.typ != 0 {
vts := c.table.get_type_symbol(v.typ)
if vts.kind == .function {
info := vts.info as table.FnType
f = info.func
found = true
found_in_args = true
}
}
}
}
if !found {
c.error('unknown function: $fn_name', call_expr.pos)
return table.void_type
}
if !found_in_args {
scope := c.file.scope.innermost(call_expr.pos.pos)
if _ := scope.find_var(fn_name) {
2020-04-29 12:20:22 +02:00
c.error('ambiguous call to: `$fn_name`, may refer to fn `$fn_name` or variable `$fn_name`',
call_expr.pos)
}
}
if !f.is_pub && f.language == .v && f.name.len > 0 && f.mod.len > 0 && f.mod != c.mod {
c.error('function `$f.name` is private. curmod=$c.mod fmod=$f.mod', call_expr.pos)
2020-06-23 16:25:24 +02:00
}
if f.is_deprecated {
c.warn('function `$f.name` has been deprecated', call_expr.pos)
}
if f.is_unsafe && !c.inside_unsafe && f.language == .c && f.name[2] in [`m`, `s`] &&
2020-07-20 22:30:09 +02:00
f.mod == 'builtin' {
// builtin C.m*, C.s* only - temp
2020-07-20 22:30:09 +02:00
c.warn('function `$f.name` must be called from an `unsafe` block', call_expr.pos)
}
if f.is_generic && f.return_type.has_flag(.generic) {
rts := c.table.get_type_symbol(f.return_type)
if rts.kind == .struct_ {
rts_info := rts.info as table.Struct
if rts_info.generic_types.len > 0 {
// TODO: multiple generic types
// for gt in rts_info.generic_types {
// gtss := c.table.get_type_symbol(gt)
// }
gts := c.table.get_type_symbol(call_expr.generic_type)
nrt := '$rts.name<$gts.name>'
idx := c.table.type_idxs[nrt]
if idx == 0 {
c.error('unknown type: $nrt', call_expr.pos)
}
call_expr.return_type = table.new_type(idx).derive(f.return_type)
}
}
} else {
call_expr.return_type = f.return_type
}
2020-06-24 14:44:06 +02:00
if f.return_type == table.void_type &&
2020-06-27 16:41:29 +02:00
f.ctdefine.len > 0 && f.ctdefine !in c.pref.compile_defines {
call_expr.should_be_skipped = true
}
if f.language != .v || call_expr.language != .v {
for arg in call_expr.args {
c.expr(arg.expr)
}
return f.return_type
}
min_required_args := if f.is_variadic { f.args.len - 1 } else { f.args.len }
if call_expr.args.len < min_required_args {
c.error('too few arguments in call to `$fn_name` ($call_expr.args.len instead of $min_required_args)',
call_expr.pos)
} else if !f.is_variadic && call_expr.args.len > f.args.len {
c.error('too many arguments in call to `$fn_name` ($call_expr.args.len instead of $f.args.len)',
call_expr.pos)
return f.return_type
}
// println can print anything
2020-05-12 19:38:43 +02:00
if (fn_name == 'println' || fn_name == 'print') && call_expr.args.len > 0 {
c.expected_type = table.string_type
call_expr.args[0].typ = c.expr(call_expr.args[0].expr)
// check optional argument
if call_expr.args[0].typ.has_flag(.optional) {
c.error('cannot print optional type', call_expr.args[0].expr.position())
}
/*
// TODO: optimize `struct T{} fn (t &T) str() string {return 'abc'} mut a := []&T{} a << &T{} println(a[0])`
// It currently generates:
// `println(T_str_no_ptr(*(*(T**)array_get(a, 0))));`
// ... which works, but could be just:
// `println(T_str(*(T**)array_get(a, 0)));`
prexpr := call_expr.args[0].expr
prtyp := call_expr.args[0].typ
prtyp_sym := c.table.get_type_symbol(prtyp)
prtyp_is_ptr := prtyp.is_ptr()
prhas_str, prexpects_ptr, prnr_args := prtyp_sym.str_method_info()
2020-08-22 12:29:15 +02:00
eprintln('>>> println hack typ: ${prtyp} | sym.source_name: ${prtyp_sym.source_name} | is_ptr: $prtyp_is_ptr | has_str: $prhas_str | expects_ptr: $prexpects_ptr | nr_args: $prnr_args | expr: ${prexpr.str()} ')
*/
return f.return_type
2020-03-26 11:32:29 +01:00
}
// TODO: typ optimize.. this node can get processed more than once
if call_expr.expected_arg_types.len == 0 {
for arg in f.args {
call_expr.expected_arg_types << arg.typ
}
}
for i, call_arg in call_expr.args {
arg := if f.is_variadic && i >= f.args.len - 1 { f.args[f.args.len - 1] } else { f.args[i] }
c.expected_type = arg.typ
typ := c.expr(call_arg.expr)
call_expr.args[i].typ = typ
typ_sym := c.table.get_type_symbol(typ)
arg_typ_sym := c.table.get_type_symbol(arg.typ)
2020-06-04 14:38:54 +02:00
if f.is_variadic && typ.has_flag(.variadic) && call_expr.args.len - 1 > i {
2020-06-24 14:44:06 +02:00
c.error('when forwarding a varg variable, it must be the final argument',
call_expr.pos)
}
if call_arg.is_mut {
c.fail_if_immutable(call_arg.expr)
if !arg.is_mut {
tok := call_arg.share.str()
c.error('`$call_expr.name` parameter `$arg.name` is not `$tok`, `$tok` is not needed`',
call_arg.expr.position())
} else if arg.typ.share() != call_arg.share {
c.error('wrong shared type', call_arg.expr.position())
}
} else {
if arg.is_mut && (!call_arg.is_mut || arg.typ.share() != call_arg.share) {
tok := call_arg.share.str()
c.error('`$call_expr.name` parameter `$arg.name` is `$tok`, you need to provide `$tok` e.g. `$tok arg${i+1}`',
call_arg.expr.position())
}
}
// Handle expected interface
if arg_typ_sym.kind == .interface_ {
c.type_implements(typ, arg.typ, call_arg.expr.position())
continue
}
// Handle expected interface array
/*
if exp_type_sym.kind == .array && t.get_type_symbol(t.value_type(exp_idx)).kind == .interface_ {
return true
}
*/
2020-05-24 04:43:00 +02:00
if !c.check_types(typ, arg.typ) {
// str method, allow type with str method if fn arg is string
// Passing an int or a string array produces a c error here
// Deleting this condition results in propper V error messages
// if arg_typ_sym.kind == .string && typ_sym.has_method('str') {
2020-09-13 16:21:40 +02:00
// continue
// }
if typ_sym.kind == .void && arg_typ_sym.kind == .string {
continue
}
if f.is_generic {
continue
}
if typ_sym.kind == .array_fixed {
}
if typ_sym.kind == .function && arg_typ_sym.kind == .function {
2020-08-22 12:29:15 +02:00
candidate_fn_name := if typ_sym.source_name.starts_with('anon_') { 'anonymous function' } else { 'fn `$typ_sym.source_name`' }
c.error('cannot use $candidate_fn_name as function type `$arg_typ_sym.str()` in argument ${i+1} to `$fn_name`',
call_expr.pos)
} else {
2020-08-22 12:29:15 +02:00
c.error('cannot use type `$typ_sym.source_name` as type `$arg_typ_sym.source_name` in argument ${i+1} to `$fn_name`',
call_expr.pos)
}
}
}
2020-05-21 18:15:04 +02:00
if call_expr.generic_type != table.void_type && f.return_type != 0 { // table.t_type {
// Handle `foo<T>() T` => `foo<int>() int` => return int
2020-06-22 14:54:24 +02:00
return_sym := c.table.get_type_symbol(f.return_type)
2020-08-22 12:29:15 +02:00
if return_sym.source_name == 'T' {
2020-05-21 18:15:04 +02:00
return call_expr.generic_type
2020-06-22 14:54:24 +02:00
} else if return_sym.kind == .array {
elem_info := return_sym.info as table.Array
elem_sym := c.table.get_type_symbol(elem_info.elem_type)
2020-08-22 12:29:15 +02:00
if elem_sym.source_name == 'T' {
2020-06-22 14:54:24 +02:00
idx := c.table.find_or_register_array(call_expr.generic_type, 1, return_sym.mod)
return table.new_type(idx)
}
2020-05-21 18:15:04 +02:00
}
}
if f.is_generic {
return call_expr.return_type
}
return f.return_type
}
2020-05-31 10:22:18 +02:00
fn (mut c Checker) type_implements(typ, inter_typ table.Type, pos token.Position) bool {
typ_sym := c.table.get_type_symbol(typ)
inter_sym := c.table.get_type_symbol(inter_typ)
styp := c.table.type_to_str(typ)
for imethod in inter_sym.methods {
if method := typ_sym.find_method(imethod.name) {
if !imethod.is_same_method_as(method) {
2020-08-22 12:29:15 +02:00
c.error('`$styp` incorrectly implements method `$imethod.name` of interface `$inter_sym.source_name`, expected `${c.table.fn_to_str(imethod)}`',
pos)
2020-05-31 10:22:18 +02:00
return false
}
continue
}
c.error("`$styp` doesn't implement method `$imethod.name`", pos)
}
mut inter_info := inter_sym.info as table.Interface
if typ !in inter_info.types && typ_sym.kind != .interface_ {
inter_info.types << typ
}
2020-05-31 10:22:18 +02:00
return true
}
// return the actual type of the expression, once the optional is handled
pub fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type table.Type) table.Type {
2020-05-21 22:35:43 +02:00
if expr is ast.CallExpr {
if expr.return_type.has_flag(.optional) {
if expr.or_block.kind == .absent {
if ret_type != table.void_type {
c.error('${expr.name}() returns an option, but you missed to add an `or {}` block to it',
expr.pos)
}
} else {
c.check_or_expr(expr.or_block, ret_type)
}
// remove optional flag
// return ret_type.clear_flag(.optional)
// TODO: currently unwrapped in assign, would need to refactor assign to unwrap here
return ret_type
} else if expr.or_block.kind == .block {
c.error('unexpected `or` block, the function `$expr.name` does not return an optional',
expr.pos)
} else if expr.or_block.kind == .propagate {
c.error('unexpected `?`, the function `$expr.name`, does not return an optional',
expr.pos)
}
}
return ret_type
}
pub fn (mut c Checker) check_or_expr(mut or_expr ast.OrExpr, ret_type table.Type) {
if or_expr.kind == .propagate {
if !c.cur_fn.return_type.has_flag(.optional) && c.cur_fn.name != 'main.main' {
2020-06-18 20:38:59 +02:00
c.error('to propagate the optional call, `$c.cur_fn.name` must itself return an optional',
or_expr.pos)
2020-05-23 08:51:15 +02:00
}
return
}
stmts_len := or_expr.stmts.len
if stmts_len == 0 {
if ret_type != table.void_type {
// x := f() or {}
c.error('assignment requires a non empty `or {}` block', or_expr.pos)
return
}
// allow `f() or {}`
return
}
mut last_stmt := or_expr.stmts[stmts_len - 1]
if ret_type != table.void_type {
2020-05-23 08:51:15 +02:00
if !(last_stmt is ast.Return || last_stmt is ast.BranchStmt || last_stmt is ast.ExprStmt) {
expected_type_name := c.table.get_type_symbol(ret_type).name
c.error('last statement in the `or {}` block should return `$expected_type_name`',
or_expr.pos)
return
}
match mut last_stmt {
ast.ExprStmt {
2020-06-18 20:38:59 +02:00
last_stmt.typ = c.expr(last_stmt.expr)
type_fits := c.check_types(last_stmt.typ, ret_type)
is_panic_or_exit := is_expr_panic_or_exit(last_stmt.expr)
if type_fits || is_panic_or_exit {
return
}
2020-06-18 20:38:59 +02:00
type_name := c.table.get_type_symbol(last_stmt.typ).name
expected_type_name := c.table.get_type_symbol(ret_type).name
c.error('wrong return type `$type_name` in the `or {}` block, expected `$expected_type_name`',
2020-06-18 20:38:59 +02:00
last_stmt.pos)
return
}
ast.BranchStmt {
2020-06-18 20:38:59 +02:00
if last_stmt.tok.kind !in [.key_continue, .key_break] {
c.error('only break/continue is allowed as a branch statement in the end of an `or {}` block',
2020-06-18 20:38:59 +02:00
last_stmt.tok.position())
return
}
}
else {}
}
return
}
}
fn is_expr_panic_or_exit(expr ast.Expr) bool {
match expr {
2020-06-18 20:38:59 +02:00
ast.CallExpr { return expr.name in ['panic', 'exit'] }
2020-04-25 17:49:16 +02:00
else { return false }
}
}
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) table.Type {
typ := c.expr(selector_expr.expr)
2020-03-11 05:56:15 +01:00
if typ == table.void_type_idx {
c.error('unknown selector expression', selector_expr.pos)
return table.void_type
}
if selector_expr.expr is ast.TypeOf as left {
if selector_expr.field_name == 'name' {
return table.string_type
} else {
c.error('expected `.name`, not `.$selector_expr.field_name` after `typeof` expression',
selector_expr.pos)
}
}
2020-03-07 04:45:35 +01:00
selector_expr.expr_type = typ
2020-05-27 15:56:21 +02:00
sym := c.table.get_type_symbol(c.unwrap_generic(typ))
2020-05-09 15:16:48 +02:00
field_name := selector_expr.field_name
2020-02-29 09:04:47 +01:00
// variadic
if typ.has_flag(.variadic) || sym.kind == .array_fixed || sym.kind == .chan {
if field_name == 'len' || (sym.kind == .chan && field_name == 'cap') {
selector_expr.typ = table.int_type
2020-02-29 09:04:47 +01:00
return table.int_type
}
}
2020-05-27 15:56:21 +02:00
if field := c.table.struct_find_field(sym, field_name) {
if sym.mod != c.mod && !field.is_pub {
2020-08-22 12:29:15 +02:00
c.error('field `${sym.source_name}.$field_name` is not public', selector_expr.pos)
2020-05-11 14:38:25 +02:00
}
selector_expr.typ = field.typ
return field.typ
}
2020-05-27 15:56:21 +02:00
if sym.kind != .struct_ {
if sym.kind != .placeholder {
2020-08-22 12:29:15 +02:00
c.error('`$sym.source_name` is not a struct', selector_expr.pos)
}
} else {
2020-08-22 12:29:15 +02:00
c.error('type `$sym.source_name` has no field or method `$field_name`', selector_expr.pos)
2020-02-08 16:59:57 +01:00
}
2020-02-22 12:58:16 +01:00
return table.void_type
}
// TODO: non deferred
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) return_stmt(mut return_stmt ast.Return) {
2020-05-23 08:51:15 +02:00
c.expected_type = c.cur_fn.return_type
if return_stmt.exprs.len > 0 && c.expected_type == table.void_type {
c.error('too many arguments to return, current function does not return anything',
2020-04-10 18:11:43 +02:00
return_stmt.pos)
return
2020-06-24 12:45:15 +02:00
} else if return_stmt.exprs.len == 0 && !(c.expected_type == table.void_type ||
2020-06-24 14:44:06 +02:00
c.table.get_type_symbol(c.expected_type).kind == .void) {
2020-04-11 04:09:41 +02:00
c.error('too few arguments to return', return_stmt.pos)
2020-03-22 13:19:45 +01:00
return
}
2020-04-10 18:11:43 +02:00
if return_stmt.exprs.len == 0 {
return
}
expected_type := c.unwrap_generic(c.expected_type)
expected_type_sym := c.table.get_type_symbol(expected_type)
2020-06-04 14:38:54 +02:00
exp_is_optional := expected_type.has_flag(.optional)
mut expected_types := [expected_type]
if expected_type_sym.kind == .multi_return {
mr_info := expected_type_sym.info as table.MultiReturn
expected_types = mr_info.types
}
mut got_types := []table.Type{}
for expr in return_stmt.exprs {
typ := c.expr(expr)
// Unpack multi return types
2020-05-28 05:50:57 +02:00
sym := c.table.get_type_symbol(typ)
if sym.kind == .multi_return {
for t in sym.mr_info().types {
got_types << t
2020-05-28 01:19:03 +02:00
}
} else {
got_types << typ
}
}
2020-03-16 07:42:45 +01:00
return_stmt.types = got_types
// allow `none` & `error (Option)` return types for function that returns optional
2020-04-25 09:08:53 +02:00
if exp_is_optional && got_types[0].idx() in [table.none_type_idx, c.table.type_idxs['Option']] {
return
}
if expected_types.len > 0 && expected_types.len != got_types.len {
2020-03-21 07:01:06 +01:00
c.error('wrong number of return arguments', return_stmt.pos)
return
}
for i, exp_type in expected_types {
got_typ := c.unwrap_generic(got_types[i])
if got_typ.has_flag(.optional) &&
(!exp_type.has_flag(.optional) || c.table.type_to_str(got_typ) != c.table.type_to_str(exp_type)) {
2020-06-04 10:40:32 +02:00
pos := return_stmt.exprs[i].position()
c.error('cannot use `${c.table.type_to_str(got_typ)}` as type `${c.table.type_to_str(exp_type)}` in return argument',
pos)
2020-06-04 10:40:32 +02:00
}
if !c.check_types(got_typ, exp_type) {
got_typ_sym := c.table.get_type_symbol(got_typ)
mut exp_typ_sym := c.table.get_type_symbol(exp_type)
2020-04-17 16:16:56 +02:00
pos := return_stmt.exprs[i].position()
2020-05-30 19:54:16 +02:00
if exp_typ_sym.kind == .interface_ {
c.type_implements(got_typ, exp_type, return_stmt.pos)
continue
}
2020-08-22 12:29:15 +02:00
c.error('cannot use `$got_typ_sym.source_name` as type `$exp_typ_sym.source_name` in return argument',
2020-04-25 17:49:16 +02:00
pos)
}
if got_typ.is_ptr() && !exp_type.is_ptr() {
pos := return_stmt.exprs[i].position()
c.error('fn `$c.cur_fn.name` expects you to return a non reference type `${c.table.type_to_str(exp_type)}`, but you are returning `${c.table.type_to_str(got_typ)}` instead',
pos)
}
}
}
2020-04-25 17:49:16 +02:00
pub fn (mut c Checker) enum_decl(decl ast.EnumDecl) {
2020-05-16 16:12:23 +02:00
c.check_valid_pascal_case(decl.name, 'enum name', decl.pos)
mut seen := []int{}
for i, field in decl.fields {
2020-08-09 03:57:54 +02:00
if !c.pref.experimental && util.contains_capital(field.name) {
// TODO C2V uses hundreds of enums with capitals, remove -experimental check once it's handled
2020-05-16 16:12:23 +02:00
c.error('field name `$field.name` cannot contain uppercase letters, use snake_case instead',
field.pos)
}
2020-04-30 18:06:14 +02:00
for j in 0 .. i {
if field.name == decl.fields[j].name {
c.error('field name `$field.name` duplicate', field.pos)
}
}
2020-04-10 14:44:01 +02:00
if field.has_expr {
match field.expr as field_expr {
ast.IntegerLiteral {
val := field_expr.val.i64()
if val < enum_min || val > enum_max {
c.error('enum value `$val` overflows int', field_expr.pos)
} else if !decl.is_multi_allowed && int(val) in seen {
c.error('enum value `$val` already exists', field_expr.pos)
}
seen << int(val)
}
2020-04-09 19:23:49 +02:00
ast.PrefixExpr {}
else {
2020-04-19 22:26:04 +02:00
if field.expr is ast.Ident {
expr := field.expr as ast.Ident
if expr.language == .c {
2020-04-19 22:26:04 +02:00
continue
}
}
mut pos := field.expr.position()
2020-04-10 00:09:34 +02:00
if pos.pos == 0 {
pos = field.pos
}
2020-04-11 04:09:41 +02:00
c.error('default value for enum has to be an integer', pos)
2020-04-09 19:23:49 +02:00
}
}
} else {
if seen.len > 0 {
last := seen[seen.len - 1]
if last == enum_max {
c.error('enum value overflows', field.pos)
}
seen << last + 1
} else {
seen << 0
}
2020-04-09 19:23:49 +02:00
}
}
}
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) {
2020-04-25 17:49:16 +02:00
c.expected_type = table.none_type // TODO a hack to make `x := if ... work`
2020-08-10 19:54:38 +02:00
defer {
c.expected_type = table.void_type
}
right_first := assign_stmt.right[0]
mut right_len := assign_stmt.right.len
mut right_type0 := table.void_type
if right_first is ast.CallExpr || right_first is ast.IfExpr || right_first is ast.MatchExpr {
right_type0 = c.expr(right_first)
2020-06-24 14:44:06 +02:00
assign_stmt.right_types = [
c.check_expr_opt_call(right_first, right_type0),
2020-06-24 14:44:06 +02:00
]
right_type_sym0 := c.table.get_type_symbol(right_type0)
if right_type_sym0.kind == .multi_return {
assign_stmt.right_types = right_type_sym0.mr_info().types
right_len = assign_stmt.right_types.len
2020-06-17 04:05:13 +02:00
} else if right_type0 == table.void_type {
right_len = 0
}
}
if assign_stmt.left.len != right_len {
if right_first is ast.CallExpr {
c.error('assignment mismatch: $assign_stmt.left.len variable(s) but `${right_first.name}()` returns $right_len value(s)',
assign_stmt.pos)
} else {
c.error('assignment mismatch: $assign_stmt.left.len variable(s) $right_len value(s)',
assign_stmt.pos)
}
return
}
// Check `x := &y` and `mut x := <-ch`
if right_first is ast.PrefixExpr {
node := right_first
left_first := assign_stmt.left[0]
if left_first is ast.Ident {
assigned_var := left_first
if node.right is ast.Ident {
ident := node.right as ast.Ident
scope := c.file.scope.innermost(node.pos.pos)
if v := scope.find_var(ident.name) {
right_type0 = v.typ
if node.op == .amp {
if !v.is_mut && assigned_var.is_mut && !c.inside_unsafe {
c.error('`$ident.name` is immutable, cannot have a mutable reference to it',
node.pos)
}
}
}
}
if node.op == .arrow {
if assigned_var.is_mut {
right_sym := c.table.get_type_symbol(right_type0)
if right_sym.kind == .chan {
chan_info := right_sym.chan_info()
if chan_info.elem_type.is_ptr() && !chan_info.is_mut {
c.error('cannot have a mutable reference to object from `$right_sym.source_name`',
node.pos)
}
}
}
}
}
}
//
is_decl := assign_stmt.op == .decl_assign
for i, left in assign_stmt.left {
if left is ast.CallExpr {
c.error('cannot call function `${left.name}()` on the left side of an assignment',
left.pos)
}
is_blank_ident := left.is_blank_ident()
mut left_type := table.void_type
if !is_decl && !is_blank_ident {
left_type = c.expr(left)
c.expected_type = c.unwrap_generic(left_type)
}
if assign_stmt.right_types.len < assign_stmt.left.len { // first type or multi return types added above
right_type := c.expr(assign_stmt.right[i])
if assign_stmt.right_types.len == i {
assign_stmt.right_types << c.check_expr_opt_call(assign_stmt.right[i], right_type)
}
}
right := if i < assign_stmt.right.len { assign_stmt.right[i] } else { assign_stmt.right[0] }
right_type := assign_stmt.right_types[i]
if is_decl {
left_type = c.table.mktyp(right_type)
// we are unwrapping here instead if check_expr_opt_call currently
2020-06-17 04:05:13 +02:00
if left_type.has_flag(.optional) {
left_type = left_type.clear_flag(.optional)
}
} else {
// Make sure the variable is mutable
c.fail_if_immutable(left)
// left_type = c.expr(left)
}
assign_stmt.left_types << left_type
match mut left {
ast.Ident {
2020-06-18 20:38:59 +02:00
if left.kind == .blank_ident {
left_type = right_type
assign_stmt.left_types[i] = right_type
if assign_stmt.op !in [.assign, .decl_assign] {
2020-06-18 20:38:59 +02:00
c.error('cannot modify blank `_` identifier', left.pos)
}
2020-06-17 04:05:13 +02:00
} else {
if is_decl {
2020-06-18 20:38:59 +02:00
c.check_valid_snake_case(left.name, 'variable name', left.pos)
2020-06-17 04:05:13 +02:00
}
2020-06-18 20:38:59 +02:00
mut ident_var_info := left.var_info()
if ident_var_info.share == .shared_t {
left_type = left_type.set_flag(.shared_f)
}
if ident_var_info.share == .atomic_t {
left_type = left_type.set_flag(.atomic_f)
}
assign_stmt.left_types[i] = left_type
ident_var_info.typ = left_type
2020-06-18 20:38:59 +02:00
left.info = ident_var_info
if left_type != 0 {
match mut left.obj as v {
ast.Var { v.typ = left_type }
ast.GlobalDecl { v.typ = left_type }
else {}
}
/*
if left.obj is ast.Var as v {
v.typ = left_type
} else if left.obj is ast.GlobalDecl as v {
v.typ = left_type
}
*/
}
}
}
ast.PrefixExpr {
// Do now allow `*x = y` outside `unsafe`
2020-06-18 20:38:59 +02:00
if left.op == .mul && !c.inside_unsafe {
c.error('modifying variables via dereferencing can only be done in `unsafe` blocks',
2020-06-17 04:05:13 +02:00
assign_stmt.pos)
}
}
else {
if is_decl {
c.error('non-name `$left` on left side of `:=`', left.position())
}
}
}
left_type_unwrapped := c.unwrap_generic(left_type)
right_type_unwrapped := c.unwrap_generic(right_type)
left_sym := c.table.get_type_symbol(left_type_unwrapped)
right_sym := c.table.get_type_symbol(right_type_unwrapped)
if (left_type.is_ptr() || left_sym.is_pointer()) &&
assign_stmt.op !in [.assign, .decl_assign] && !c.inside_unsafe {
c.warn('pointer arithmetic is only allowed in `unsafe` blocks', assign_stmt.pos)
}
2020-08-10 19:54:38 +02:00
if c.pref.translated {
// TODO fix this in C2V instead, for example cast enums to int before using `|` on them.
2020-08-11 16:26:49 +02:00
// TODO replace all c.pref.translated checks with `$if !translated` for performance
2020-08-11 00:51:15 +02:00
continue
2020-08-10 19:54:38 +02:00
}
// Single side check
match assign_stmt.op {
.assign {} // No need to do single side check for =. But here put it first for speed.
.plus_assign {
if !left_sym.is_number() && left_type != table.string_type && !left_sym.is_pointer() {
2020-08-22 12:29:15 +02:00
c.error('operator += not defined on left operand type `$left_sym.source_name`',
2020-06-17 04:05:13 +02:00
left.position())
} else if !right_sym.is_number() && right_type != table.string_type && !right_sym.is_pointer() {
2020-08-22 12:29:15 +02:00
c.error('operator += not defined on right operand type `$right_sym.source_name`',
2020-06-17 04:05:13 +02:00
right.position())
}
if right is ast.IntegerLiteral && right.str().int() == 1 {
c.error('use `++` instead of `+= 1`', assign_stmt.pos)
}
}
.minus_assign {
if !left_sym.is_number() && !left_sym.is_pointer() {
2020-08-22 12:29:15 +02:00
c.error('operator -= not defined on left operand type `$left_sym.source_name`',
2020-06-17 04:05:13 +02:00
left.position())
} else if !right_sym.is_number() && !right_sym.is_pointer() {
2020-08-22 12:29:15 +02:00
c.error('operator -= not defined on right operand type `$right_sym.source_name`',
2020-06-17 04:05:13 +02:00
right.position())
}
if right is ast.IntegerLiteral && right.str().int() == 1 {
c.error('use `--` instead of `-= 1`', assign_stmt.pos)
}
}
.mult_assign, .div_assign {
if !left_sym.is_number() {
2020-08-22 12:29:15 +02:00
c.error('operator $assign_stmt.op.str() not defined on left operand type `$left_sym.source_name`',
left.position())
} else if !right_sym.is_number() {
2020-08-22 12:29:15 +02:00
c.error('operator $assign_stmt.op.str() not defined on right operand type `$right_sym.source_name`',
right.position())
}
}
.and_assign, .or_assign, .xor_assign, .mod_assign, .left_shift_assign, .right_shift_assign {
if !left_sym.is_int() {
2020-08-22 12:29:15 +02:00
c.error('operator $assign_stmt.op.str() not defined on left operand type `$left_sym.source_name`',
left.position())
} else if !right_sym.is_int() {
2020-08-22 12:29:15 +02:00
c.error('operator $assign_stmt.op.str() not defined on right operand type `$right_sym.source_name`',
right.position())
}
}
else {}
}
// Dual sides check (compatibility check)
if !is_blank_ident && !c.check_types(right_type_unwrapped, left_type_unwrapped) &&
right_sym.kind != .placeholder {
2020-08-22 12:29:15 +02:00
c.error('cannot assign `$right_sym.source_name` to `$left.str()` of type `$left_sym.source_name`',
right.position())
}
2020-02-27 16:51:39 +01:00
}
2020-02-27 00:12:37 +01:00
}
2020-02-06 17:38:02 +01:00
fn (mut c Checker) check_array_init_para_type(para string, expr ast.Expr, pos token.Position) {
sym := c.table.get_type_symbol(c.expr(expr))
if sym.kind !in [.int, .any_int] {
c.error('array $para needs to be an int', pos)
}
}
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) array_init(mut array_init ast.ArrayInit) table.Type {
2020-03-24 16:18:37 +01:00
// println('checker: array init $array_init.pos.line_nr $c.file.path')
mut elem_type := table.void_type
2020-03-04 02:50:32 +01:00
// []string - was set in parser
if array_init.typ != table.void_type {
if array_init.exprs.len == 0 {
if array_init.has_cap {
c.check_array_init_para_type('cap', array_init.cap_expr, array_init.pos)
}
if array_init.has_len {
c.check_array_init_para_type('len', array_init.len_expr, array_init.pos)
}
}
sym := c.table.get_type_symbol(array_init.elem_type)
if sym.kind == .placeholder {
2020-08-22 12:29:15 +02:00
c.error('unknown type `$sym.source_name`', array_init.elem_type_pos)
}
2020-03-04 02:50:32 +01:00
return array_init.typ
}
// a = []
2020-03-04 02:50:32 +01:00
if array_init.exprs.len == 0 {
type_sym := c.table.get_type_symbol(c.expected_type)
if type_sym.kind != .array {
2020-06-24 14:44:06 +02:00
c.error('array_init: no type specified (maybe: `[]Type{}` instead of `[]`)',
array_init.pos)
return table.void_type
}
// TODO: seperate errors once bug is fixed with `x := if expr { ... } else { ... }`
// if c.expected_type == table.void_type {
// c.error('array_init: use `[]Type{}` instead of `[]`', array_init.pos)
// return table.void_type
// }
array_info := type_sym.array_info()
array_init.elem_type = array_info.elem_type
2020-03-04 02:50:32 +01:00
return c.expected_type
}
// [1,2,3]
if array_init.exprs.len > 0 && array_init.elem_type == table.void_type {
2020-04-29 12:20:22 +02:00
mut expected_value_type := table.void_type
mut expecting_interface_array := false
cap := array_init.exprs.len
mut interface_types := []table.Type{cap: cap}
if c.expected_type != 0 {
expected_value_type = c.table.value_type(c.expected_type)
if c.table.get_type_symbol(expected_value_type).kind == .interface_ {
// Array of interfaces? (`[dog, cat]`) Save the interface type (`Animal`)
expecting_interface_array = true
array_init.interface_type = expected_value_type
array_init.is_interface = true
}
}
// expecting_interface_array := c.expected_type != 0 &&
// c.table.get_type_symbol(c.table.value_type(c.expected_type)).kind == .interface_
//
// if expecting_interface_array {
// println('ex $c.expected_type')
// }
2020-03-04 02:50:32 +01:00
for i, expr in array_init.exprs {
typ := c.expr(expr)
2020-04-29 12:20:22 +02:00
if expecting_interface_array {
if i == 0 {
elem_type = expected_value_type
c.expected_type = elem_type
}
interface_types << typ
continue
}
2020-03-04 02:50:32 +01:00
// The first element's type
if i == 0 {
elem_type = c.table.mktyp(typ)
c.expected_type = elem_type
2020-03-04 02:50:32 +01:00
continue
}
if !c.check_types(typ, elem_type) {
2020-03-04 02:50:32 +01:00
elem_type_sym := c.table.get_type_symbol(elem_type)
2020-08-22 12:29:15 +02:00
c.error('expected array element with type `$elem_type_sym.source_name`',
array_init.pos)
2020-03-04 02:50:32 +01:00
}
2020-01-19 13:52:34 +01:00
}
2020-04-29 12:20:22 +02:00
if expecting_interface_array {
array_init.interface_types = interface_types
}
2020-04-25 18:26:38 +02:00
if array_init.is_fixed {
2020-06-24 14:44:06 +02:00
idx := c.table.find_or_register_array_fixed(elem_type, array_init.exprs.len,
1)
2020-04-25 18:26:38 +02:00
array_init.typ = table.new_type(idx)
} else {
sym := c.table.get_type_symbol(elem_type)
idx := c.table.find_or_register_array(elem_type, 1, sym.mod)
2020-04-25 18:26:38 +02:00
array_init.typ = table.new_type(idx)
}
array_init.elem_type = elem_type
} else if array_init.is_fixed && array_init.exprs.len == 1 && array_init.elem_type != table.void_type {
// [50]byte
2020-03-04 02:50:32 +01:00
mut fixed_size := 1
match array_init.exprs[0] as init_expr {
2020-03-04 02:50:32 +01:00
ast.IntegerLiteral {
fixed_size = init_expr.val.int()
2020-03-04 02:50:32 +01:00
}
2020-04-08 19:15:16 +02:00
ast.Ident {
// if obj := c.file.global_scope.find_const(init_expr.name) {
// if obj := scope.find(init_expr.name) {
2020-04-11 04:09:41 +02:00
// scope := c.file.scope.innermost(array_init.pos.pos)
// eprintln('scope: ${scope.str()}')
// scope.find(init_expr.name) or {
// c.error('undefined ident: `$init_expr.name`', array_init.pos)
2020-04-11 04:09:41 +02:00
// }
mut full_const_name := init_expr.mod + '.' + init_expr.name
2020-04-11 04:09:41 +02:00
if obj := c.file.global_scope.find_const(full_const_name) {
2020-04-11 21:31:54 +02:00
if cint := const_int_value(obj) {
fixed_size = cint
}
2020-04-08 19:15:16 +02:00
} else {
c.error('non existent integer const $full_const_name while initializing the size of a static array',
2020-04-11 04:09:41 +02:00
array_init.pos)
2020-04-08 19:15:16 +02:00
}
2020-04-11 04:09:41 +02:00
}
else {
2020-03-04 02:50:32 +01:00
c.error('expecting `int` for fixed size', array_init.pos)
}
}
2020-06-24 14:44:06 +02:00
idx := c.table.find_or_register_array_fixed(array_init.elem_type, fixed_size,
1)
2020-02-19 14:34:44 +01:00
array_type := table.new_type(idx)
array_init.typ = array_type
}
return array_init.typ
2020-01-19 13:52:34 +01:00
}
fn const_int_value(cfield ast.ConstField) ?int {
if cint := is_const_integer(cfield) {
return cint.val.int()
}
2020-04-11 21:31:54 +02:00
return none
}
fn is_const_integer(cfield ast.ConstField) ?ast.IntegerLiteral {
match cfield.expr {
ast.IntegerLiteral { return *it }
else {}
}
return none
}
2020-04-25 17:49:16 +02:00
fn (mut c Checker) stmt(node ast.Stmt) {
$if trace_checker ? {
stmt_pos := node.position()
eprintln('checking file: ${c.file.path:-30} | stmt pos: ${stmt_pos.str():-45} | stmt')
}
2020-02-27 00:12:37 +01:00
// c.expected_type = table.void_type
2020-02-03 10:27:06 +01:00
match mut node {
2020-03-26 10:27:46 +01:00
ast.AssertStmt {
cur_exp_typ := c.expected_type
2020-06-18 20:38:59 +02:00
assert_type := c.expr(node.expr)
if assert_type != table.bool_type_idx {
atype_name := c.table.get_type_symbol(assert_type).name
2020-06-18 20:38:59 +02:00
c.error('assert can be used only with `bool` expressions, but found `$atype_name` instead',
node.pos)
}
c.expected_type = cur_exp_typ
2020-03-26 10:27:46 +01:00
}
2020-02-06 17:38:02 +01:00
ast.AssignStmt {
2020-06-18 20:38:59 +02:00
c.assign_stmt(mut node)
2020-02-06 17:38:02 +01:00
}
2020-03-25 14:24:48 +01:00
ast.Block {
if node.is_unsafe {
assert !c.inside_unsafe
c.inside_unsafe = true
c.stmts(node.stmts)
c.inside_unsafe = false
} else {
c.stmts(node.stmts)
}
2020-03-25 14:24:48 +01:00
}
ast.BranchStmt {
if c.in_for_count == 0 {
2020-06-18 20:38:59 +02:00
c.error('$node.tok.lit statement not within a loop', node.tok.position())
}
}
ast.CompFor {
// node.typ = c.expr(node.expr)
c.stmts(node.stmts)
}
ast.ConstDecl {
mut field_names := []string{}
mut field_order := []int{}
2020-06-18 20:38:59 +02:00
for i, field in node.fields {
2020-05-16 16:12:23 +02:00
// TODO Check const name once the syntax is decided
2020-04-30 12:17:31 +02:00
if field.name in c.const_names {
2020-07-14 17:24:06 +02:00
c.error('duplicate const `$field.name`', field.pos)
2020-04-30 12:17:31 +02:00
}
c.const_names << field.name
field_names << field.name
field_order << i
}
mut needs_order := false
mut done_fields := []int{}
2020-06-18 20:38:59 +02:00
for i, field in node.fields {
2020-04-04 08:05:26 +02:00
c.const_decl = field.name
c.const_deps << field.name
typ := c.expr(field.expr)
2020-06-18 20:38:59 +02:00
node.fields[i].typ = c.table.mktyp(typ)
2020-04-04 08:05:26 +02:00
for cd in c.const_deps {
2020-06-18 20:38:59 +02:00
for j, f in node.fields {
if j != i && cd in field_names && cd == f.name && j !in done_fields {
2020-04-04 08:05:26 +02:00
needs_order = true
x := field_order[j]
field_order[j] = field_order[i]
field_order[i] = x
break
}
}
}
2020-04-04 08:05:26 +02:00
done_fields << i
c.const_deps = []
}
if needs_order {
mut ordered_fields := []ast.ConstField{}
for order in field_order {
2020-06-18 20:38:59 +02:00
ordered_fields << node.fields[order]
}
2020-06-18 20:38:59 +02:00
node.fields = ordered_fields
}
}
2020-04-09 19:23:49 +02:00
ast.DeferStmt {
2020-06-18 20:38:59 +02:00
c.stmts(node.stmts)
2020-04-09 19:23:49 +02:00
}
ast.EnumDecl {
2020-06-18 20:38:59 +02:00
c.enum_decl(node)
2020-04-09 19:23:49 +02:00
}
ast.ExprStmt {
2020-06-18 20:38:59 +02:00
node.typ = c.expr(node.expr)
2020-03-19 15:18:29 +01:00
c.expected_type = table.void_type
2020-06-18 20:38:59 +02:00
c.check_expr_opt_call(node.expr, table.void_type)
// TODO This should work, even if it's prolly useless .-.
// node.typ = c.check_expr_opt_call(node.expr, table.void_type)
}
ast.FnDecl {
c.fn_decl(mut node)
}
ast.ForCStmt {
c.in_for_count++
2020-06-18 20:38:59 +02:00
c.stmt(node.init)
c.expr(node.cond)
c.stmt(node.inc)
c.stmts(node.stmts)
c.in_for_count--
}
ast.ForInStmt {
c.in_for_count++
2020-06-18 20:38:59 +02:00
typ := c.expr(node.cond)
2020-04-25 09:08:53 +02:00
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)
}
2020-06-18 20:38:59 +02:00
if node.is_range {
high_type := c.expr(node.high)
high_type_idx := high_type.idx()
2020-04-25 06:14:17 +02:00
if typ_idx in table.integer_type_idxs && high_type_idx !in table.integer_type_idxs {
2020-06-18 20:38:59 +02:00
c.error('range types do not match', node.cond.position())
2020-04-25 06:14:17 +02:00
} else if typ_idx in table.float_type_idxs || high_type_idx in table.float_type_idxs {
2020-06-18 20:38:59 +02:00
c.error('range type can not be float', node.cond.position())
2020-04-25 06:14:17 +02:00
} else if typ_idx == table.bool_type_idx || high_type_idx == table.bool_type_idx {
2020-06-18 20:38:59 +02:00
c.error('range type can not be bool', node.cond.position())
2020-04-25 06:14:17 +02:00
} else if typ_idx == table.string_type_idx || high_type_idx == table.string_type_idx {
2020-06-18 20:38:59 +02:00
c.error('range type can not be string', node.cond.position())
}
} else {
2020-06-18 20:38:59 +02:00
mut scope := c.file.scope.innermost(node.pos.pos)
2020-03-21 10:22:16 +01:00
sym := c.table.get_type_symbol(typ)
2020-06-18 20:38:59 +02:00
if sym.kind == .map && !(node.key_var.len > 0 && node.val_var.len > 0) {
2020-05-21 03:58:50 +02:00
c.error('declare a key and a value variable when ranging a map: `for key, val in map {`\n' +
2020-06-18 20:38:59 +02:00
'use `_` if you do not need the variable', node.pos)
}
2020-06-18 20:38:59 +02:00
if node.key_var.len > 0 {
key_type := match sym.kind {
2020-04-25 17:49:16 +02:00
.map { sym.map_info().key_type }
else { table.int_type }
}
node.key_type = key_type
scope.update_var_type(node.key_var, key_type)
}
value_type := c.table.value_type(typ)
2020-06-05 09:44:25 +02:00
if value_type == table.void_type || typ.has_flag(.optional) {
if typ != table.void_type {
2020-06-24 14:44:06 +02:00
c.error('for in: cannot index `${c.table.type_to_str(typ)}`',
node.cond.position())
}
}
node.cond_type = typ
node.kind = sym.kind
node.val_type = value_type
scope.update_var_type(node.val_var, value_type)
}
c.stmts(node.stmts)
c.in_for_count--
}
ast.ForStmt {
c.in_for_count++
typ := c.expr(node.cond)
2020-08-09 03:57:54 +02:00
if !node.is_inf && typ.idx() != table.bool_type_idx && !c.pref.translated {
c.error('non-bool used as for condition', node.pos)
}
// TODO: update loop var type
// how does this work currenly?
c.stmts(node.stmts)
c.in_for_count--
}
2020-05-16 16:12:23 +02:00
ast.GlobalDecl {
c.check_valid_snake_case(node.name, 'global name', node.pos)
2020-05-16 16:12:23 +02:00
}
ast.GoStmt {
2020-07-08 15:46:58 +02:00
if node.call_expr !is ast.CallExpr {
c.error('expression in `go` must be a function call', node.call_expr.position())
2020-04-16 12:17:15 +02:00
}
c.expr(node.call_expr)
if node.call_expr is ast.CallExpr {
call_expr := node.call_expr as ast.CallExpr
// Make sure there are no mutable arguments
for arg in call_expr.args {
if arg.is_mut && !arg.typ.is_ptr() {
2020-06-27 16:41:29 +02:00
c.error('function in `go` statement cannot contain mutable non-reference arguments',
arg.expr.position())
}
}
if call_expr.is_method && call_expr.receiver_type.is_ptr() && !call_expr.left_type.is_ptr() {
2020-06-27 16:41:29 +02:00
c.error('method in `go` statement cannot have non-reference mutable receiver',
call_expr.left.position())
}
}
2020-04-03 15:18:17 +02:00
}
ast.GotoLabel {}
ast.GotoStmt {}
ast.HashStmt {
c.hash_stmt(node)
}
ast.Import {
c.import_stmt(node)
}
ast.InterfaceDecl {
c.interface_decl(node)
}
ast.Module {
c.mod = node.name
2020-09-05 12:00:35 +02:00
c.is_builtin_mod = node.name in ['builtin', 'os', 'strconv']
c.check_valid_snake_case(node.name, 'module name', node.pos)
}
ast.Return {
// c.returns = true
c.return_stmt(mut node)
2020-04-29 12:31:18 +02:00
c.scope_returns = true
}
2020-06-24 14:32:14 +02:00
ast.SqlStmt {
c.sql_stmt(node)
}
ast.StructDecl {
c.struct_decl(node)
}
ast.TypeDecl {
c.type_decl(node)
}
}
}
fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
if c.skip_flags {
return
}
if c.pref.backend == .js {
if !c.file.path.ends_with('.js.v') {
c.error('Hash statements are only allowed in backend specific files such "x.js.v"',
node.pos)
}
if c.mod == 'main' {
c.error('Hash statements are not allowed in the main module. Please place them in a separate module.',
node.pos)
}
} else if node.val.starts_with('include') {
mut flag := node.val[8..]
if flag.contains('@VROOT') {
vroot := util.resolve_vroot(flag, c.file.path) or {
c.error(err, node.pos)
return
}
node.val = 'include $vroot'
}
} else if node.val.starts_with('flag') {
// #flag linux -lm
mut flag := node.val[5..]
// expand `@VROOT` to its absolute path
if flag.contains('@VROOT') {
flag = util.resolve_vroot(flag, c.file.path) or {
c.error(err, node.pos)
return
}
}
for deprecated in ['@VMOD', '@VMODULE', '@VPATH', '@VLIB_PATH'] {
if flag.contains(deprecated) {
c.error('$deprecated had been deprecated, use @VROOT instead.', node.pos)
}
}
// println('adding flag "$flag"')
c.table.parse_cflag(flag, c.mod, c.pref.compile_defines_all) or {
c.error(err, node.pos)
}
}
}
fn (mut c Checker) import_stmt(imp ast.Import) {
for sym in imp.syms {
name := '$imp.mod\.$sym.name'
if sym.kind == .fn_ {
c.table.find_fn(name) or {
c.error('module `$imp.mod` has no public fn named `$sym.name\()`', sym.pos)
}
}
if sym.kind == .type_ {
if type_sym := c.table.find_type(name) {
if type_sym.kind == .placeholder {
c.error('module `$imp.mod` has no public type `$sym.name\{}`', sym.pos)
}
} else {
c.error('module `$imp.mod` has no public type `$sym.name\{}`', sym.pos)
}
}
}
}
2020-04-25 17:49:16 +02:00
fn (mut c Checker) stmts(stmts []ast.Stmt) {
2020-04-30 18:06:14 +02:00
mut unreachable := token.Position{
line_nr: -1
}
2020-03-18 12:18:48 +01:00
c.expected_type = table.void_type
2020-02-29 18:34:25 +01:00
for stmt in stmts {
2020-04-29 12:31:18 +02:00
if c.scope_returns {
if unreachable.line_nr == -1 {
unreachable = stmt.position()
}
}
2020-02-29 18:34:25 +01:00
c.stmt(stmt)
}
2020-04-29 12:31:18 +02:00
if unreachable.line_nr >= 0 {
c.error('unreachable code', unreachable)
2020-04-29 12:31:18 +02:00
}
c.scope_returns = false
2020-03-18 12:34:27 +01:00
c.expected_type = table.void_type
2020-02-29 18:34:25 +01:00
}
[inline]
pub fn (c &Checker) unwrap_generic(typ table.Type) table.Type {
if typ.has_flag(.generic) {
// return c.cur_generic_type
return c.cur_generic_type.derive(typ).clear_flag(.generic)
}
return typ
}
// TODO node must be mut
2020-04-25 17:49:16 +02:00
pub fn (mut c Checker) expr(node ast.Expr) table.Type {
c.expr_level++
2020-06-10 12:17:49 +02:00
defer {
c.expr_level--
}
if c.expr_level > 200 {
c.error('checker: too many expr levels: $c.expr_level ', node.position())
return table.void_type
}
match mut node {
2020-05-15 23:14:53 +02:00
ast.AnonFn {
2020-05-23 08:51:15 +02:00
keep_fn := c.cur_fn
2020-06-18 20:38:59 +02:00
c.cur_fn = &node.decl
c.stmts(node.decl.stmts)
2020-05-23 08:51:15 +02:00
c.cur_fn = keep_fn
return node.typ
2020-05-15 23:14:53 +02:00
}
ast.ArrayInit {
2020-06-18 20:38:59 +02:00
return c.array_init(mut node)
}
ast.AsCast {
2020-06-18 20:38:59 +02:00
node.expr_type = c.expr(node.expr)
expr_type_sym := c.table.get_type_symbol(node.expr_type)
type_sym := c.table.get_type_symbol(node.typ)
if expr_type_sym.kind == .sum_type {
if type_sym.kind == .placeholder {
// Unknown type used in the right part of `as`
2020-08-22 12:29:15 +02:00
c.error('unknown type `$type_sym.source_name`', node.pos)
}
if !c.table.sumtype_has_variant(node.expr_type, node.typ) {
2020-08-22 12:29:15 +02:00
c.error('cannot cast `$expr_type_sym.source_name` to `$type_sym.source_name`',
node.pos)
// c.error('only $info.variants can be casted to `$typ`', node.pos)
}
} else {
//
2020-08-22 12:29:15 +02:00
c.error('cannot cast non sum type `$type_sym.source_name` using `as`',
node.pos)
}
2020-06-18 20:38:59 +02:00
return node.typ.to_ptr()
// return node.typ
}
2020-02-29 18:07:29 +01:00
ast.Assoc {
2020-06-18 20:38:59 +02:00
scope := c.file.scope.innermost(node.pos.pos)
v := scope.find_var(node.var_name) or {
2020-02-29 18:07:29 +01:00
panic(err)
}
2020-06-18 20:38:59 +02:00
for i, _ in node.fields {
c.expr(node.exprs[i])
2020-03-19 08:57:33 +01:00
}
2020-06-18 20:38:59 +02:00
node.typ = v.typ
2020-04-15 01:45:27 +02:00
return v.typ
2020-02-29 18:07:29 +01:00
}
ast.BoolLiteral {
return table.bool_type
}
ast.CastExpr {
2020-06-18 20:38:59 +02:00
node.expr_type = c.expr(node.expr)
from_type_sym := c.table.get_type_symbol(node.expr_type)
to_type_sym := c.table.get_type_symbol(node.typ)
expr_is_ptr := node.expr_type.is_ptr() || node.expr_type.idx() in table.pointer_type_idxs
if expr_is_ptr && to_type_sym.kind == .string && !node.in_prexpr {
if node.has_arg {
c.warn('to convert a C string buffer pointer to a V string, please use x.vstring_with_len(len) instead of string(x,len)',
node.pos)
} else {
c.warn('to convert a C string buffer pointer to a V string, please use x.vstring() instead of string(x)',
node.pos)
}
}
if node.expr_type == table.byte_type && to_type_sym.kind == .string {
c.error('can not cast type `byte` to string, use `${node.expr.str()}.str()` instead.',
node.pos)
}
if to_type_sym.kind == .sum_type {
if node.expr_type in [table.any_int_type, table.any_flt_type] {
2020-07-07 14:37:43 +02:00
node.expr_type = c.promote_num(node.expr_type, if node.expr_type == table.any_int_type { table.int_type } else { table.f64_type })
}
if !c.table.sumtype_has_variant(node.typ, node.expr_type) {
2020-08-22 12:29:15 +02:00
c.error('cannot cast `$from_type_sym.source_name` to `$to_type_sym.source_name`',
2020-07-07 14:37:43 +02:00
node.pos)
}
2020-08-22 12:29:15 +02:00
} else if node.typ == table.string_type &&
(from_type_sym.kind in [.any_int, .int, .byte, .byteptr] ||
(from_type_sym.kind == .array && from_type_sym.name == 'array_byte')) {
2020-06-18 20:38:59 +02:00
type_name := c.table.type_to_str(node.expr_type)
2020-06-24 14:44:06 +02:00
c.error('cannot cast type `$type_name` to string, use `x.str()` instead',
node.pos)
2020-07-07 14:37:43 +02:00
} else if node.expr_type == table.string_type {
if to_type_sym.kind != .alias {
mut error_msg := 'cannot cast a string'
if node.expr is ast.StringLiteral {
str_lit := node.expr as ast.StringLiteral
if str_lit.val.len == 1 {
error_msg += ", for denoting characters use `$str_lit.val` instead of '$str_lit.val'"
}
2020-05-16 22:52:41 +02:00
}
c.error(error_msg, node.pos)
2020-05-16 22:52:41 +02:00
}
2020-08-01 23:41:32 +02:00
} else if !node.expr_type.is_int() && node.expr_type != table.voidptr_type && !node.expr_type.is_ptr() &&
to_type_sym.kind == .byte {
type_name := c.table.type_to_str(node.expr_type)
c.error('cannot cast type `$type_name` to `byte`', node.pos)
2020-08-14 14:57:08 +02:00
} else if to_type_sym.kind == .struct_ && !node.typ.is_ptr() && !(to_type_sym.info as table.Struct).is_typedef {
// For now we ignore C typedef because of `C.Window(C.None)` in vlib/clipboard
if from_type_sym.kind == .struct_ && !node.expr_type.is_ptr() {
from_type_info := from_type_sym.info as table.Struct
to_type_info := to_type_sym.info as table.Struct
if !c.check_struct_signature(from_type_info, to_type_info) {
2020-08-22 12:29:15 +02:00
c.error('cannot convert struct `$from_type_sym.source_name` to struct `$to_type_sym.source_name`',
node.pos)
}
} else {
type_name := c.table.type_to_str(node.expr_type)
c.error('cannot cast `$type_name` to struct', node.pos)
2020-08-14 14:57:08 +02:00
}
} else if node.typ == table.bool_type {
c.error('cannot cast to bool - use e.g. `some_int != 0` instead', node.pos)
2020-05-16 22:48:41 +02:00
}
2020-06-18 20:38:59 +02:00
if node.has_arg {
c.expr(node.arg)
}
2020-06-18 20:38:59 +02:00
node.typname = c.table.get_type_symbol(node.typ).name
return node.typ
}
ast.CallExpr {
2020-06-18 20:38:59 +02:00
return c.call_expr(mut node)
}
ast.ChanInit {
return c.chan_init(mut node)
}
ast.CharLiteral {
2020-08-27 06:46:18 +02:00
// return any_int, not rune, so that we can do "bytes << `A`" without a cast etc
// return table.any_int_type
return table.rune_type
// return table.byte_type
}
ast.Comment {
return table.void_type
}
ast.ComptimeCall {
2020-06-18 20:38:59 +02:00
node.sym = c.table.get_type_symbol(c.unwrap_generic(c.expr(node.left)))
if node.is_vweb {
// TODO assoc parser bug
pref := *c.pref
2020-06-24 14:44:06 +02:00
pref2 := {
pref |
is_vweb: true
}
mut c2 := new_checker(c.table, pref2)
2020-06-18 20:38:59 +02:00
c2.check(node.vweb_tmpl)
2020-06-09 18:47:51 +02:00
c.warnings << c2.warnings
c.errors << c2.errors
c.nr_warnings += c2.nr_warnings
c.nr_errors += c2.nr_errors
}
2020-06-20 03:12:35 +02:00
return c.table.find_type_idx('vweb.Result')
// return table.void_type
}
2020-05-15 23:14:53 +02:00
ast.ConcatExpr {
2020-06-18 20:38:59 +02:00
return c.concat_expr(mut node)
2020-05-15 23:14:53 +02:00
}
2020-02-25 15:02:34 +01:00
ast.EnumVal {
2020-06-18 20:38:59 +02:00
return c.enum_val(mut node)
2020-02-25 15:02:34 +01:00
}
2020-02-17 14:15:42 +01:00
ast.FloatLiteral {
return table.any_flt_type
}
ast.Ident {
// c.checked_ident = node.name
2020-06-18 20:38:59 +02:00
res := c.ident(mut node)
2020-04-08 19:15:16 +02:00
// c.checked_ident = ''
return res
2020-02-18 18:13:34 +01:00
}
ast.IfExpr {
2020-06-18 20:38:59 +02:00
return c.if_expr(mut node)
}
ast.IfGuardExpr {
2020-06-18 20:38:59 +02:00
node.expr_type = c.expr(node.expr)
return table.bool_type
2020-02-18 18:13:34 +01:00
}
ast.IndexExpr {
2020-06-18 20:38:59 +02:00
return c.index_expr(mut node)
}
ast.InfixExpr {
2020-06-18 20:38:59 +02:00
return c.infix_expr(mut node)
}
ast.IntegerLiteral {
return table.any_int_type
}
ast.LockExpr {
return c.lock_expr(mut node)
}
ast.MapInit {
2020-06-18 20:38:59 +02:00
return c.map_init(mut node)
}
ast.MatchExpr {
2020-06-18 20:38:59 +02:00
return c.match_expr(mut node)
}
ast.PostfixExpr {
2020-06-18 20:38:59 +02:00
return c.postfix_expr(node)
}
ast.PrefixExpr {
2020-06-18 20:38:59 +02:00
right_type := c.expr(node.right)
node.right_type = right_type
2020-03-21 16:41:01 +01:00
// TODO: testing ref/deref strategy
2020-06-18 20:38:59 +02:00
if node.op == .amp && !right_type.is_ptr() {
2020-04-25 09:08:53 +02:00
return right_type.to_ptr()
}
if node.op == .mul {
if right_type.is_ptr() {
return right_type.deref()
}
if !right_type.is_pointer() {
s := c.table.type_to_str(right_type)
c.error('prefix operator `*` not defined for type `$s`', node.pos)
}
2020-03-21 15:27:10 +01:00
}
2020-08-09 03:57:54 +02:00
if node.op == .bit_not && !right_type.is_int() && !c.pref.translated {
2020-06-18 20:38:59 +02:00
c.error('operator ~ only defined on int types', node.pos)
}
2020-08-09 03:57:54 +02:00
if node.op == .not && right_type != table.bool_type_idx && !c.pref.translated {
2020-06-18 20:38:59 +02:00
c.error('! operator can only be used with bool types', node.pos)
2020-03-28 14:38:16 +01:00
}
if node.op == .arrow {
right := c.table.get_type_symbol(right_type)
if right.kind == .chan {
c.stmts(node.or_block.stmts)
return right.chan_info().elem_type
} else {
c.error('<- operator can only be used with `chan` types', node.pos)
}
}
2020-03-21 16:41:01 +01:00
return right_type
}
ast.None {
return table.none_type
}
2020-06-17 00:59:33 +02:00
ast.OrExpr {
// never happens
return table.void_type
}
// ast.OrExpr2 {
// return node.typ
// }
ast.ParExpr {
return c.expr(node.expr)
}
2020-06-17 00:59:33 +02:00
ast.RangeExpr {
// never happens
return table.void_type
}
ast.SelectExpr {
// TODO: to be implemented
return table.void_type
}
ast.SelectorExpr {
2020-06-18 20:38:59 +02:00
return c.selector_expr(mut node)
}
ast.SizeOf {
return table.u32_type
}
2020-06-17 00:59:33 +02:00
ast.SqlExpr {
2020-06-18 20:38:59 +02:00
return c.sql_expr(node)
2020-06-17 00:59:33 +02:00
}
ast.StringLiteral {
2020-06-18 20:38:59 +02:00
if node.language == .c {
2020-04-01 17:14:17 +02:00
return table.byteptr_type
}
return table.string_type
}
2020-03-21 07:01:06 +01:00
ast.StringInterLiteral {
2020-06-18 20:38:59 +02:00
return c.string_inter_lit(mut node)
2020-03-21 07:01:06 +01:00
}
ast.StructInit {
2020-06-18 20:38:59 +02:00
return c.struct_init(mut node)
2020-02-29 05:36:39 +01:00
}
ast.Type {
2020-06-18 20:38:59 +02:00
return node.typ
2020-03-02 10:53:38 +01:00
}
2020-03-19 12:15:39 +01:00
ast.TypeOf {
2020-06-18 20:38:59 +02:00
node.expr_type = c.expr(node.expr)
2020-03-19 12:15:39 +01:00
return table.string_type
}
2020-07-12 12:58:33 +02:00
ast.UnsafeExpr {
return c.unsafe_expr(mut node)
}
ast.Likely {
2020-06-18 20:38:59 +02:00
ltype := c.expr(node.expr)
if !c.check_types(ltype, table.bool_type) {
ltype_sym := c.table.get_type_symbol(ltype)
2020-06-18 20:38:59 +02:00
lname := if node.is_likely { '_likely_' } else { '_unlikely_' }
2020-08-22 12:29:15 +02:00
c.error('`${lname}()` expects a boolean expression, instead it got `$ltype_sym.source_name`',
2020-06-18 20:38:59 +02:00
node.pos)
}
return table.bool_type
}
}
return table.void_type
}
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) ident(mut ident ast.Ident) table.Type {
// TODO: move this
if c.const_deps.len > 0 {
mut name := ident.name
if !name.contains('.') && ident.mod != 'builtin' {
name = '${ident.mod}.$ident.name'
}
2020-04-04 08:05:26 +02:00
if name == c.const_decl {
c.error('cycle in constant `$c.const_decl`', ident.pos)
return table.void_type
}
c.const_deps << name
}
if ident.kind == .blank_ident {
return table.void_type
}
// second use
if ident.kind in [.constant, .global, .variable] {
info := ident.info as ast.IdentVar
2020-05-28 05:50:57 +02:00
// if info.typ == table.t_type {
// Got a var with type T, return current generic type
// return c.cur_generic_type
// }
return info.typ
} else if ident.kind == .function {
info := ident.info as ast.IdentFn
return info.typ
} else if ident.kind == .unresolved {
// first use
2020-08-28 19:07:32 +02:00
if ident.tok_kind == .assign && ident.is_mut {
c.error('`mut` not allowed with `=` (use `:=` to declare a variable)', ident.pos)
}
start_scope := c.file.scope.innermost(ident.pos.pos)
if obj1 := start_scope.find(ident.name) {
match mut obj1 as obj {
2020-06-09 09:08:11 +02:00
ast.GlobalDecl {
ident.kind = .global
ident.info = ast.IdentVar{
2020-06-18 20:38:59 +02:00
typ: obj.typ
2020-06-09 09:08:11 +02:00
}
ident.obj = obj1
2020-06-18 20:38:59 +02:00
return obj.typ
2020-06-09 09:08:11 +02:00
}
ast.Var {
// incase var was not marked as used yet (vweb tmpl)
obj.is_used = true
if ident.pos.pos < obj.pos.pos {
2020-06-24 14:44:06 +02:00
c.error('undefined variable `$ident.name` (used before declaration)',
ident.pos)
}
2020-06-18 20:38:59 +02:00
mut typ := obj.typ
if typ == 0 {
2020-06-18 20:38:59 +02:00
if obj.expr is ast.Ident {
inner_ident := obj.expr as ast.Ident
if inner_ident.kind == .unresolved {
c.error('unresolved variable: `$ident.name`', ident.pos)
return table.void_type
}
}
2020-06-18 20:38:59 +02:00
typ = c.expr(obj.expr)
}
2020-06-04 14:38:54 +02:00
is_optional := typ.has_flag(.optional)
ident.kind = .variable
ident.info = ast.IdentVar{
typ: typ
is_optional: is_optional
}
// if typ == table.t_type {
// sym := c.table.get_type_symbol(c.cur_generic_type)
2020-08-22 12:29:15 +02:00
// println('IDENT T unresolved $ident.name typ=$sym.source_name')
// Got a var with type T, return current generic type
// typ = c.cur_generic_type
// }
2020-05-24 04:43:00 +02:00
// } else {
2020-06-18 20:38:59 +02:00
obj.typ = typ
ident.obj = obj1
// unwrap optional (`println(x)`)
if is_optional {
2020-06-04 14:38:54 +02:00
return typ.clear_flag(.optional)
}
return typ
}
else {}
}
}
// prepend mod to look for fn call or const
mut name := ident.name
if !name.contains('.') && ident.mod != 'builtin' {
name = '${ident.mod}.$ident.name'
}
if obj1 := c.file.global_scope.find(name) {
match mut obj1 as obj {
ast.ConstField {
2020-06-18 20:38:59 +02:00
mut typ := obj.typ
if typ == 0 {
2020-06-18 20:38:59 +02:00
typ = c.expr(obj.expr)
}
ident.name = name
ident.kind = .constant
ident.info = ast.IdentVar{
typ: typ
}
2020-06-18 20:38:59 +02:00
obj.typ = typ
ident.obj = obj1
return typ
}
else {}
}
}
// Non-anon-function object (not a call), e.g. `onclick(my_click)`
if func := c.table.find_fn(name) {
2020-06-24 14:44:06 +02:00
fn_type := table.new_type(c.table.find_or_register_fn_type(ident.mod, func,
false, true))
ident.name = name
ident.kind = .function
ident.info = ast.IdentFn{
typ: fn_type
}
return fn_type
}
}
if ident.language == .c {
2020-02-18 18:13:34 +01:00
return table.int_type
}
2020-05-01 12:22:39 +02:00
if ident.name != '_' {
if c.inside_sql {
if field := c.table.struct_find_field(c.cur_orm_ts, ident.name) {
return field.typ
}
}
if ident.kind == .unresolved && ident.mod != 'builtin' {
// search in the `builtin` idents, for example
// main.compare_f32 may actually be builtin.compare_f32
saved_mod := ident.mod
ident.mod = 'builtin'
builtin_type := c.ident(ident)
if builtin_type != table.void_type {
return builtin_type
}
ident.mod = saved_mod
}
if ident.tok_kind == .assign {
2020-08-28 19:07:32 +02:00
c.error('undefined ident: `$ident.name` (use `:=` to declare a variable)',
ident.pos)
} else {
c.error('undefined ident: `$ident.name`', ident.pos)
}
2020-05-01 12:22:39 +02:00
}
2020-05-01 12:02:31 +02:00
if c.table.known_type(ident.name) {
// e.g. `User` in `json.decode(User, '...')`
return table.void_type
}
return table.void_type
}
2020-05-16 16:12:23 +02:00
pub fn (mut c Checker) concat_expr(mut concat_expr ast.ConcatExpr) table.Type {
2020-05-15 23:14:53 +02:00
mut mr_types := []table.Type{}
for expr in concat_expr.vals {
mr_types << c.expr(expr)
}
if concat_expr.vals.len == 1 {
typ := mr_types[0]
concat_expr.return_type = typ
return typ
} else {
typ := c.table.find_or_register_multi_return(mr_types)
table.new_type(typ)
concat_expr.return_type = typ
return typ
}
}
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) table.Type {
2020-03-18 12:18:48 +01:00
node.is_expr = c.expected_type != table.void_type
node.expected_type = c.expected_type
2020-03-18 12:23:32 +01:00
cond_type := c.expr(node.cond)
2020-08-17 20:19:21 +02:00
// we setting this here rather than at the end of the method
// since it is used in c.match_exprs() it saves checking twice
node.cond_type = cond_type
2020-03-18 12:23:32 +01:00
if cond_type == 0 {
c.error('match 0 cond type', node.pos)
}
2020-05-31 10:22:18 +02:00
cond_type_sym := c.table.get_type_symbol(cond_type)
if cond_type_sym.kind !in [.sum_type, .interface_] {
node.is_sum_type = false
}
2020-05-31 10:22:18 +02:00
c.match_exprs(mut node, cond_type_sym)
2020-03-18 12:23:32 +01:00
c.expected_type = cond_type
2020-03-03 17:29:16 +01:00
mut ret_type := table.void_type
mut require_return := false
mut branch_without_return := false
2020-03-04 11:59:45 +01:00
for branch in node.branches {
c.stmts(branch.stmts)
// If the last statement is an expression, return its type
2020-03-04 11:59:45 +01:00
if branch.stmts.len > 0 {
match mut branch.stmts[branch.stmts.len - 1] as stmt {
2020-03-03 17:29:16 +01:00
ast.ExprStmt {
ret_type = c.expr(stmt.expr)
stmt.typ = ret_type
2020-03-03 17:29:16 +01:00
}
else {
// TODO: ask alex about this
// typ := c.expr(stmt.expr)
// type_sym := c.table.get_type_symbol(typ)
2020-08-22 12:29:15 +02:00
// p.warn('match expr ret $type_sym.source_name')
// node.typ = typ
// return typ
}
}
}
if has_return := c.has_return(branch.stmts) {
if has_return {
require_return = true
} else {
branch_without_return = true
}
}
}
if require_return && branch_without_return {
c.returns = false
} else {
// if inner if branch has not covered all branches but this one
c.returns = true
}
// if ret_type != table.void_type {
// node.is_expr = c.expected_type != table.void_type
// node.expected_type = c.expected_type
// }
node.return_type = ret_type
// println('!m $expr_type')
2020-03-03 17:29:16 +01:00
return ret_type
}
2020-05-11 14:38:25 +02:00
fn (mut c Checker) match_exprs(mut node ast.MatchExpr, type_sym table.TypeSymbol) {
// branch_exprs is a histogram of how many times
// an expr was used in the match
2020-05-04 21:03:18 +02:00
mut branch_exprs := map[string]int{}
cond_type_sym := c.table.get_type_symbol(node.cond_type)
for branch in node.branches {
for expr in branch.exprs {
mut key := ''
if expr is ast.RangeExpr {
mut low := 0
mut high := 0
c.expected_type = node.expected_type
2020-07-16 19:40:14 +02:00
low_expr := expr.low
high_expr := expr.high
if low_expr is ast.IntegerLiteral {
if high_expr is ast.IntegerLiteral {
low = low_expr.val.int()
high = high_expr.val.int()
} else {
c.error('mismatched range types', low_expr.pos)
}
2020-07-16 19:40:14 +02:00
} else if low_expr is ast.CharLiteral {
if high_expr is ast.CharLiteral {
low = low_expr.val[0]
high = high_expr.val[0]
} else {
c.error('mismatched range types', low_expr.pos)
}
} else {
typ := c.table.type_to_str(c.expr(expr.low))
c.error('cannot use type `$typ` in match range', branch.pos)
}
for i in low .. high + 1 {
key = i.str()
val := if key in branch_exprs { branch_exprs[key] } else { 0 }
if val == 1 {
c.error('match case `$key` is handled more than once', branch.pos)
}
branch_exprs[key] = val + 1
}
continue
}
match expr {
2020-06-18 20:38:59 +02:00
ast.Type { key = c.table.type_to_str(expr.typ) }
ast.EnumVal { key = expr.val }
2020-04-25 17:49:16 +02:00
else { key = expr.str() }
}
2020-06-25 17:12:32 +02:00
val := if key in branch_exprs { branch_exprs[key] } else { 0 }
if val == 1 {
c.error('match case `$key` is handled more than once', branch.pos)
}
2020-08-17 20:19:21 +02:00
c.expected_type = node.cond_type
expr_type := c.expr(expr)
if cond_type_sym.kind == .interface_ {
c.type_implements(expr_type, c.expected_type, branch.pos)
} else if !c.check_types(expr_type, c.expected_type) {
2020-08-17 20:19:21 +02:00
expr_str := c.table.type_to_str(expr_type)
expect_str := c.table.type_to_str(c.expected_type)
c.error('cannot use type `$expect_str` as type `$expr_str`', node.pos)
}
branch_exprs[key] = val + 1
}
}
// check that expressions are exhaustive
// this is achieved either by putting an else
// or, when the match is on a sum type or an enum
// by listing all variants or values
2020-04-25 21:51:44 +02:00
mut is_exhaustive := true
mut unhandled := []string{}
match type_sym.info as info {
table.SumType { for v in info.variants {
2020-04-25 21:51:44 +02:00
v_str := c.table.type_to_str(v)
if v_str !in branch_exprs {
is_exhaustive = false
2020-06-24 14:56:44 +02:00
unhandled << '`$v_str`'
}
} }
//
table.Enum { for v in info.vals {
2020-04-25 21:51:44 +02:00
if v !in branch_exprs {
is_exhaustive = false
2020-06-24 14:56:44 +02:00
unhandled << '`.$v`'
}
} }
else { is_exhaustive = false }
2020-04-25 21:51:44 +02:00
}
mut else_branch := node.branches[node.branches.len - 1]
mut has_else := else_branch.is_else
if !has_else {
for i, branch in node.branches {
if branch.is_else && i != node.branches.len - 1 {
c.error('`else` must be the last branch of `match`', branch.pos)
else_branch = branch
has_else = true
}
}
}
2020-04-25 21:51:44 +02:00
if is_exhaustive {
if has_else {
c.error('match expression is exhaustive, `else` is unnecessary', else_branch.pos)
}
return
}
if has_else {
return
}
mut err_details := 'match must be exhaustive'
if unhandled.len > 0 {
err_details += ' (add match branches for: '
if unhandled.len < match_exhaustive_cutoff_limit {
err_details += unhandled.join(', ')
} else {
remaining := unhandled.len - match_exhaustive_cutoff_limit
err_details += unhandled[0..match_exhaustive_cutoff_limit].join(', ')
err_details += ', and $remaining others ...'
}
err_details += ' or `else {}` at the end)'
2020-04-25 21:51:44 +02:00
} else {
err_details += ' (add `else {}` at the end)'
}
c.error(err_details, node.pos)
}
pub fn (mut c Checker) lock_expr(mut node ast.LockExpr) table.Type {
for id in node.lockeds {
c.ident(mut id)
if id.obj is ast.Var as v {
if v.typ.share() != .shared_t {
c.error('`$id.name` must be declared `shared` to be locked', id.pos)
}
} else {
c.error('`$id.name` is not a variable and cannot be locked', id.pos)
}
if id.name in c.locked_names {
c.error('`$id.name` is already locked', id.pos)
} else if id.name in c.rlocked_names {
c.error('`$id.name` is already read-locked', id.pos)
}
if node.is_rlock {
c.rlocked_names << id.name
} else {
c.locked_names << id.name
}
}
c.stmts(node.stmts)
if node.is_rlock {
c.rlocked_names = c.rlocked_names[..c.rlocked_names.len - node.lockeds.len]
} else {
c.locked_names = c.locked_names[..c.locked_names.len - node.lockeds.len]
}
// void for now... maybe sometime `x := lock a { a.getval() }`
return table.void_type
}
2020-07-12 12:58:33 +02:00
pub fn (mut c Checker) unsafe_expr(mut node ast.UnsafeExpr) table.Type {
slen := node.stmts.len
if slen > 1 {
c.error('FIXME: unsafe expression block should support multiple statements', node.pos)
2020-07-12 12:58:33 +02:00
return table.none_type
}
if slen == 0 {
c.error('unsafe expression does not yield an expression', node.pos)
return table.none_type
}
assert !c.inside_unsafe
c.inside_unsafe = true
defer {
c.inside_unsafe = false
}
if slen > 1 {
c.stmts(node.stmts[0..slen - 1])
}
last := node.stmts[0]
if last is ast.ExprStmt {
t := c.expr(last.expr)
return t
}
c.error('unsafe expression does not yield an expression', node.pos)
return table.none_type
}
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type {
is_ct := node.is_comptime
if_kind := if is_ct { '\$if' } else { 'if' }
expr_required := c.expected_type != table.void_type
former_expected_type := c.expected_type
node.typ = table.void_type
mut require_return := false
mut branch_without_return := false
mut should_skip := false // Whether the current branch should be skipped
mut found_branch := false // Whether a matching branch was found- skip the rest
for i in 0 .. node.branches.len {
mut branch := node.branches[i]
if branch.cond is ast.ParExpr {
c.error('unnecessary `()` in `$if_kind` condition, use `$if_kind expr {` instead of `$if_kind (expr) {`.',
2020-04-25 17:49:16 +02:00
branch.pos)
}
if !node.has_else || i < node.branches.len - 1 {
if is_ct {
should_skip = c.comp_if_branch(branch.cond, branch.pos)
} else {
// check condition type is boolean
cond_typ := c.expr(branch.cond)
if cond_typ.idx() !in [table.bool_type_idx, table.void_type_idx] && !c.pref.translated {
// void types are skipped, because they mean the var was initialized incorrectly
// (via missing function etc)
typ_sym := c.table.get_type_symbol(cond_typ)
c.error('non-bool type `$typ_sym.source_name` used as if condition',
branch.pos)
}
}
}
2020-08-24 11:10:11 +02:00
// smartcast sumtypes and interfaces when using `is`
if !is_ct && branch.cond is ast.InfixExpr {
2020-07-08 15:17:28 +02:00
infix := branch.cond as ast.InfixExpr
if infix.op == .key_is {
2020-07-08 15:17:28 +02:00
right_expr := infix.right as ast.Type
left_sym := c.table.get_type_symbol(infix.left_type)
expr_type := c.expr(infix.left)
if left_sym.kind == .interface_ {
c.type_implements(right_expr.typ, expr_type, branch.pos)
} else if !c.check_types(expr_type, right_expr.typ) {
expect_str := c.table.type_to_str(right_expr.typ)
expr_str := c.table.type_to_str(expr_type)
c.error('cannot use type `$expect_str` as type `$expr_str`', branch.pos)
}
if (infix.left is ast.Ident ||
infix.left is ast.SelectorExpr) &&
infix.right is ast.Type {
is_variable := if infix.left is ast.Ident { (infix.left as ast.Ident).kind ==
.variable } else { true }
// Register shadow variable or `as` variable with actual type
if is_variable {
if left_sym.kind in [.sum_type, .interface_] && branch.left_as_name.len > 0 {
mut is_mut := false
mut scope := c.file.scope.innermost(branch.body_pos.pos)
if infix.left is ast.Ident as infix_left {
if var := scope.find_var(infix_left.name) {
is_mut = var.is_mut
}
} else if infix.left is ast.SelectorExpr {
selector := infix.left as ast.SelectorExpr
field := c.table.struct_find_field(left_sym, selector.field_name) or {
table.Field{}
}
is_mut = field.is_mut
}
scope.register(branch.left_as_name, ast.Var{
name: branch.left_as_name
typ: right_expr.typ.to_ptr()
pos: infix.left.position()
is_used: true
is_mut: is_mut
})
node.branches[i].smartcast = true
}
2020-07-08 15:17:28 +02:00
}
}
}
}
if is_ct { // Skip checking if needed
cur_skip_flags := c.skip_flags
if found_branch {
c.skip_flags = true
} else if should_skip {
c.skip_flags = true
should_skip = false // Reset the value of `should_skip` for the next branch
} else {
found_branch = true // If a branch wasn't skipped, the rest must be
}
if !c.skip_flags || c.pref.output_cross_c {
c.stmts(branch.stmts)
} else {
node.branches[i].stmts = []
}
c.skip_flags = cur_skip_flags
} else {
c.stmts(branch.stmts)
}
if expr_required {
if branch.stmts.len > 0 && branch.stmts[branch.stmts.len - 1] is ast.ExprStmt {
mut last_expr := branch.stmts[branch.stmts.len - 1] as ast.ExprStmt
c.expected_type = former_expected_type
2020-05-21 22:35:43 +02:00
last_expr.typ = c.expr(last_expr.expr)
2020-08-27 06:46:18 +02:00
// if last_expr.typ != node.typ {
// if !c.check_types(node.typ, last_expr.typ) {
if !c.check_types(last_expr.typ, node.typ) {
if node.typ == table.void_type {
2020-05-21 22:35:43 +02:00
// first branch of if expression
node.is_expr = true
2020-05-21 22:35:43 +02:00
node.typ = last_expr.typ
continue
} else if node.typ in [table.any_flt_type, table.any_int_type] {
if node.typ == table.any_int_type {
if last_expr.typ.is_int() || last_expr.typ.is_float() {
node.typ = last_expr.typ
continue
2020-05-27 15:56:21 +02:00
}
} else { // node.typ == any_float
if last_expr.typ.is_float() {
node.typ = last_expr.typ
continue
}
}
2020-04-30 12:17:31 +02:00
}
if last_expr.typ in [table.any_flt_type, table.any_int_type] {
if last_expr.typ == table.any_int_type {
if node.typ.is_int() || node.typ.is_float() {
continue
2020-05-27 15:56:21 +02:00
}
} else { // expr_type == any_float
if node.typ.is_float() {
continue
}
}
}
c.error('mismatched types `${c.table.type_to_str(node.typ)}` and `${c.table.type_to_str(last_expr.typ)}`',
2020-05-28 05:50:57 +02:00
node.pos)
}
} else {
c.error('`$if_kind` expression requires an expression as the last statement of every branch',
branch.pos)
}
}
// Also check for returns inside a comp.if's statements, even if its contents aren't parsed
if has_return := c.has_return(branch.stmts) {
if has_return {
require_return = true
} else {
branch_without_return = true
}
}
}
if require_return && (!node.has_else || branch_without_return) {
c.returns = false
} else {
// if inner if branch has not covered all branches but this one
c.returns = true
}
// if only untyped literals were given default to int/f64
if node.typ == table.any_int_type {
node.typ = table.int_type
2020-05-28 05:50:57 +02:00
} else if node.typ == table.any_flt_type {
node.typ = table.f64_type
}
if expr_required {
if !node.has_else {
d := if is_ct { '$' } else { '' }
c.error('`$if_kind` expression needs `${d}else` clause', node.pos)
}
return node.typ
2020-05-11 14:38:25 +02:00
}
return table.bool_type
}
// comp_if_branch checks the condition of a compile-time `if` branch. It returns a `bool` that
// saying whether that branch's contents should be skipped (targets a different os for example)
fn (mut c Checker) comp_if_branch(cond ast.Expr, pos token.Position) bool {
// TODO: better error messages here
match cond {
ast.ParExpr {
return c.comp_if_branch(cond.expr, pos)
}
ast.PrefixExpr {
if cond.op != .not {
c.error('invalid `\$if` condition', cond.pos)
}
return !c.comp_if_branch(cond.right, cond.pos)
}
ast.PostfixExpr {
if cond.op != .question {
c.error('invalid \$if postfix operator', cond.pos)
} else if cond.expr is ast.Ident as ident {
return ident.name !in c.pref.compile_defines_all
} else {
c.error('invalid `\$if` condition', cond.pos)
}
}
ast.InfixExpr {
match cond.op {
.and {
l := c.comp_if_branch(cond.left, cond.pos)
r := c.comp_if_branch(cond.right, cond.pos)
return l && r
}
.logical_or {
l := c.comp_if_branch(cond.left, cond.pos)
r := c.comp_if_branch(cond.right, cond.pos)
return l || r
}
.key_is, .not_is {
// $if method.@type is string
// TODO better checks here, will be done in comp. for PR
if cond.left !is ast.SelectorExpr || cond.right !is ast.Type {
c.error('invalid `\$if` condition', cond.pos)
}
}
.eq, .ne {
// $if method.args.len == 1
// TODO better checks here, will be done in comp. for PR
if cond.left !is ast.SelectorExpr || cond.right !is ast.IntegerLiteral {
c.error('invalid `\$if` condition', cond.pos)
}
}
else {
c.error('invalid `\$if` condition', cond.pos)
}
}
}
ast.Ident {
if cond.name in valid_comp_if_os {
return cond.name != c.pref.os.str().to_lower() // TODO hack
} else if cond.name in valid_comp_if_compilers {
return pref.cc_from_string(cond.name) != c.pref.ccompiler_type
} else if cond.name in valid_comp_if_platforms {
return false // TODO
} else if cond.name in valid_comp_if_other {
// TODO: This should probably be moved
match cond.name as name {
'js' { return c.pref.backend != .js }
'debug' { return !c.pref.is_debug }
'test' { return !c.pref.is_test }
'glibc' { return false } // TODO
'prealloc' { return !c.pref.prealloc }
'no_bounds_checking' { return cond.name !in c.pref.compile_defines_all }
else { return false }
}
}
}
else {
c.error('invalid `\$if` condition', pos)
}
}
return false
}
2020-08-10 23:59:38 +02:00
fn (c &Checker) has_return(stmts []ast.Stmt) ?bool {
// complexity means either more match or ifs
mut has_complexity := false
for s in stmts {
if s is ast.ExprStmt {
if s.expr is ast.IfExpr || s.expr is ast.MatchExpr {
has_complexity = true
break
}
}
}
// if the inner complexity covers all paths with returns there is no need for further checks
if !has_complexity || !c.returns {
return has_top_return(stmts)
}
return none
}
pub fn (mut c Checker) postfix_expr(mut node ast.PostfixExpr) table.Type {
2020-02-04 07:37:38 +01:00
typ := c.expr(node.expr)
2020-02-27 17:31:10 +01:00
typ_sym := c.table.get_type_symbol(typ)
2020-04-25 09:08:53 +02:00
// if !typ.is_number() {
2020-02-27 17:31:10 +01:00
if !typ_sym.is_number() {
2020-08-22 12:29:15 +02:00
c.error('invalid operation: $node.op.str() (non-numeric type `$typ_sym.source_name`)',
2020-06-24 14:44:06 +02:00
node.pos)
2020-07-14 18:11:16 +02:00
} else {
node.auto_locked, _ = c.fail_if_immutable(node.expr)
2020-02-04 07:37:38 +01:00
}
if (typ.is_ptr() || typ_sym.is_pointer()) && !c.inside_unsafe {
c.warn('pointer arithmetic is only allowed in `unsafe` blocks', node.pos)
}
2020-02-04 07:37:38 +01:00
return typ
}
2020-07-20 22:30:09 +02:00
fn (mut c Checker) check_index_type(typ_sym &table.TypeSymbol, index_type table.Type, pos token.Position) {
index_type_sym := c.table.get_type_symbol(index_type)
2020-08-22 12:29:15 +02:00
// println('index expr left=$typ_sym.source_name $node.pos.line_nr')
// if typ_sym.kind == .array && (!(table.type_idx(index_type) in table.number_type_idxs) &&
// index_type_sym.kind != .enum_) {
if typ_sym.kind in [.array, .array_fixed] && !(index_type.is_number() || index_type_sym.kind ==
.enum_) {
2020-08-22 12:29:15 +02:00
c.error('non-integer index `$index_type_sym.source_name` (array type `$typ_sym.source_name`)',
pos)
}
}
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) index_expr(mut node ast.IndexExpr) table.Type {
2020-02-19 14:34:44 +01:00
typ := c.expr(node.left)
node.left_type = typ
2020-03-31 16:47:55 +02:00
typ_sym := c.table.get_type_symbol(typ)
if typ_sym.kind !in [.array, .array_fixed, .string, .map] && !typ.is_ptr() && !(!typ_sym.name[0].is_capital() &&
typ_sym.name.ends_with('ptr')) && !typ.has_flag(.variadic) { // byteptr, charptr etc
2020-08-22 12:29:15 +02:00
c.error('type `$typ_sym.source_name` does not support indexing', node.pos)
}
if typ_sym.kind == .string && !typ.is_ptr() && node.is_setter {
2020-08-13 23:21:11 +02:00
c.error('cannot assign to s[i] since V strings are immutable\n' +
'(note, that variables may be mutable but string values are always immutable, like in Go and Java)',
node.pos)
}
if !c.inside_unsafe && (typ.is_ptr() || typ.is_pointer()) {
mut is_ok := false
if node.left is ast.Ident {
ident := node.left as ast.Ident
scope := c.file.scope.innermost(ident.pos.pos)
if v := scope.find_var(ident.name) {
// `mut param []T` function parameter
is_ok = v.is_mut && v.is_arg && !typ.deref().is_ptr()
}
}
2020-08-09 03:57:54 +02:00
if !is_ok && !c.pref.translated {
c.warn('pointer indexing is only allowed in `unsafe` blocks', node.pos)
}
}
if node.index !is ast.RangeExpr { // [1]
index_type := c.expr(node.index)
c.check_index_type(typ_sym, index_type, node.pos)
if typ_sym.kind == .map && index_type.idx() != table.string_type_idx {
2020-08-22 12:29:15 +02:00
c.error('non-string map index (map type `$typ_sym.source_name`)', node.pos)
}
value_type := c.table.value_type(typ)
if value_type != table.void_type {
return value_type
}
} else { // [1..2]
range := node.index as ast.RangeExpr
if range.has_low {
index_type := c.expr(range.low)
c.check_index_type(typ_sym, index_type, node.pos)
}
if range.has_high {
index_type := c.expr(range.high)
c.check_index_type(typ_sym, index_type, node.pos)
}
2020-03-31 16:47:55 +02:00
// array[1..2] => array
// fixed_array[1..2] => array
if typ_sym.kind == .array_fixed {
elem_type := c.table.value_type(typ)
idx := c.table.find_or_register_array(elem_type, 1, c.mod)
2020-03-31 16:47:55 +02:00
return table.new_type(idx)
}
}
return typ
}
// `.green` or `Color.green`
// If a short form is used, `expected_type` needs to be an enum
// with this value.
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) enum_val(mut node ast.EnumVal) table.Type {
typ_idx := if node.enum_name == '' {
2020-04-25 09:08:53 +02:00
c.expected_type.idx()
2020-04-25 17:49:16 +02:00
} else { //
c.table.find_type_idx(node.enum_name)
}
2020-02-29 18:34:25 +01:00
// println('checker: enum_val: $node.enum_name typeidx=$typ_idx')
if typ_idx == 0 {
c.error('not an enum (name=$node.enum_name) (type_idx=0)', node.pos)
2020-05-08 15:00:04 +02:00
return table.void_type
2020-02-29 18:34:25 +01:00
}
mut typ := table.new_type(typ_idx)
2020-08-11 16:26:49 +02:00
if c.pref.translated {
// TODO make more strict
node.typ = typ
return typ
}
2020-04-26 19:59:03 +02:00
if typ == table.void_type {
c.error('not an enum', node.pos)
2020-05-08 15:00:04 +02:00
return table.void_type
2020-04-26 19:59:03 +02:00
}
mut typ_sym := c.table.get_type_symbol(typ)
2020-08-22 12:29:15 +02:00
// println('tname=$typ_sym.source_name $node.pos.line_nr $c.file.path')
if typ_sym.kind == .array && node.enum_name.len == 0 {
array_info := typ_sym.info as table.Array
typ = array_info.elem_type
typ_sym = c.table.get_type_symbol(typ)
}
2020-08-11 16:26:49 +02:00
if typ_sym.kind != .enum_ && !c.pref.translated {
// TODO in C int fields can be compared to enums, need to handle that in C2V
2020-08-22 12:29:15 +02:00
c.error('expected type is not an enum (`$typ_sym.source_name`)', node.pos)
2020-05-08 15:00:04 +02:00
return table.void_type
}
2020-07-08 15:46:58 +02:00
if typ_sym.info !is table.Enum {
2020-05-08 15:00:04 +02:00
c.error('not an enum', node.pos)
return table.void_type
}
2020-03-15 02:51:31 +01:00
// info := typ_sym.info as table.Enum
info := typ_sym.enum_info()
2020-08-22 12:29:15 +02:00
// rintln('checker: x = $info.x enum val $c.expected_type $typ_sym.source_name')
2020-02-27 00:12:37 +01:00
// println(info.vals)
if node.val !in info.vals {
2020-08-22 12:29:15 +02:00
c.error('enum `$typ_sym.source_name` does not have a value `$node.val`', node.pos)
}
2020-03-15 02:51:31 +01:00
node.typ = typ
return typ
}
pub fn (mut c Checker) chan_init(mut node ast.ChanInit) table.Type {
if node.typ != 0 {
info := c.table.get_type_symbol(node.typ).chan_info()
node.elem_type = info.elem_type
if node.has_cap {
c.check_array_init_para_type('cap', node.cap_expr, node.pos)
}
return node.typ
} else {
c.error('`chan` of unknown type', node.pos)
return node.typ
}
}
2020-05-11 14:38:25 +02:00
pub fn (mut c Checker) map_init(mut node ast.MapInit) table.Type {
2020-03-07 08:13:00 +01:00
// `x ;= map[string]string` - set in parser
if node.typ != 0 {
info := c.table.get_type_symbol(node.typ).map_info()
key_sym := c.table.get_type_symbol(info.key_type)
value_sym := c.table.get_type_symbol(info.value_type)
if key_sym.kind == .placeholder {
c.error('unknown type `$key_sym.source_name`', node.pos)
}
if value_sym.kind == .placeholder {
c.error('unknown type `$value_sym.source_name`', node.pos)
}
2020-03-07 08:13:00 +01:00
node.key_type = info.key_type
node.value_type = info.value_type
return node.typ
}
// `{'age': 20}`
key0_type := c.table.mktyp(c.expr(node.keys[0]))
val0_type := c.table.mktyp(c.expr(node.vals[0]))
for i, key in node.keys {
key_i := key as ast.StringLiteral
2020-05-11 14:38:25 +02:00
for j in 0 .. i {
key_j := node.keys[j] as ast.StringLiteral
if key_i.val == key_j.val {
c.error('duplicate key "$key_i.val" in map literal', key.position())
}
}
if i == 0 {
continue
}
val := node.vals[i]
key_type := c.expr(key)
val_type := c.expr(val)
2020-05-24 04:43:00 +02:00
if !c.check_types(key_type, key0_type) {
key0_type_sym := c.table.get_type_symbol(key0_type)
key_type_sym := c.table.get_type_symbol(key_type)
2020-08-22 12:29:15 +02:00
c.error('map init: cannot use `$key_type_sym.source_name` as `$key0_type_sym.source_name` for map key',
2020-04-07 18:51:39 +02:00
node.pos)
}
2020-05-24 04:43:00 +02:00
if !c.check_types(val_type, val0_type) {
val0_type_sym := c.table.get_type_symbol(val0_type)
val_type_sym := c.table.get_type_symbol(val_type)
2020-08-22 12:29:15 +02:00
c.error('map init: cannot use `$val_type_sym.source_name` as `$val0_type_sym.source_name` for map value',
2020-04-07 18:51:39 +02:00
node.pos)
}
}
map_type := table.new_type(c.table.find_or_register_map(key0_type, val0_type))
node.typ = map_type
2020-03-07 08:13:00 +01:00
node.key_type = key0_type
node.value_type = val0_type
return map_type
}
pub fn (mut c Checker) add_error_detail(s string) {
c.error_details << s
}
2020-04-25 17:49:16 +02:00
pub fn (mut c Checker) warn(s string, pos token.Position) {
allow_warnings := !c.pref.is_prod // allow warnings only in dev builds
c.warn_or_error(s, pos, allow_warnings) // allow warnings only in dev builds
}
2020-04-25 17:49:16 +02:00
pub fn (mut c Checker) error(message string, pos token.Position) {
2020-08-11 16:26:49 +02:00
if c.pref.translated && message.starts_with('mismatched types') {
// TODO move this
return
}
2020-04-16 15:32:11 +02:00
if c.pref.is_verbose {
print_backtrace()
}
msg := message.replace('`array_', '`[]')
c.warn_or_error(msg, pos, false)
}
2020-08-14 14:57:08 +02:00
// check_struct_signature checks if both structs has the same signature / fields for casting
fn (c Checker) check_struct_signature(from, to table.Struct) bool {
if from.fields.len != to.fields.len {
return false
}
for _, field in from.fields {
filtered := to.fields.filter(it.name == field.name)
if filtered.len != 1 {
// field doesn't exist
return false
}
counterpart := filtered[0]
if field.typ != counterpart.typ {
// field has different tye
return false
}
if field.is_pub != counterpart.is_pub {
// field is not public while the other one is
return false
}
if field.is_mut != counterpart.is_mut {
// field is not mutable while the other one is
return false
}
}
return true
}
2020-04-25 17:49:16 +02:00
fn (mut c Checker) warn_or_error(message string, pos token.Position, warn bool) {
2020-04-13 01:56:01 +02:00
// add backtrace to issue struct, how?
// if c.pref.is_verbose {
// print_backtrace()
2020-04-13 01:56:01 +02:00
// }
mut details := ''
if c.error_details.len > 0 {
details = c.error_details.join('\n')
c.error_details = []
}
if warn && !c.pref.skip_warnings {
c.nr_warnings++
wrn := errors.Warning{
reporter: errors.Reporter.checker
pos: pos
file_path: c.file.path
message: message
details: details
}
c.file.warnings << wrn
c.warnings << wrn
return
}
if !warn {
c.nr_errors++
if pos.line_nr !in c.error_lines {
err := errors.Error{
reporter: errors.Reporter.checker
2020-04-13 01:56:01 +02:00
pos: pos
file_path: c.file.path
message: message
details: details
2020-04-13 01:56:01 +02:00
}
c.file.errors << err
c.errors << err
2020-04-13 01:56:01 +02:00
c.error_lines << pos.line_nr
}
2020-02-19 19:54:36 +01:00
}
}
// for debugging only
fn (c &Checker) fileis(s string) bool {
return c.file.path.contains(s)
}
fn (mut c Checker) sql_expr(mut node ast.SqlExpr) table.Type {
c.inside_sql = true
defer {
c.inside_sql = false
}
sym := c.table.get_type_symbol(node.table_type)
2020-06-28 19:01:30 +02:00
if sym.kind == .placeholder {
2020-08-22 12:29:15 +02:00
c.error('orm: unknown type `$sym.source_name`', node.pos)
2020-06-28 19:01:30 +02:00
return table.void_type
}
c.cur_orm_ts = sym
info := sym.info as table.Struct
fields := c.fetch_and_verify_orm_fields(info, node.pos, node.table_name)
node.fields = fields
node.table_name = sym.name
2020-06-17 04:05:13 +02:00
if node.has_where {
c.expr(node.where_expr)
}
2020-06-27 16:41:29 +02:00
if node.has_offset {
c.expr(node.offset_expr)
}
if node.has_limit {
c.expr(node.limit_expr)
}
2020-07-02 19:29:22 +02:00
if node.has_order {
c.expr(node.order_expr)
}
c.expr(node.db_expr)
2020-06-17 04:05:13 +02:00
return node.typ
}
fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) table.Type {
c.inside_sql = true
defer {
c.inside_sql = false
}
2020-06-28 19:01:30 +02:00
if node.table_type == 0 {
c.error('orm: unknown type `$node.table_name`', node.pos)
}
sym := c.table.get_type_symbol(node.table_type)
2020-06-28 19:01:30 +02:00
if sym.kind == .placeholder {
2020-08-22 12:29:15 +02:00
c.error('orm: unknown type `$sym.source_name`', node.pos)
2020-06-28 19:01:30 +02:00
return table.void_type
}
c.cur_orm_ts = sym
info := sym.info as table.Struct
fields := c.fetch_and_verify_orm_fields(info, node.pos, node.table_name)
node.fields = fields
c.expr(node.db_expr)
2020-06-27 16:41:29 +02:00
if node.kind == .update {
for expr in node.update_exprs {
c.expr(expr)
}
}
2020-06-25 22:37:36 +02:00
c.expr(node.where_expr)
2020-06-19 16:43:32 +02:00
return table.void_type
}
fn (mut c Checker) fetch_and_verify_orm_fields(info table.Struct, pos token.Position, table_name string) []table.Field {
fields := info.fields.filter(it.typ in
[table.string_type, table.int_type, table.bool_type] && !it.attrs.contains('skip'))
if fields.len == 0 {
c.error('V orm: select: empty fields in `$table_name`', pos)
}
if fields[0].name != 'id' {
c.error('V orm: `id int` must be the first field in `$table_name`', pos)
}
return fields
}
fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
c.returns = false
if node.is_generic && c.cur_generic_type == 0 { // need the cur_generic_type check to avoid inf. recursion
// loop thru each generic type and generate a function
for gen_type in c.table.fn_gen_types[node.name] {
c.cur_generic_type = gen_type
2020-05-28 05:50:57 +02:00
// sym:=c.table.get_type_symbol(gen_type)
2020-08-22 12:29:15 +02:00
// println('\ncalling check for $node.name for type $sym.source_name')
c.fn_decl(mut node)
}
c.cur_generic_type = 0
return
}
if node.language == .v && !c.is_builtin_mod {
c.check_valid_snake_case(node.name, 'function name', node.pos)
}
if node.is_method {
mut sym := c.table.get_type_symbol(node.receiver.typ)
if sym.kind == .interface_ {
c.error('interfaces cannot be used as method receiver', node.receiver_pos)
}
// if sym.has_method(node.name) {
// c.warn('duplicate method `$node.name`', node.pos)
// }
// Do not allow to modify types from other modules
if sym.mod != c.mod && !c.is_builtin_mod && sym.mod != '' { // TODO remove != ''
// remove the method to hide other related errors (`method is private` etc)
mut idx := 0
for i, m in sym.methods {
if m.name == node.name {
idx = i
break
}
}
sym.methods.delete(idx)
//
2020-08-22 12:29:15 +02:00
c.error('cannot define new methods on non-local `$sym.source_name` (' +
'current module is `$c.mod`, `$sym.source_name` is from `$sym.mod`)', node.pos)
}
}
if node.language == .v {
// Make sure all types are valid
for arg in node.args {
sym := c.table.get_type_symbol(arg.typ)
if sym.kind == .placeholder {
2020-08-22 12:29:15 +02:00
c.error('unknown type `$sym.source_name`', node.pos)
}
}
}
if node.language == .v && node.is_method && node.name == 'str' {
if node.return_type != table.string_type {
c.error('.str() methods should return `string`', node.pos)
}
if node.args.len != 1 {
c.error('.str() methods should have 0 arguments', node.pos)
}
}
c.expected_type = table.void_type
c.cur_fn = node
// Add return if `fn(...) ? {...}` have no return at end
if node.return_type != table.void_type && node.return_type.has_flag(.optional) &&
(node.stmts.len == 0 || node.stmts[node.stmts.len - 1] !is ast.Return) {
sym := c.table.get_type_symbol(node.return_type)
if sym.kind == .void {
node.stmts << ast.Return{
pos: node.pos
}
} else {
node.stmts << ast.Return{
pos: node.pos
exprs: [ast.Expr(ast.None{
pos: node.pos
})]
}
}
}
c.stmts(node.stmts)
returns := c.returns || has_top_return(node.stmts)
if node.language == .v && !node.no_body && node.return_type != table.void_type && !returns &&
node.name !in ['panic', 'exit'] {
c.error('missing return at end of function `$node.name`', node.pos)
}
c.returns = false
}
fn has_top_return(stmts []ast.Stmt) bool {
for stmt in stmts {
if stmt is ast.Return {
return true
} else if stmt is ast.Block {
if has_top_return(stmt.stmts) {
return true
}
} else if stmt is ast.ExprStmt {
if stmt.expr is ast.CallExpr as ce {
if ce.name in ['panic', 'exit'] {
return true
}
}
}
}
return false
}