checker: split up checker.v: assign.v, orm.v, comptime.v; c2v fixes
parent
ed4ecae57d
commit
eaf0f9b4c1
|
@ -0,0 +1,595 @@
|
|||
// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
|
||||
module checker
|
||||
|
||||
import v.ast
|
||||
import v.pref
|
||||
|
||||
// TODO 600 line function
|
||||
pub fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) {
|
||||
c.expected_type = ast.none_type // TODO a hack to make `x := if ... work`
|
||||
defer {
|
||||
c.expected_type = ast.void_type
|
||||
}
|
||||
right_first := node.right[0]
|
||||
node.left_types = []
|
||||
mut right_len := node.right.len
|
||||
mut right_type0 := ast.void_type
|
||||
for i, right in node.right {
|
||||
if right in [ast.CallExpr, ast.IfExpr, ast.LockExpr, ast.MatchExpr] {
|
||||
right_type := c.expr(right)
|
||||
if i == 0 {
|
||||
right_type0 = right_type
|
||||
node.right_types = [
|
||||
c.check_expr_opt_call(right, right_type0),
|
||||
]
|
||||
}
|
||||
right_type_sym := c.table.get_type_symbol(right_type)
|
||||
if right_type_sym.kind == .multi_return {
|
||||
if node.right.len > 1 {
|
||||
c.error('cannot use multi-value $right_type_sym.name in single-value context',
|
||||
right.position())
|
||||
}
|
||||
node.right_types = right_type_sym.mr_info().types
|
||||
right_len = node.right_types.len
|
||||
} else if right_type == ast.void_type {
|
||||
right_len = 0
|
||||
}
|
||||
}
|
||||
if right is ast.InfixExpr {
|
||||
if right.op == .arrow {
|
||||
c.error('cannot use `<-` on the right-hand side of an assignment, as it does not return any values',
|
||||
right.pos)
|
||||
}
|
||||
}
|
||||
if right is ast.Ident {
|
||||
if right.is_mut {
|
||||
c.error('unexpected `mut` on right-hand side of assignment', right.mut_pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.left.len != right_len {
|
||||
if right_first is ast.CallExpr {
|
||||
if node.left_types.len > 0 && node.left_types[0] == ast.void_type {
|
||||
// If it's a void type, it's an unknown variable, already had an error earlier.
|
||||
return
|
||||
}
|
||||
c.error('assignment mismatch: $node.left.len variable(s) but `${right_first.name}()` returns $right_len value(s)',
|
||||
node.pos)
|
||||
} else {
|
||||
c.error('assignment mismatch: $node.left.len variable(s) $right_len value(s)',
|
||||
node.pos)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
is_decl := node.op == .decl_assign
|
||||
for i, left in node.left {
|
||||
if left is ast.CallExpr {
|
||||
// ban `foo() = 10`
|
||||
c.error('cannot call function `${left.name}()` on the left side of an assignment',
|
||||
left.pos)
|
||||
} else if left is ast.PrefixExpr {
|
||||
// ban `*foo() = 10`
|
||||
if left.right is ast.CallExpr && left.op == .mul {
|
||||
c.error('cannot dereference a function call on the left side of an assignment, use a temporary variable',
|
||||
left.pos)
|
||||
}
|
||||
} else if left is ast.IndexExpr {
|
||||
if left.index is ast.RangeExpr {
|
||||
c.error('cannot reassign using range expression on the left side of an assignment',
|
||||
left.pos)
|
||||
}
|
||||
}
|
||||
is_blank_ident := left.is_blank_ident()
|
||||
mut left_type := ast.void_type
|
||||
if !is_decl && !is_blank_ident {
|
||||
if left in [ast.Ident, ast.SelectorExpr] {
|
||||
c.prevent_sum_type_unwrapping_once = true
|
||||
}
|
||||
left_type = c.expr(left)
|
||||
c.expected_type = c.unwrap_generic(left_type)
|
||||
// `map = {}`
|
||||
if left_type != 0 {
|
||||
sym := c.table.get_type_symbol(left_type)
|
||||
if sym.kind == .map {
|
||||
if node.right.len <= i {
|
||||
// `map_1, map_2, map_3 = f()`, where f returns (map[int]int, map[int]int, map[int]int)
|
||||
// i.e. 3 left parts of the assignment, but just 1 right part
|
||||
} else {
|
||||
if node.right[i] is ast.StructInit {
|
||||
c.warn('assigning a struct literal to a map is deprecated - use `map{}` instead',
|
||||
node.right[i].position())
|
||||
node.right[i] = ast.MapInit{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.right_types.len < node.left.len { // first type or multi return types added above
|
||||
old_inside_ref_lit := c.inside_ref_lit
|
||||
if left is ast.Ident {
|
||||
if left.info is ast.IdentVar {
|
||||
c.inside_ref_lit = c.inside_ref_lit || left.info.share == .shared_t
|
||||
}
|
||||
}
|
||||
c.inside_decl_rhs = is_decl
|
||||
right_type := c.expr(node.right[i])
|
||||
c.inside_decl_rhs = false
|
||||
c.inside_ref_lit = old_inside_ref_lit
|
||||
if node.right_types.len == i {
|
||||
node.right_types << c.check_expr_opt_call(node.right[i], right_type)
|
||||
}
|
||||
}
|
||||
right := if i < node.right.len { node.right[i] } else { node.right[0] }
|
||||
mut right_type := node.right_types[i]
|
||||
if right is ast.Ident {
|
||||
right_sym := c.table.get_type_symbol(right_type)
|
||||
if right_sym.info is ast.Struct {
|
||||
if right_sym.info.generic_types.len > 0 {
|
||||
if obj := right.scope.find(right.name) {
|
||||
right_type = obj.typ
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_decl {
|
||||
// check generic struct init and return unwrap generic struct type
|
||||
if right is ast.StructInit {
|
||||
if right.typ.has_flag(.generic) {
|
||||
c.expr(right)
|
||||
right_type = right.typ
|
||||
}
|
||||
} else if right is ast.PrefixExpr {
|
||||
if right.op == .amp && right.right is ast.StructInit {
|
||||
right_type = c.expr(right)
|
||||
}
|
||||
}
|
||||
if right.is_auto_deref_var() {
|
||||
left_type = c.table.mktyp(right_type.deref())
|
||||
} else {
|
||||
left_type = c.table.mktyp(right_type)
|
||||
}
|
||||
if left_type == ast.int_type {
|
||||
if right is ast.IntegerLiteral {
|
||||
mut is_large := right.val.len > 13
|
||||
if !is_large && right.val.len > 8 {
|
||||
val := right.val.i64()
|
||||
is_large = val > int_max || val < int_min
|
||||
}
|
||||
if is_large {
|
||||
c.error('overflow in implicit type `int`, use explicit type casting instead',
|
||||
right.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Make sure the variable is mutable
|
||||
c.fail_if_immutable(left)
|
||||
// left_type = c.expr(left)
|
||||
}
|
||||
if right_type.is_ptr() && left_type.is_ptr() {
|
||||
if mut right is ast.Ident {
|
||||
if mut right.obj is ast.Var {
|
||||
mut obj := unsafe { &right.obj }
|
||||
if c.fn_scope != voidptr(0) {
|
||||
obj = c.fn_scope.find_var(right.obj.name) or { obj }
|
||||
}
|
||||
if obj.is_stack_obj && !c.inside_unsafe {
|
||||
type_sym := c.table.get_type_symbol(obj.typ.set_nr_muls(0))
|
||||
if !type_sym.is_heap() && !c.pref.translated {
|
||||
suggestion := if type_sym.kind == .struct_ {
|
||||
'declaring `$type_sym.name` as `[heap]`'
|
||||
} else {
|
||||
'wrapping the `$type_sym.name` object in a `struct` declared as `[heap]`'
|
||||
}
|
||||
c.error('`$right.name` cannot be assigned outside `unsafe` blocks as it might refer to an object stored on stack. Consider ${suggestion}.',
|
||||
right.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Do not allow `a := 0; b := 0; a = &b`
|
||||
if !is_decl && left is ast.Ident && !is_blank_ident && !left_type.is_real_pointer()
|
||||
&& right_type.is_real_pointer() {
|
||||
left_sym := c.table.get_type_symbol(left_type)
|
||||
if left_sym.kind != .function {
|
||||
c.warn(
|
||||
'cannot assign a reference to a value (this will be an error soon) left=${c.table.type_str(left_type)} $left_type.is_ptr() ' +
|
||||
'right=${c.table.type_str(right_type)} $right_type.is_real_pointer() ptr=$right_type.is_ptr()',
|
||||
node.pos)
|
||||
}
|
||||
}
|
||||
node.left_types << left_type
|
||||
match mut left {
|
||||
ast.Ident {
|
||||
if left.kind == .blank_ident {
|
||||
left_type = right_type
|
||||
node.left_types[i] = right_type
|
||||
if node.op !in [.assign, .decl_assign] {
|
||||
c.error('cannot modify blank `_` identifier', left.pos)
|
||||
}
|
||||
} else if left.info !is ast.IdentVar {
|
||||
c.error('cannot assign to $left.kind `$left.name`', left.pos)
|
||||
} else {
|
||||
if is_decl {
|
||||
c.check_valid_snake_case(left.name, 'variable name', left.pos)
|
||||
if left.name in reserved_type_names {
|
||||
c.error('invalid use of reserved type `$left.name` as a variable name',
|
||||
left.pos)
|
||||
}
|
||||
}
|
||||
mut ident_var_info := left.info as ast.IdentVar
|
||||
if ident_var_info.share == .shared_t {
|
||||
left_type = left_type.set_flag(.shared_f)
|
||||
if is_decl {
|
||||
if left_type.nr_muls() > 1 {
|
||||
c.error('shared cannot be multi level reference', left.pos)
|
||||
}
|
||||
left_type = left_type.set_nr_muls(1)
|
||||
}
|
||||
} else if left_type.has_flag(.shared_f) {
|
||||
left_type = left_type.clear_flag(.shared_f)
|
||||
}
|
||||
if ident_var_info.share == .atomic_t {
|
||||
left_type = left_type.set_flag(.atomic_f)
|
||||
}
|
||||
node.left_types[i] = left_type
|
||||
ident_var_info.typ = left_type
|
||||
left.info = ident_var_info
|
||||
if left_type != 0 {
|
||||
match mut left.obj {
|
||||
ast.Var {
|
||||
left.obj.typ = left_type
|
||||
if left.obj.is_auto_deref {
|
||||
left.obj.is_used = true
|
||||
}
|
||||
if !left_type.is_ptr() {
|
||||
if c.table.get_type_symbol(left_type).is_heap() {
|
||||
left.obj.is_auto_heap = true
|
||||
}
|
||||
}
|
||||
if left_type in ast.unsigned_integer_type_idxs {
|
||||
if right is ast.IntegerLiteral {
|
||||
if right.val[0] == `-` {
|
||||
c.error('Cannot assign negative value to unsigned integer type',
|
||||
right.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ast.GlobalField {
|
||||
left.obj.typ = left_type
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
if is_decl {
|
||||
full_name := '${left.mod}.$left.name'
|
||||
if obj := c.file.global_scope.find(full_name) {
|
||||
if obj is ast.ConstField {
|
||||
c.warn('duplicate of a const name `$full_name`', left.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ast.PrefixExpr {
|
||||
// Do now allow `*x = y` outside `unsafe`
|
||||
if left.op == .mul {
|
||||
if !c.inside_unsafe && !c.pref.translated {
|
||||
c.error('modifying variables via dereferencing can only be done in `unsafe` blocks',
|
||||
node.pos)
|
||||
} else {
|
||||
// mark `p` in `*p = val` as used:
|
||||
match mut left.right {
|
||||
ast.Ident {
|
||||
match mut left.right.obj {
|
||||
ast.Var {
|
||||
left.right.obj.is_used = true
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_decl {
|
||||
c.error('non-name on the left side of `:=`', left.pos)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if mut left is ast.IndexExpr {
|
||||
// eprintln('>>> left.is_setter: ${left.is_setter:10} | left.is_map: ${left.is_map:10} | left.is_array: ${left.is_array:10}')
|
||||
if left.is_map && left.is_setter {
|
||||
left.recursive_mapset_is_setter(true)
|
||||
}
|
||||
}
|
||||
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)
|
||||
if right_type_unwrapped == 0 {
|
||||
// right type was a generic `T`
|
||||
continue
|
||||
}
|
||||
if c.pref.translated {
|
||||
// TODO fix this in C2V instead, for example cast enums to int before using `|` on them.
|
||||
// TODO replace all c.pref.translated checks with `$if !translated` for performance
|
||||
continue
|
||||
}
|
||||
if left_type_unwrapped == 0 {
|
||||
continue
|
||||
}
|
||||
left_sym := c.table.get_type_symbol(left_type_unwrapped)
|
||||
right_sym := c.table.get_type_symbol(right_type_unwrapped)
|
||||
if left_sym.kind == .array && !c.inside_unsafe && node.op in [.assign, .decl_assign]
|
||||
&& right_sym.kind == .array && (left is ast.Ident && !left.is_blank_ident())
|
||||
&& right is ast.Ident {
|
||||
// Do not allow `a = b`, only `a = b.clone()`
|
||||
c.error('use `array2 $node.op.str() array1.clone()` instead of `array2 $node.op.str() array1` (or use `unsafe`)',
|
||||
node.pos)
|
||||
}
|
||||
if left_sym.kind == .array_fixed && !c.inside_unsafe && node.op in [.assign, .decl_assign]
|
||||
&& right_sym.kind == .array_fixed && (left is ast.Ident && !left.is_blank_ident())
|
||||
&& right is ast.Ident {
|
||||
if right_sym.info is ast.ArrayFixed {
|
||||
if right_sym.info.elem_type.is_ptr() {
|
||||
c.error('assignment from one fixed array to another with a pointer element type is prohibited outside of `unsafe`',
|
||||
node.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
if left_sym.kind == .map && node.op in [.assign, .decl_assign] && right_sym.kind == .map
|
||||
&& ((right is ast.Ident && right.is_auto_deref_var())
|
||||
|| !right_type.is_ptr()) && !left.is_blank_ident() && right.is_lvalue() {
|
||||
// Do not allow `a = b`
|
||||
c.error('cannot copy map: call `move` or `clone` method (or use a reference)',
|
||||
right.position())
|
||||
}
|
||||
left_is_ptr := left_type.is_ptr() || left_sym.is_pointer()
|
||||
if left_is_ptr && !left.is_auto_deref_var() {
|
||||
if !c.inside_unsafe && node.op !in [.assign, .decl_assign] {
|
||||
// ptr op=
|
||||
c.warn('pointer arithmetic is only allowed in `unsafe` blocks', node.pos)
|
||||
}
|
||||
right_is_ptr := right_type.is_ptr() || right_sym.is_pointer()
|
||||
if !right_is_ptr && node.op == .assign && right_type_unwrapped.is_number() {
|
||||
c.error('cannot assign to `$left`: ' +
|
||||
c.expected_msg(right_type_unwrapped, left_type_unwrapped), right.position())
|
||||
}
|
||||
if (right is ast.StructInit || !right_is_ptr) && !(right_sym.is_number()
|
||||
|| left_type.has_flag(.shared_f)) {
|
||||
left_name := c.table.type_to_str(left_type_unwrapped)
|
||||
mut rtype := right_type_unwrapped
|
||||
if rtype.is_ptr() {
|
||||
rtype = rtype.deref()
|
||||
}
|
||||
right_name := c.table.type_to_str(rtype)
|
||||
c.error('mismatched types `$left_name` and `$right_name`', node.pos)
|
||||
}
|
||||
}
|
||||
// Single side check
|
||||
match node.op {
|
||||
.assign {} // No need to do single side check for =. But here put it first for speed.
|
||||
.plus_assign, .minus_assign {
|
||||
if left_type == ast.string_type {
|
||||
if node.op != .plus_assign {
|
||||
c.error('operator `$node.op` not defined on left operand type `$left_sym.name`',
|
||||
left.position())
|
||||
}
|
||||
if right_type != ast.string_type {
|
||||
c.error('invalid right operand: $left_sym.name $node.op $right_sym.name',
|
||||
right.position())
|
||||
}
|
||||
} else if !left_sym.is_number()
|
||||
&& left_sym.kind !in [.byteptr, .charptr, .struct_, .alias] {
|
||||
c.error('operator `$node.op` not defined on left operand type `$left_sym.name`',
|
||||
left.position())
|
||||
} else if !right_sym.is_number()
|
||||
&& left_sym.kind !in [.byteptr, .charptr, .struct_, .alias] {
|
||||
c.error('invalid right operand: $left_sym.name $node.op $right_sym.name',
|
||||
right.position())
|
||||
}
|
||||
}
|
||||
.mult_assign, .div_assign {
|
||||
if !left_sym.is_number()
|
||||
&& !c.table.get_final_type_symbol(left_type_unwrapped).is_int()
|
||||
&& left_sym.kind !in [.struct_, .alias] {
|
||||
c.error('operator $node.op.str() not defined on left operand type `$left_sym.name`',
|
||||
left.position())
|
||||
} else if !right_sym.is_number()
|
||||
&& !c.table.get_final_type_symbol(left_type_unwrapped).is_int()
|
||||
&& left_sym.kind !in [.struct_, .alias] {
|
||||
c.error('operator $node.op.str() not defined on right operand type `$right_sym.name`',
|
||||
right.position())
|
||||
}
|
||||
}
|
||||
.and_assign, .or_assign, .xor_assign, .mod_assign, .left_shift_assign,
|
||||
.right_shift_assign {
|
||||
if !left_sym.is_int()
|
||||
&& !c.table.get_final_type_symbol(left_type_unwrapped).is_int() {
|
||||
c.error('operator $node.op.str() not defined on left operand type `$left_sym.name`',
|
||||
left.position())
|
||||
} else if !right_sym.is_int()
|
||||
&& !c.table.get_final_type_symbol(right_type_unwrapped).is_int() {
|
||||
c.error('operator $node.op.str() not defined on right operand type `$right_sym.name`',
|
||||
right.position())
|
||||
}
|
||||
}
|
||||
.unsigned_right_shift_assign {
|
||||
if node.left.len != 1 || node.right.len != 1 {
|
||||
c.error('unsupported operation: unable to lower expression for unsigned shift assignment.',
|
||||
node.pos)
|
||||
}
|
||||
|
||||
modified_left_type := if !left_type.is_int() {
|
||||
c.error('invalid operation: shift on type `${c.table.get_type_symbol(left_type).name}`',
|
||||
node.pos)
|
||||
ast.void_type_idx
|
||||
} else if left_type.is_int_literal() {
|
||||
// int literal => i64
|
||||
ast.u32_type_idx
|
||||
} else if left_type.is_unsigned() {
|
||||
left_type
|
||||
} else {
|
||||
// signed types' idx adds with 5 will get correct relative unsigned type
|
||||
// i8 => byte
|
||||
// i16 => u16
|
||||
// int => u32
|
||||
// i64 => u64
|
||||
// isize => usize
|
||||
// i128 => u128 NOT IMPLEMENTED YET
|
||||
left_type.idx() + ast.u32_type_idx - ast.int_type_idx
|
||||
}
|
||||
|
||||
node = ast.AssignStmt{
|
||||
op: .assign
|
||||
pos: node.pos
|
||||
comments: node.comments
|
||||
end_comments: node.end_comments
|
||||
left: node.left
|
||||
right: [
|
||||
ast.Expr(ast.InfixExpr{
|
||||
left: ast.CastExpr{
|
||||
expr: node.left[0]
|
||||
typ: modified_left_type
|
||||
typname: c.table.type_str(modified_left_type)
|
||||
pos: node.pos
|
||||
}
|
||||
op: .right_shift
|
||||
right: node.right[0]
|
||||
left_type: modified_left_type
|
||||
right_type: right_type
|
||||
pos: node.pos
|
||||
}),
|
||||
]
|
||||
left_types: node.left_types
|
||||
right_types: node.right_types
|
||||
is_static: node.is_static
|
||||
is_simple: node.is_simple
|
||||
has_cross_var: node.has_cross_var
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
if node.op in [.plus_assign, .minus_assign, .mod_assign, .mult_assign, .div_assign]
|
||||
&& ((left_sym.kind == .struct_ && right_sym.kind == .struct_)
|
||||
|| left_sym.kind == .alias) {
|
||||
left_name := c.table.type_to_str(left_type_unwrapped)
|
||||
right_name := c.table.type_to_str(right_type_unwrapped)
|
||||
parent_sym := c.table.get_final_type_symbol(left_type_unwrapped)
|
||||
if left_sym.kind == .alias && right_sym.kind != .alias {
|
||||
c.error('mismatched types `$left_name` and `$right_name`', node.pos)
|
||||
}
|
||||
extracted_op := match node.op {
|
||||
.plus_assign { '+' }
|
||||
.minus_assign { '-' }
|
||||
.div_assign { '/' }
|
||||
.mod_assign { '%' }
|
||||
.mult_assign { '*' }
|
||||
else { 'unknown op' }
|
||||
}
|
||||
if left_sym.kind == .struct_ && (left_sym.info as ast.Struct).generic_types.len > 0 {
|
||||
continue
|
||||
}
|
||||
if method := left_sym.find_method(extracted_op) {
|
||||
if method.return_type != left_type_unwrapped {
|
||||
c.error('operator `$extracted_op` must return `$left_name` to be used as an assignment operator',
|
||||
node.pos)
|
||||
}
|
||||
} else {
|
||||
if parent_sym.is_primitive() {
|
||||
c.error('cannot use operator methods on type alias for `$parent_sym.name`',
|
||||
node.pos)
|
||||
}
|
||||
if left_name == right_name {
|
||||
c.error('undefined operation `$left_name` $extracted_op `$right_name`',
|
||||
node.pos)
|
||||
} else {
|
||||
c.error('mismatched types `$left_name` and `$right_name`', node.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !is_blank_ident && !left.is_auto_deref_var() && !right.is_auto_deref_var()
|
||||
&& right_sym.kind != .placeholder && left_sym.kind != .interface_
|
||||
&& !right_type.has_flag(.generic) && !left_type.has_flag(.generic) {
|
||||
// Dual sides check (compatibility check)
|
||||
c.check_expected(right_type_unwrapped, left_type_unwrapped) or {
|
||||
// allow for ptr += 2
|
||||
if left_type_unwrapped.is_ptr() && right_type_unwrapped.is_int()
|
||||
&& node.op in [.plus_assign, .minus_assign] {
|
||||
if !c.inside_unsafe {
|
||||
c.warn('pointer arithmetic is only allowed in `unsafe` blocks',
|
||||
node.pos)
|
||||
}
|
||||
} else {
|
||||
c.error('cannot assign to `$left`: $err.msg', right.position())
|
||||
}
|
||||
}
|
||||
}
|
||||
if left_sym.kind == .interface_ {
|
||||
if c.type_implements(right_type, left_type, right.position()) {
|
||||
if !right_type.is_ptr() && !right_type.is_pointer() && right_sym.kind != .interface_
|
||||
&& !c.inside_unsafe {
|
||||
c.mark_as_referenced(mut &node.right[i], true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// this needs to run after the assign stmt left exprs have been run through checker
|
||||
// so that ident.obj is set
|
||||
// Check `x := &y` and `mut x := <-ch`
|
||||
if right_first is ast.PrefixExpr {
|
||||
right_node := right_first
|
||||
left_first := node.left[0]
|
||||
if left_first is ast.Ident {
|
||||
assigned_var := left_first
|
||||
mut is_shared := false
|
||||
if left_first.info is ast.IdentVar {
|
||||
is_shared = left_first.info.share == .shared_t
|
||||
}
|
||||
old_inside_ref_lit := c.inside_ref_lit
|
||||
c.inside_ref_lit = (c.inside_ref_lit || right_node.op == .amp || is_shared)
|
||||
c.expr(right_node.right)
|
||||
c.inside_ref_lit = old_inside_ref_lit
|
||||
if right_node.op == .amp {
|
||||
if right_node.right is ast.Ident {
|
||||
if right_node.right.obj is ast.Var {
|
||||
v := right_node.right.obj
|
||||
right_type0 = v.typ
|
||||
if !v.is_mut && assigned_var.is_mut && !c.inside_unsafe {
|
||||
c.error('`$right_node.right.name` is immutable, cannot have a mutable reference to it',
|
||||
right_node.pos)
|
||||
}
|
||||
} else if right_node.right.obj is ast.ConstField {
|
||||
if assigned_var.is_mut && !c.inside_unsafe {
|
||||
c.error('`$right_node.right.name` is immutable, cannot have a mutable reference to it',
|
||||
right_node.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if right_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.name`',
|
||||
right_node.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.left_types.len != node.left.len {
|
||||
c.error('assign statement left type number mismatch', node.pos)
|
||||
}
|
||||
}
|
|
@ -12,6 +12,21 @@ pub fn (mut c Checker) check_types(got ast.Type, expected ast.Type) bool {
|
|||
if got == expected {
|
||||
return true
|
||||
}
|
||||
if c.pref.translated {
|
||||
if expected == ast.byteptr_type {
|
||||
return true
|
||||
}
|
||||
if expected.is_any_kind_of_pointer() { //&& !got.is_any_kind_of_pointer() {
|
||||
// if true {
|
||||
// return true
|
||||
//}
|
||||
deref := expected.deref()
|
||||
got_sym := c.table.get_type_symbol(got)
|
||||
if deref.is_number() && (got_sym.is_number() || got_sym.kind == .enum_) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
got_is_ptr := got.is_ptr()
|
||||
exp_is_ptr := expected.is_ptr()
|
||||
if got_is_ptr && exp_is_ptr {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,570 @@
|
|||
// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
|
||||
module checker
|
||||
|
||||
import v.ast
|
||||
import v.pref
|
||||
import v.token
|
||||
import v.util
|
||||
import v.pkgconfig
|
||||
|
||||
fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type {
|
||||
sym := c.table.get_type_symbol(c.unwrap_generic(c.expr(node.left)))
|
||||
node.sym = *sym
|
||||
if node.is_env {
|
||||
env_value := util.resolve_env_value("\$env('$node.args_var')", false) or {
|
||||
c.error(err.msg, node.env_pos)
|
||||
return ast.string_type
|
||||
}
|
||||
node.env_value = env_value
|
||||
return ast.string_type
|
||||
}
|
||||
if node.is_embed {
|
||||
// c.file.embedded_files << node.embed_file
|
||||
if node.embed_file.compression_type !in valid_comptime_compression_types {
|
||||
supported := valid_comptime_compression_types.map('.$it').join(', ')
|
||||
c.error('not supported compression type: .${node.embed_file.compression_type}. supported: $supported',
|
||||
node.pos)
|
||||
}
|
||||
return c.table.find_type_idx('v.embed_file.EmbedFileData')
|
||||
}
|
||||
if node.is_vweb {
|
||||
// TODO assoc parser bug
|
||||
pref_ := *c.pref
|
||||
pref2 := &pref.Preferences{
|
||||
...pref_
|
||||
is_vweb: true
|
||||
}
|
||||
mut c2 := new_checker(c.table, pref2)
|
||||
c2.check(node.vweb_tmpl)
|
||||
mut i := 0 // tmp counter var for skipping first three tmpl vars
|
||||
for k, _ in c2.file.scope.children[0].objects {
|
||||
if i < 2 {
|
||||
// Skip first three because they are tmpl vars see vlib/vweb/tmpl/tmpl.v
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if k in c.fn_scope.objects && unsafe { c.fn_scope.objects[k] } is ast.Var {
|
||||
mut vsc := unsafe { c.fn_scope.objects[k] } as ast.Var
|
||||
vsc.is_used = true
|
||||
c.fn_scope.objects[k] = vsc
|
||||
}
|
||||
}
|
||||
c.warnings << c2.warnings
|
||||
c.errors << c2.errors
|
||||
c.notices << c2.notices
|
||||
c.nr_warnings += c2.nr_warnings
|
||||
c.nr_errors += c2.nr_errors
|
||||
c.nr_notices += c2.nr_notices
|
||||
}
|
||||
if node.method_name == 'html' {
|
||||
rtyp := c.table.find_type_idx('vweb.Result')
|
||||
node.result_type = rtyp
|
||||
return rtyp
|
||||
}
|
||||
if node.method_name == 'method' {
|
||||
for i, arg in node.args {
|
||||
// check each arg expression
|
||||
node.args[i].typ = c.expr(arg.expr)
|
||||
}
|
||||
// assume string for now
|
||||
return ast.string_type
|
||||
}
|
||||
if node.is_vweb {
|
||||
return ast.string_type
|
||||
}
|
||||
// s.$my_str()
|
||||
v := node.scope.find_var(node.method_name) or {
|
||||
c.error('unknown identifier `$node.method_name`', node.method_pos)
|
||||
return ast.void_type
|
||||
}
|
||||
if v.typ != ast.string_type {
|
||||
s := c.expected_msg(v.typ, ast.string_type)
|
||||
c.error('invalid string method call: $s', node.method_pos)
|
||||
return ast.void_type
|
||||
}
|
||||
// note: we should use a compile-time evaluation function rather than handle here
|
||||
// mut variables will not work after init
|
||||
mut method_name := ''
|
||||
if v.expr is ast.StringLiteral {
|
||||
method_name = v.expr.val
|
||||
} else {
|
||||
c.error('todo: not a string literal', node.method_pos)
|
||||
}
|
||||
f := node.sym.find_method(method_name) or {
|
||||
c.error('could not find method `$method_name`', node.method_pos)
|
||||
return ast.void_type
|
||||
}
|
||||
// println(f.name + ' ' + c.table.type_to_str(f.return_type))
|
||||
node.result_type = f.return_type
|
||||
return f.return_type
|
||||
}
|
||||
|
||||
// comptime const eval
|
||||
fn (mut c Checker) eval_comptime_const_expr(expr ast.Expr, nlevel int) ?ast.ComptTimeConstValue {
|
||||
if nlevel > 100 {
|
||||
// protect against a too deep comptime eval recursion
|
||||
return none
|
||||
}
|
||||
match expr {
|
||||
ast.ParExpr {
|
||||
return c.eval_comptime_const_expr(expr.expr, nlevel + 1)
|
||||
}
|
||||
// ast.EnumVal {
|
||||
// c.note('>>>>>>>> expr: $expr', expr.pos)
|
||||
// return expr.val.i64()
|
||||
// }
|
||||
ast.SizeOf {
|
||||
xtype := expr.typ
|
||||
if xtype.is_real_pointer() {
|
||||
if c.pref.m64 {
|
||||
return 8 // 64bit platform
|
||||
}
|
||||
return 4 // 32bit platform
|
||||
}
|
||||
if int(xtype) == xtype.idx() {
|
||||
match xtype {
|
||||
ast.char_type { return 1 }
|
||||
ast.i8_type { return 1 }
|
||||
ast.i16_type { return 2 }
|
||||
ast.int_type { return 4 }
|
||||
ast.i64_type { return 8 }
|
||||
//
|
||||
ast.byte_type { return 1 }
|
||||
ast.u8_type { return 1 }
|
||||
ast.u16_type { return 2 }
|
||||
ast.u32_type { return 4 }
|
||||
ast.u64_type { return 8 }
|
||||
else {}
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
ast.FloatLiteral {
|
||||
x := expr.val.f64()
|
||||
return x
|
||||
}
|
||||
ast.IntegerLiteral {
|
||||
x := expr.val.u64()
|
||||
if x > 9223372036854775807 {
|
||||
return x
|
||||
}
|
||||
return expr.val.i64()
|
||||
}
|
||||
ast.StringLiteral {
|
||||
return util.smart_quote(expr.val, expr.is_raw)
|
||||
}
|
||||
ast.CharLiteral {
|
||||
runes := expr.val.runes()
|
||||
if runes.len > 0 {
|
||||
return runes[0]
|
||||
}
|
||||
return none
|
||||
}
|
||||
ast.Ident {
|
||||
if expr.obj is ast.ConstField {
|
||||
// an existing constant?
|
||||
return c.eval_comptime_const_expr(expr.obj.expr, nlevel + 1)
|
||||
}
|
||||
}
|
||||
ast.CastExpr {
|
||||
cast_expr_value := c.eval_comptime_const_expr(expr.expr, nlevel + 1) or { return none }
|
||||
if expr.typ == ast.i8_type {
|
||||
return cast_expr_value.i8() or { return none }
|
||||
}
|
||||
if expr.typ == ast.i16_type {
|
||||
return cast_expr_value.i16() or { return none }
|
||||
}
|
||||
if expr.typ == ast.int_type {
|
||||
return cast_expr_value.int() or { return none }
|
||||
}
|
||||
if expr.typ == ast.i64_type {
|
||||
return cast_expr_value.i64() or { return none }
|
||||
}
|
||||
//
|
||||
if expr.typ == ast.byte_type {
|
||||
return cast_expr_value.byte() or { return none }
|
||||
}
|
||||
if expr.typ == ast.u16_type {
|
||||
return cast_expr_value.u16() or { return none }
|
||||
}
|
||||
if expr.typ == ast.u32_type {
|
||||
return cast_expr_value.u32() or { return none }
|
||||
}
|
||||
if expr.typ == ast.u64_type {
|
||||
return cast_expr_value.u64() or { return none }
|
||||
}
|
||||
//
|
||||
if expr.typ == ast.f32_type {
|
||||
return cast_expr_value.f32() or { return none }
|
||||
}
|
||||
if expr.typ == ast.f64_type {
|
||||
return cast_expr_value.f64() or { return none }
|
||||
}
|
||||
}
|
||||
ast.InfixExpr {
|
||||
left := c.eval_comptime_const_expr(expr.left, nlevel + 1) ?
|
||||
right := c.eval_comptime_const_expr(expr.right, nlevel + 1) ?
|
||||
if left is string && right is string {
|
||||
match expr.op {
|
||||
.plus {
|
||||
return left + right
|
||||
}
|
||||
else {
|
||||
return none
|
||||
}
|
||||
}
|
||||
} else if left is u64 && right is i64 {
|
||||
match expr.op {
|
||||
.plus { return i64(left) + i64(right) }
|
||||
.minus { return i64(left) - i64(right) }
|
||||
.mul { return i64(left) * i64(right) }
|
||||
.div { return i64(left) / i64(right) }
|
||||
.mod { return i64(left) % i64(right) }
|
||||
.xor { return i64(left) ^ i64(right) }
|
||||
.pipe { return i64(left) | i64(right) }
|
||||
.amp { return i64(left) & i64(right) }
|
||||
.left_shift { return i64(u64(left) << i64(right)) }
|
||||
.right_shift { return i64(u64(left) >> i64(right)) }
|
||||
.unsigned_right_shift { return i64(u64(left) >>> i64(right)) }
|
||||
else { return none }
|
||||
}
|
||||
} else if left is i64 && right is u64 {
|
||||
match expr.op {
|
||||
.plus { return i64(left) + i64(right) }
|
||||
.minus { return i64(left) - i64(right) }
|
||||
.mul { return i64(left) * i64(right) }
|
||||
.div { return i64(left) / i64(right) }
|
||||
.mod { return i64(left) % i64(right) }
|
||||
.xor { return i64(left) ^ i64(right) }
|
||||
.pipe { return i64(left) | i64(right) }
|
||||
.amp { return i64(left) & i64(right) }
|
||||
.left_shift { return i64(u64(left) << i64(right)) }
|
||||
.right_shift { return i64(u64(left) >> i64(right)) }
|
||||
.unsigned_right_shift { return i64(u64(left) >>> i64(right)) }
|
||||
else { return none }
|
||||
}
|
||||
} else if left is u64 && right is u64 {
|
||||
match expr.op {
|
||||
.plus { return left + right }
|
||||
.minus { return left - right }
|
||||
.mul { return left * right }
|
||||
.div { return left / right }
|
||||
.mod { return left % right }
|
||||
.xor { return left ^ right }
|
||||
.pipe { return left | right }
|
||||
.amp { return left & right }
|
||||
.left_shift { return left << right }
|
||||
.right_shift { return left >> right }
|
||||
.unsigned_right_shift { return left >>> right }
|
||||
else { return none }
|
||||
}
|
||||
} else if left is i64 && right is i64 {
|
||||
match expr.op {
|
||||
.plus { return left + right }
|
||||
.minus { return left - right }
|
||||
.mul { return left * right }
|
||||
.div { return left / right }
|
||||
.mod { return left % right }
|
||||
.xor { return left ^ right }
|
||||
.pipe { return left | right }
|
||||
.amp { return left & right }
|
||||
.left_shift { return i64(u64(left) << right) }
|
||||
.right_shift { return i64(u64(left) >> right) }
|
||||
.unsigned_right_shift { return i64(u64(left) >>> right) }
|
||||
else { return none }
|
||||
}
|
||||
} else if left is byte && right is byte {
|
||||
match expr.op {
|
||||
.plus { return left + right }
|
||||
.minus { return left - right }
|
||||
.mul { return left * right }
|
||||
.div { return left / right }
|
||||
.mod { return left % right }
|
||||
.xor { return left ^ right }
|
||||
.pipe { return left | right }
|
||||
.amp { return left & right }
|
||||
.left_shift { return left << right }
|
||||
.right_shift { return left >> right }
|
||||
.unsigned_right_shift { return left >>> right }
|
||||
else { return none }
|
||||
}
|
||||
}
|
||||
}
|
||||
// ast.ArrayInit {}
|
||||
// ast.PrefixExpr {
|
||||
// c.note('prefixexpr: $expr', expr.pos)
|
||||
// }
|
||||
else {
|
||||
// eprintln('>>> nlevel: $nlevel | another $expr.type_name() | $expr ')
|
||||
return none
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn (mut c Checker) verify_vweb_params_for_method(node ast.Fn) (bool, int, int) {
|
||||
margs := node.params.len - 1 // first arg is the receiver/this
|
||||
if node.attrs.len == 0 {
|
||||
// allow non custom routed methods, with 1:1 mapping
|
||||
return true, -1, margs
|
||||
}
|
||||
if node.params.len > 1 {
|
||||
for param in node.params[1..] {
|
||||
param_sym := c.table.get_final_type_symbol(param.typ)
|
||||
if !(param_sym.is_string() || param_sym.is_number() || param_sym.is_float()
|
||||
|| param_sym.kind == .bool) {
|
||||
c.error('invalid type `$param_sym.name` for parameter `$param.name` in vweb app method `$node.name`',
|
||||
param.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
mut route_attributes := 0
|
||||
for a in node.attrs {
|
||||
if a.name.starts_with('/') {
|
||||
route_attributes += a.name.count(':')
|
||||
}
|
||||
}
|
||||
return route_attributes == margs, route_attributes, margs
|
||||
}
|
||||
|
||||
fn (mut c Checker) verify_all_vweb_routes() {
|
||||
if c.vweb_gen_types.len == 0 {
|
||||
return
|
||||
}
|
||||
c.table.used_vweb_types = c.vweb_gen_types
|
||||
typ_vweb_result := c.table.find_type_idx('vweb.Result')
|
||||
old_file := c.file
|
||||
for vgt in c.vweb_gen_types {
|
||||
sym_app := c.table.get_type_symbol(vgt)
|
||||
for m in sym_app.methods {
|
||||
if m.return_type == typ_vweb_result {
|
||||
is_ok, nroute_attributes, nargs := c.verify_vweb_params_for_method(m)
|
||||
if !is_ok {
|
||||
f := &ast.FnDecl(m.source_fn)
|
||||
if isnil(f) {
|
||||
continue
|
||||
}
|
||||
if f.return_type == typ_vweb_result && f.receiver.typ == m.params[0].typ
|
||||
&& f.name == m.name && !f.attrs.contains('post') {
|
||||
c.change_current_file(f.source_file) // setup of file path for the warning
|
||||
c.warn('mismatched parameters count between vweb method `${sym_app.name}.$m.name` ($nargs) and route attribute $m.attrs ($nroute_attributes)',
|
||||
f.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
c.change_current_file(old_file)
|
||||
}
|
||||
|
||||
fn (mut c Checker) evaluate_once_comptime_if_attribute(mut node ast.Attr) bool {
|
||||
if node.ct_evaled {
|
||||
return node.ct_skip
|
||||
}
|
||||
if node.ct_expr is ast.Ident {
|
||||
if node.ct_opt {
|
||||
if node.ct_expr.name in valid_comptime_not_user_defined {
|
||||
c.error('optional `[if expression ?]` tags, can be used only for user defined identifiers',
|
||||
node.pos)
|
||||
node.ct_skip = true
|
||||
} else {
|
||||
node.ct_skip = node.ct_expr.name !in c.pref.compile_defines
|
||||
}
|
||||
node.ct_evaled = true
|
||||
return node.ct_skip
|
||||
} else {
|
||||
if node.ct_expr.name !in valid_comptime_not_user_defined {
|
||||
c.note('`[if $node.ct_expr.name]` is deprecated. Use `[if $node.ct_expr.name ?]` instead',
|
||||
node.pos)
|
||||
node.ct_skip = node.ct_expr.name !in c.pref.compile_defines
|
||||
node.ct_evaled = true
|
||||
return node.ct_skip
|
||||
} else {
|
||||
if node.ct_expr.name in c.pref.compile_defines {
|
||||
// explicitly allow custom user overrides with `-d linux` for example, for easier testing:
|
||||
node.ct_skip = false
|
||||
node.ct_evaled = true
|
||||
return node.ct_skip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
c.inside_ct_attr = true
|
||||
node.ct_skip = c.comptime_if_branch(node.ct_expr, node.pos)
|
||||
c.inside_ct_attr = false
|
||||
node.ct_evaled = true
|
||||
return node.ct_skip
|
||||
}
|
||||
|
||||
// comptime_if_branch checks the condition of a compile-time `if` branch. It returns `true`
|
||||
// if that branch's contents should be skipped (targets a different os for example)
|
||||
fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Position) bool {
|
||||
// TODO: better error messages here
|
||||
match cond {
|
||||
ast.BoolLiteral {
|
||||
return !cond.val
|
||||
}
|
||||
ast.ParExpr {
|
||||
return c.comptime_if_branch(cond.expr, pos)
|
||||
}
|
||||
ast.PrefixExpr {
|
||||
if cond.op != .not {
|
||||
c.error('invalid `\$if` condition', cond.pos)
|
||||
}
|
||||
return !c.comptime_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 {
|
||||
return cond.expr.name !in c.pref.compile_defines_all
|
||||
} else {
|
||||
c.error('invalid `\$if` condition', cond.pos)
|
||||
}
|
||||
}
|
||||
ast.InfixExpr {
|
||||
match cond.op {
|
||||
.and {
|
||||
l := c.comptime_if_branch(cond.left, cond.pos)
|
||||
r := c.comptime_if_branch(cond.right, cond.pos)
|
||||
return l || r // skip (return true) if at least one should be skipped
|
||||
}
|
||||
.logical_or {
|
||||
l := c.comptime_if_branch(cond.left, cond.pos)
|
||||
r := c.comptime_if_branch(cond.right, cond.pos)
|
||||
return l && r // skip (return true) only if both should be skipped
|
||||
}
|
||||
.key_is, .not_is {
|
||||
if cond.left is ast.TypeNode && cond.right is ast.TypeNode {
|
||||
// `$if Foo is Interface {`
|
||||
sym := c.table.get_type_symbol(cond.right.typ)
|
||||
if sym.kind != .interface_ {
|
||||
c.expr(cond.left)
|
||||
// c.error('`$sym.name` is not an interface', cond.right.position())
|
||||
}
|
||||
return false
|
||||
} else if cond.left in [ast.SelectorExpr, ast.TypeNode] {
|
||||
// `$if method.@type is string`
|
||||
c.expr(cond.left)
|
||||
return false
|
||||
} else {
|
||||
c.error('invalid `\$if` condition: expected a type or a selector expression or an interface check',
|
||||
cond.left.position())
|
||||
}
|
||||
}
|
||||
.eq, .ne {
|
||||
if cond.left is ast.SelectorExpr && cond.right is ast.IntegerLiteral {
|
||||
// $if method.args.len == 1
|
||||
} else if cond.left is ast.Ident {
|
||||
// $if version == 2
|
||||
left_type := c.expr(cond.left)
|
||||
right_type := c.expr(cond.right)
|
||||
expr := c.find_definition(cond.left) or {
|
||||
c.error(err.msg, cond.left.pos)
|
||||
return false
|
||||
}
|
||||
if !c.check_types(right_type, left_type) {
|
||||
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`',
|
||||
cond.pos)
|
||||
}
|
||||
// :)
|
||||
// until `v.eval` is stable, I can't think of a better way to do this
|
||||
different := expr.str() != cond.right.str()
|
||||
return if cond.op == .eq { different } else { !different }
|
||||
} else {
|
||||
c.error('invalid `\$if` condition: ${cond.left.type_name()}1',
|
||||
cond.pos)
|
||||
}
|
||||
}
|
||||
else {
|
||||
c.error('invalid `\$if` condition', cond.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
ast.Ident {
|
||||
cname := cond.name
|
||||
if cname in valid_comptime_if_os {
|
||||
mut is_os_target_different := false
|
||||
if !c.pref.output_cross_c {
|
||||
target_os := c.pref.os.str().to_lower()
|
||||
is_os_target_different = cname != target_os
|
||||
}
|
||||
return is_os_target_different
|
||||
} else if cname in valid_comptime_if_compilers {
|
||||
return pref.cc_from_string(cname) != c.pref.ccompiler_type
|
||||
} else if cname in valid_comptime_if_platforms {
|
||||
if cname == 'aarch64' {
|
||||
c.note('use `arm64` instead of `aarch64`', pos)
|
||||
}
|
||||
match cname {
|
||||
'amd64' { return c.pref.arch != .amd64 }
|
||||
'i386' { return c.pref.arch != .i386 }
|
||||
'aarch64' { return c.pref.arch != .arm64 }
|
||||
'arm64' { return c.pref.arch != .arm64 }
|
||||
'arm32' { return c.pref.arch != .arm32 }
|
||||
'rv64' { return c.pref.arch != .rv64 }
|
||||
'rv32' { return c.pref.arch != .rv32 }
|
||||
else { return false }
|
||||
}
|
||||
} else if cname in valid_comptime_if_cpu_features {
|
||||
return false
|
||||
} else if cname in valid_comptime_if_other {
|
||||
match cname {
|
||||
'js' { return !c.pref.backend.is_js() }
|
||||
'debug' { return !c.pref.is_debug }
|
||||
'prod' { return !c.pref.is_prod }
|
||||
'test' { return !c.pref.is_test }
|
||||
'glibc' { return false } // TODO
|
||||
'threads' { return c.table.gostmts == 0 }
|
||||
'prealloc' { return !c.pref.prealloc }
|
||||
'no_bounds_checking' { return cname !in c.pref.compile_defines_all }
|
||||
'freestanding' { return !c.pref.is_bare || c.pref.output_cross_c }
|
||||
'interpreter' { c.pref.backend != .interpret }
|
||||
else { return false }
|
||||
}
|
||||
} else if cname !in c.pref.compile_defines_all {
|
||||
if cname == 'linux_or_macos' {
|
||||
c.error('linux_or_macos is deprecated, use `\$if linux || macos {` instead',
|
||||
cond.pos)
|
||||
return false
|
||||
}
|
||||
// `$if some_var {}`, or `[if user_defined_tag] fn abc(){}`
|
||||
typ := c.expr(cond)
|
||||
if cond.obj !is ast.Var && cond.obj !is ast.ConstField
|
||||
&& cond.obj !is ast.GlobalField {
|
||||
if !c.inside_ct_attr {
|
||||
c.error('unknown var: `$cname`', pos)
|
||||
}
|
||||
return false
|
||||
}
|
||||
expr := c.find_obj_definition(cond.obj) or {
|
||||
c.error(err.msg, cond.pos)
|
||||
return false
|
||||
}
|
||||
if !c.check_types(typ, ast.bool_type) {
|
||||
type_name := c.table.type_to_str(typ)
|
||||
c.error('non-bool type `$type_name` used as \$if condition', cond.pos)
|
||||
}
|
||||
// :)
|
||||
// until `v.eval` is stable, I can't think of a better way to do this
|
||||
return !(expr as ast.BoolLiteral).val
|
||||
}
|
||||
}
|
||||
ast.ComptimeCall {
|
||||
if cond.is_pkgconfig {
|
||||
mut m := pkgconfig.main([cond.args_var]) or {
|
||||
c.error(err.msg, cond.pos)
|
||||
return true
|
||||
}
|
||||
m.run() or { return true }
|
||||
}
|
||||
}
|
||||
else {
|
||||
c.error('invalid `\$if` condition', pos)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
|
||||
module checker
|
||||
|
||||
import v.ast
|
||||
import v.token
|
||||
|
||||
fn (mut c Checker) sql_expr(mut node ast.SqlExpr) ast.Type {
|
||||
c.inside_sql = true
|
||||
defer {
|
||||
c.inside_sql = false
|
||||
}
|
||||
sym := c.table.get_type_symbol(node.table_expr.typ)
|
||||
c.ensure_type_exists(node.table_expr.typ, node.pos) or { return ast.void_type }
|
||||
c.cur_orm_ts = *sym
|
||||
if sym.info !is ast.Struct {
|
||||
c.error('The table symbol `$sym.name` has to be a struct', node.table_expr.pos)
|
||||
return ast.void_type
|
||||
}
|
||||
info := sym.info as ast.Struct
|
||||
fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, sym.name)
|
||||
mut sub_structs := map[int]ast.SqlExpr{}
|
||||
for f in fields.filter((c.table.type_symbols[int(it.typ)].kind == .struct_
|
||||
|| (c.table.get_type_symbol(it.typ).kind == .array
|
||||
&& c.table.get_type_symbol(c.table.get_type_symbol(it.typ).array_info().elem_type).kind == .struct_))
|
||||
&& c.table.get_type_name(it.typ) != 'time.Time') {
|
||||
typ := if c.table.get_type_symbol(f.typ).kind == .struct_ {
|
||||
f.typ
|
||||
} else if c.table.get_type_symbol(f.typ).kind == .array {
|
||||
c.table.get_type_symbol(f.typ).array_info().elem_type
|
||||
} else {
|
||||
ast.Type(0)
|
||||
}
|
||||
mut n := ast.SqlExpr{
|
||||
pos: node.pos
|
||||
has_where: true
|
||||
typ: typ
|
||||
db_expr: node.db_expr
|
||||
table_expr: ast.TypeNode{
|
||||
pos: node.table_expr.pos
|
||||
typ: typ
|
||||
}
|
||||
}
|
||||
tmp_inside_sql := c.inside_sql
|
||||
c.sql_expr(mut n)
|
||||
c.inside_sql = tmp_inside_sql
|
||||
n.where_expr = ast.InfixExpr{
|
||||
op: .eq
|
||||
pos: n.pos
|
||||
left: ast.Ident{
|
||||
language: .v
|
||||
tok_kind: .eq
|
||||
scope: c.fn_scope
|
||||
obj: ast.Var{}
|
||||
mod: 'main'
|
||||
name: 'id'
|
||||
is_mut: false
|
||||
kind: .unresolved
|
||||
info: ast.IdentVar{}
|
||||
}
|
||||
right: ast.Ident{
|
||||
language: .c
|
||||
mod: 'main'
|
||||
tok_kind: .eq
|
||||
obj: ast.Var{}
|
||||
is_mut: false
|
||||
scope: c.fn_scope
|
||||
info: ast.IdentVar{
|
||||
typ: ast.int_type
|
||||
}
|
||||
}
|
||||
left_type: ast.int_type
|
||||
right_type: ast.int_type
|
||||
auto_locked: ''
|
||||
or_block: ast.OrExpr{}
|
||||
}
|
||||
|
||||
sub_structs[int(typ)] = n
|
||||
}
|
||||
node.fields = fields
|
||||
node.sub_structs = sub_structs.move()
|
||||
if node.has_where {
|
||||
c.expr(node.where_expr)
|
||||
}
|
||||
if node.has_offset {
|
||||
c.expr(node.offset_expr)
|
||||
}
|
||||
if node.has_limit {
|
||||
c.expr(node.limit_expr)
|
||||
}
|
||||
if node.has_order {
|
||||
c.expr(node.order_expr)
|
||||
}
|
||||
c.expr(node.db_expr)
|
||||
return node.typ
|
||||
}
|
||||
|
||||
fn (mut c Checker) sql_stmt(mut node ast.SqlStmt) ast.Type {
|
||||
c.expr(node.db_expr)
|
||||
mut typ := ast.void_type
|
||||
for mut line in node.lines {
|
||||
a := c.sql_stmt_line(mut line)
|
||||
if a != ast.void_type {
|
||||
typ = a
|
||||
}
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
fn (mut c Checker) sql_stmt_line(mut node ast.SqlStmtLine) ast.Type {
|
||||
c.inside_sql = true
|
||||
defer {
|
||||
c.inside_sql = false
|
||||
}
|
||||
c.ensure_type_exists(node.table_expr.typ, node.pos) or { return ast.void_type }
|
||||
table_sym := c.table.get_type_symbol(node.table_expr.typ)
|
||||
c.cur_orm_ts = *table_sym
|
||||
if table_sym.info !is ast.Struct {
|
||||
c.error('unknown type `$table_sym.name`', node.pos)
|
||||
return ast.void_type
|
||||
}
|
||||
info := table_sym.info as ast.Struct
|
||||
fields := c.fetch_and_verify_orm_fields(info, node.table_expr.pos, table_sym.name)
|
||||
mut sub_structs := map[int]ast.SqlStmtLine{}
|
||||
for f in fields.filter(((c.table.type_symbols[int(it.typ)].kind == .struct_)
|
||||
|| (c.table.get_type_symbol(it.typ).kind == .array
|
||||
&& c.table.get_type_symbol(c.table.get_type_symbol(it.typ).array_info().elem_type).kind == .struct_))
|
||||
&& c.table.get_type_name(it.typ) != 'time.Time') {
|
||||
typ := if c.table.get_type_symbol(f.typ).kind == .struct_ {
|
||||
f.typ
|
||||
} else if c.table.get_type_symbol(f.typ).kind == .array {
|
||||
c.table.get_type_symbol(f.typ).array_info().elem_type
|
||||
} else {
|
||||
ast.Type(0)
|
||||
}
|
||||
mut object_var_name := '${node.object_var_name}.$f.name'
|
||||
if typ != f.typ {
|
||||
object_var_name = node.object_var_name
|
||||
}
|
||||
mut n := ast.SqlStmtLine{
|
||||
pos: node.pos
|
||||
kind: node.kind
|
||||
table_expr: ast.TypeNode{
|
||||
pos: node.table_expr.pos
|
||||
typ: typ
|
||||
}
|
||||
object_var_name: object_var_name
|
||||
}
|
||||
tmp_inside_sql := c.inside_sql
|
||||
c.sql_stmt_line(mut n)
|
||||
c.inside_sql = tmp_inside_sql
|
||||
sub_structs[typ] = n
|
||||
}
|
||||
node.fields = fields
|
||||
node.sub_structs = sub_structs.move()
|
||||
for i, column in node.updated_columns {
|
||||
x := node.fields.filter(it.name == column)
|
||||
if x.len == 0 {
|
||||
c.error('type `$table_sym.name` has no field named `$column`', node.pos)
|
||||
continue
|
||||
}
|
||||
field := x[0]
|
||||
node.updated_columns[i] = c.fetch_field_name(field)
|
||||
}
|
||||
if node.kind == .update {
|
||||
for expr in node.update_exprs {
|
||||
c.expr(expr)
|
||||
}
|
||||
}
|
||||
if node.where_expr !is ast.EmptyExpr {
|
||||
c.expr(node.where_expr)
|
||||
}
|
||||
|
||||
return ast.void_type
|
||||
}
|
||||
|
||||
fn (mut c Checker) fetch_and_verify_orm_fields(info ast.Struct, pos token.Position, table_name string) []ast.StructField {
|
||||
fields := info.fields.filter(
|
||||
(it.typ in [ast.string_type, ast.bool_type] || int(it.typ) in ast.number_type_idxs
|
||||
|| c.table.type_symbols[int(it.typ)].kind == .struct_
|
||||
|| (c.table.get_type_symbol(it.typ).kind == .array
|
||||
&& c.table.get_type_symbol(c.table.get_type_symbol(it.typ).array_info().elem_type).kind == .struct_))
|
||||
&& !it.attrs.contains('skip'))
|
||||
if fields.len == 0 {
|
||||
c.error('V orm: select: empty fields in `$table_name`', pos)
|
||||
return []ast.StructField{}
|
||||
}
|
||||
if fields[0].name != 'id' {
|
||||
c.error('V orm: `id int` must be the first field in `$table_name`', pos)
|
||||
}
|
||||
return fields
|
||||
}
|
Loading…
Reference in New Issue