checker: split up checker.v: assign.v, orm.v, comptime.v; c2v fixes

pull/12793/head
Alexander Medvednikov 2021-12-11 10:23:58 +03:00
parent ed4ecae57d
commit eaf0f9b4c1
5 changed files with 1374 additions and 1336 deletions

View File

@ -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)
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}