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 {
|
if got == expected {
|
||||||
return true
|
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()
|
got_is_ptr := got.is_ptr()
|
||||||
exp_is_ptr := expected.is_ptr()
|
exp_is_ptr := expected.is_ptr()
|
||||||
if got_is_ptr && exp_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