mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
checker: minor cleanup of the fns classification (#12977)
This commit is contained in:
parent
a2eb90ee4e
commit
34e175a343
@ -1600,85 +1600,6 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) {
|
||||
return to_lock, pos
|
||||
}
|
||||
|
||||
pub fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type {
|
||||
// First check everything that applies to both fns and methods
|
||||
// TODO merge logic from method_call and fn_call
|
||||
/*
|
||||
for i, call_arg in node.args {
|
||||
if call_arg.is_mut {
|
||||
c.fail_if_immutable(call_arg.expr)
|
||||
if !arg.is_mut {
|
||||
tok := call_arg.share.str()
|
||||
c.error('`$node.name` parameter `$arg.name` is not `$tok`, `$tok` is not needed`',
|
||||
call_arg.expr.position())
|
||||
} else if arg.typ.share() != call_arg.share {
|
||||
c.error('wrong shared type', call_arg.expr.position())
|
||||
}
|
||||
} else {
|
||||
if arg.is_mut && (!call_arg.is_mut || arg.typ.share() != call_arg.share) {
|
||||
tok := call_arg.share.str()
|
||||
c.error('`$node.name` parameter `$arg.name` is `$tok`, you need to provide `$tok` e.g. `$tok arg${i+1}`',
|
||||
call_arg.expr.position())
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
// Now call `method_call` or `fn_call` for specific checks.
|
||||
old_inside_fn_arg := c.inside_fn_arg
|
||||
c.inside_fn_arg = true
|
||||
mut continue_check := true
|
||||
typ := if node.is_method {
|
||||
c.method_call(mut node)
|
||||
} else {
|
||||
c.fn_call(mut node, mut continue_check)
|
||||
}
|
||||
if !continue_check {
|
||||
return ast.void_type
|
||||
}
|
||||
c.inside_fn_arg = old_inside_fn_arg
|
||||
// autofree: mark args that have to be freed (after saving them in tmp exprs)
|
||||
free_tmp_arg_vars := c.pref.autofree && !c.is_builtin_mod && node.args.len > 0
|
||||
&& !node.args[0].typ.has_flag(.optional)
|
||||
if free_tmp_arg_vars && !c.inside_const {
|
||||
for i, arg in node.args {
|
||||
if arg.typ != ast.string_type {
|
||||
continue
|
||||
}
|
||||
if arg.expr in [ast.Ident, ast.StringLiteral, ast.SelectorExpr] {
|
||||
// Simple expressions like variables, string literals, selector expressions
|
||||
// (`x.field`) can't result in allocations and don't need to be assigned to
|
||||
// temporary vars.
|
||||
// Only expressions like `str + 'b'` need to be freed.
|
||||
continue
|
||||
}
|
||||
node.args[i].is_tmp_autofree = true
|
||||
}
|
||||
// TODO copy pasta from above
|
||||
if node.receiver_type == ast.string_type
|
||||
&& node.left !in [ast.Ident, ast.StringLiteral, ast.SelectorExpr] {
|
||||
node.free_receiver = true
|
||||
}
|
||||
}
|
||||
c.expected_or_type = node.return_type.clear_flag(.optional)
|
||||
c.stmts_ending_with_expression(node.or_block.stmts)
|
||||
c.expected_or_type = ast.void_type
|
||||
if node.or_block.kind == .propagate && !c.table.cur_fn.return_type.has_flag(.optional)
|
||||
&& !c.inside_const {
|
||||
if !c.table.cur_fn.is_main {
|
||||
c.error('to propagate the optional call, `$c.table.cur_fn.name` must return an optional',
|
||||
node.or_block.pos)
|
||||
}
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
fn semicolonize(main string, details string) string {
|
||||
if details == '' {
|
||||
return main
|
||||
}
|
||||
return '$main; $details'
|
||||
}
|
||||
|
||||
fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos token.Position) bool {
|
||||
$if debug_interface_type_implements ? {
|
||||
eprintln('> type_implements typ: $typ.debug() (`${c.table.type_to_str(typ)}`) | inter_typ: $interface_type.debug() (`${c.table.type_to_str(interface_type)}`)')
|
||||
@ -2597,19 +2518,6 @@ fn (mut c Checker) for_c_stmt(node ast.ForCStmt) {
|
||||
c.in_for_count--
|
||||
}
|
||||
|
||||
fn (mut c Checker) comptime_for(node ast.ComptimeFor) {
|
||||
typ := c.unwrap_generic(node.typ)
|
||||
sym := c.table.sym(typ)
|
||||
if sym.kind == .placeholder || typ.has_flag(.generic) {
|
||||
c.error('unknown type `$sym.name`', node.typ_pos)
|
||||
}
|
||||
if node.kind == .fields {
|
||||
c.comptime_fields_type[node.val_var] = node.typ
|
||||
c.comptime_fields_default_type = node.typ
|
||||
}
|
||||
c.stmts(node.stmts)
|
||||
}
|
||||
|
||||
fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) {
|
||||
c.in_for_count++
|
||||
prev_loop_label := c.loop_label
|
||||
@ -4822,36 +4730,6 @@ fn (mut c Checker) fetch_field_name(field ast.StructField) string {
|
||||
return name
|
||||
}
|
||||
|
||||
fn (mut c Checker) post_process_generic_fns() {
|
||||
// Loop thru each generic function concrete type.
|
||||
// Check each specific fn instantiation.
|
||||
for i in 0 .. c.file.generic_fns.len {
|
||||
mut node := c.file.generic_fns[i]
|
||||
c.mod = node.mod
|
||||
gtypes := c.table.fn_generic_types[node.name]
|
||||
$if trace_post_process_generic_fns ? {
|
||||
eprintln('> post_process_generic_fns $node.mod | $node.name | $gtypes')
|
||||
}
|
||||
for concrete_types in gtypes {
|
||||
c.table.cur_concrete_types = concrete_types
|
||||
c.fn_decl(mut node)
|
||||
if node.name == 'vweb.run' {
|
||||
for ct in concrete_types {
|
||||
if ct !in c.vweb_gen_types {
|
||||
c.vweb_gen_types << ct
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
c.table.cur_concrete_types = []
|
||||
$if trace_post_process_generic_fns ? {
|
||||
if node.generic_names.len > 0 {
|
||||
eprintln(' > fn_decl node.name: $node.name | generic_names: $node.generic_names | ninstances: $node.ninstances')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut c Checker) trace(fbase string, message string) {
|
||||
if c.file.path_base == fbase {
|
||||
println('> c.trace | ${fbase:-10s} | $message')
|
||||
|
@ -102,6 +102,19 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type {
|
||||
return f.return_type
|
||||
}
|
||||
|
||||
fn (mut c Checker) comptime_for(node ast.ComptimeFor) {
|
||||
typ := c.unwrap_generic(node.typ)
|
||||
sym := c.table.sym(typ)
|
||||
if sym.kind == .placeholder || typ.has_flag(.generic) {
|
||||
c.error('unknown type `$sym.name`', node.typ_pos)
|
||||
}
|
||||
if node.kind == .fields {
|
||||
c.comptime_fields_type[node.val_var] = node.typ
|
||||
c.comptime_fields_default_type = node.typ
|
||||
}
|
||||
c.stmts(node.stmts)
|
||||
}
|
||||
|
||||
// comptime const eval
|
||||
fn (mut c Checker) eval_comptime_const_expr(expr ast.Expr, nlevel int) ?ast.ComptTimeConstValue {
|
||||
if nlevel > 100 {
|
||||
@ -570,259 +583,3 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Position) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ ast.Type, node ast.CallExpr) {
|
||||
if node.args.len != 1 {
|
||||
c.error('expected 1 argument, but got $node.args.len', node.pos)
|
||||
// Finish early so that it doesn't fail later
|
||||
return
|
||||
}
|
||||
elem_sym := c.table.sym(elem_typ)
|
||||
arg_expr := node.args[0].expr
|
||||
match arg_expr {
|
||||
ast.AnonFn {
|
||||
if arg_expr.decl.params.len > 1 {
|
||||
c.error('function needs exactly 1 argument', arg_expr.decl.pos)
|
||||
} else if is_map && (arg_expr.decl.return_type == ast.void_type
|
||||
|| arg_expr.decl.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) T {...}`', arg_expr.decl.pos)
|
||||
} else if !is_map && (arg_expr.decl.return_type != ast.bool_type
|
||||
|| arg_expr.decl.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`',
|
||||
arg_expr.decl.pos)
|
||||
}
|
||||
}
|
||||
ast.Ident {
|
||||
if arg_expr.kind == .function {
|
||||
func := c.table.find_fn(arg_expr.name) or {
|
||||
c.error('$arg_expr.name does not exist', arg_expr.pos)
|
||||
return
|
||||
}
|
||||
if func.params.len > 1 {
|
||||
c.error('function needs exactly 1 argument', node.pos)
|
||||
} else if is_map
|
||||
&& (func.return_type == ast.void_type || func.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) T {...}`',
|
||||
arg_expr.pos)
|
||||
} else if !is_map
|
||||
&& (func.return_type != ast.bool_type || func.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`',
|
||||
arg_expr.pos)
|
||||
}
|
||||
} else if arg_expr.kind == .variable {
|
||||
if arg_expr.obj is ast.Var {
|
||||
expr := arg_expr.obj.expr
|
||||
if expr is ast.AnonFn {
|
||||
// copied from above
|
||||
if expr.decl.params.len > 1 {
|
||||
c.error('function needs exactly 1 argument', expr.decl.pos)
|
||||
} else if is_map && (expr.decl.return_type == ast.void_type
|
||||
|| expr.decl.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) T {...}`',
|
||||
expr.decl.pos)
|
||||
} else if !is_map && (expr.decl.return_type != ast.bool_type
|
||||
|| expr.decl.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`',
|
||||
expr.decl.pos)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// NOTE: bug accessing typ field on sumtype variant (not cast properly).
|
||||
// leaving this here as the resulting issue is notoriously hard to debug.
|
||||
// if !is_map && arg_expr.info.typ != ast.bool_type {
|
||||
if !is_map && arg_expr.var_info().typ != ast.bool_type {
|
||||
c.error('type mismatch, should be bool', arg_expr.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
ast.CallExpr {
|
||||
if is_map && arg_expr.return_type in [ast.void_type, 0] {
|
||||
c.error('type mismatch, `$arg_expr.name` does not return anything', arg_expr.pos)
|
||||
} else if !is_map && arg_expr.return_type != ast.bool_type {
|
||||
c.error('type mismatch, `$arg_expr.name` must return a bool', arg_expr.pos)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut c Checker) map_builtin_method_call(mut node ast.CallExpr, left_type ast.Type, left_sym ast.TypeSymbol) ast.Type {
|
||||
method_name := node.name
|
||||
mut ret_type := ast.void_type
|
||||
match method_name {
|
||||
'clone', 'move' {
|
||||
if method_name[0] == `m` {
|
||||
c.fail_if_immutable(node.left)
|
||||
}
|
||||
if node.left.is_auto_deref_var() {
|
||||
ret_type = left_type.deref()
|
||||
} else {
|
||||
ret_type = left_type
|
||||
}
|
||||
}
|
||||
'keys' {
|
||||
info := left_sym.info as ast.Map
|
||||
typ := c.table.find_or_register_array(info.key_type)
|
||||
ret_type = ast.Type(typ)
|
||||
}
|
||||
'delete' {
|
||||
c.fail_if_immutable(node.left)
|
||||
if node.args.len != 1 {
|
||||
c.error('expected 1 argument, but got $node.args.len', node.pos)
|
||||
}
|
||||
info := left_sym.info as ast.Map
|
||||
arg_type := c.expr(node.args[0].expr)
|
||||
c.check_expected_call_arg(arg_type, info.key_type, node.language, node.args[0]) or {
|
||||
c.error('$err.msg in argument 1 to `Map.delete`', node.args[0].pos)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
node.receiver_type = left_type.ref()
|
||||
node.return_type = ret_type
|
||||
return node.return_type
|
||||
}
|
||||
|
||||
fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type ast.Type, left_sym ast.TypeSymbol) ast.Type {
|
||||
method_name := node.name
|
||||
mut elem_typ := ast.void_type
|
||||
if method_name == 'slice' && !c.is_builtin_mod {
|
||||
c.error('.slice() is a private method, use `x[start..end]` instead', node.pos)
|
||||
}
|
||||
array_info := left_sym.info as ast.Array
|
||||
elem_typ = array_info.elem_type
|
||||
if method_name in ['filter', 'map', 'any', 'all'] {
|
||||
// position of `it` doesn't matter
|
||||
scope_register_it(mut node.scope, node.pos, elem_typ)
|
||||
} else if method_name == 'sort' {
|
||||
if node.left is ast.CallExpr {
|
||||
c.error('the `sort()` method can be called only on mutable receivers, but `$node.left` is a call expression',
|
||||
node.pos)
|
||||
}
|
||||
c.fail_if_immutable(node.left)
|
||||
// position of `a` and `b` doesn't matter, they're the same
|
||||
scope_register_a_b(mut node.scope, node.pos, elem_typ)
|
||||
|
||||
if node.args.len > 1 {
|
||||
c.error('expected 0 or 1 argument, but got $node.args.len', node.pos)
|
||||
} else if node.args.len == 1 {
|
||||
if node.args[0].expr is ast.InfixExpr {
|
||||
if node.args[0].expr.op !in [.gt, .lt] {
|
||||
c.error('`.sort()` can only use `<` or `>` comparison', node.pos)
|
||||
}
|
||||
left_name := '${node.args[0].expr.left}'[0]
|
||||
right_name := '${node.args[0].expr.right}'[0]
|
||||
if left_name !in [`a`, `b`] || right_name !in [`a`, `b`] {
|
||||
c.error('`.sort()` can only use `a` or `b` as argument, e.g. `arr.sort(a < b)`',
|
||||
node.pos)
|
||||
} else if left_name == right_name {
|
||||
c.error('`.sort()` cannot use same argument', node.pos)
|
||||
}
|
||||
if (node.args[0].expr.left !is ast.Ident
|
||||
&& node.args[0].expr.left !is ast.SelectorExpr
|
||||
&& node.args[0].expr.left !is ast.IndexExpr)
|
||||
|| (node.args[0].expr.right !is ast.Ident
|
||||
&& node.args[0].expr.right !is ast.SelectorExpr
|
||||
&& node.args[0].expr.right !is ast.IndexExpr) {
|
||||
c.error('`.sort()` can only use ident, index or selector as argument, \ne.g. `arr.sort(a < b)`, `arr.sort(a.id < b.id)`, `arr.sort(a[0] < b[0])`',
|
||||
node.pos)
|
||||
}
|
||||
} else {
|
||||
c.error(
|
||||
'`.sort()` requires a `<` or `>` comparison as the first and only argument' +
|
||||
'\ne.g. `users.sort(a.id < b.id)`', node.pos)
|
||||
}
|
||||
} else if !(c.table.sym(elem_typ).has_method('<')
|
||||
|| c.table.unalias_num_type(elem_typ) in [ast.int_type, ast.int_type.ref(), ast.string_type, ast.string_type.ref(), ast.i8_type, ast.i16_type, ast.i64_type, ast.byte_type, ast.rune_type, ast.u16_type, ast.u32_type, ast.u64_type, ast.f32_type, ast.f64_type, ast.char_type, ast.bool_type, ast.float_literal_type, ast.int_literal_type]) {
|
||||
c.error('custom sorting condition must be supplied for type `${c.table.type_to_str(elem_typ)}`',
|
||||
node.pos)
|
||||
}
|
||||
} else if method_name == 'wait' {
|
||||
elem_sym := c.table.sym(elem_typ)
|
||||
if elem_sym.kind == .thread {
|
||||
if node.args.len != 0 {
|
||||
c.error('`.wait()` does not have any arguments', node.args[0].pos)
|
||||
}
|
||||
thread_ret_type := elem_sym.thread_info().return_type
|
||||
if thread_ret_type.has_flag(.optional) {
|
||||
c.error('`.wait()` cannot be called for an array when thread functions return optionals. Iterate over the arrays elements instead and handle each returned optional with `or`.',
|
||||
node.pos)
|
||||
}
|
||||
node.return_type = c.table.find_or_register_array(thread_ret_type)
|
||||
} else {
|
||||
c.error('`$left_sym.name` has no method `wait()` (only thread handles and arrays of them have)',
|
||||
node.left.position())
|
||||
}
|
||||
}
|
||||
// map/filter are supposed to have 1 arg only
|
||||
mut arg_type := left_type
|
||||
for arg in node.args {
|
||||
arg_type = c.check_expr_opt_call(arg.expr, c.expr(arg.expr))
|
||||
}
|
||||
if method_name == 'map' {
|
||||
// check fn
|
||||
c.check_map_and_filter(true, elem_typ, node)
|
||||
arg_sym := c.table.sym(arg_type)
|
||||
ret_type := match arg_sym.info {
|
||||
ast.FnType { arg_sym.info.func.return_type }
|
||||
else { arg_type }
|
||||
}
|
||||
node.return_type = c.table.find_or_register_array(c.unwrap_generic(ret_type))
|
||||
} else if method_name == 'filter' {
|
||||
// check fn
|
||||
c.check_map_and_filter(false, elem_typ, node)
|
||||
} else if method_name in ['any', 'all'] {
|
||||
c.check_map_and_filter(false, elem_typ, node)
|
||||
node.return_type = ast.bool_type
|
||||
} else if method_name == 'clone' {
|
||||
// need to return `array_xxx` instead of `array`
|
||||
// in ['clone', 'str'] {
|
||||
node.receiver_type = left_type.ref()
|
||||
if node.left.is_auto_deref_var() {
|
||||
node.return_type = left_type.deref()
|
||||
} else {
|
||||
node.return_type = node.receiver_type.set_nr_muls(0)
|
||||
}
|
||||
} else if method_name == 'sort' {
|
||||
node.return_type = ast.void_type
|
||||
} else if method_name == 'contains' {
|
||||
// c.warn('use `value in arr` instead of `arr.contains(value)`', node.pos)
|
||||
node.return_type = ast.bool_type
|
||||
} else if method_name == 'index' {
|
||||
node.return_type = ast.int_type
|
||||
} else if method_name in ['first', 'last', 'pop'] {
|
||||
node.return_type = array_info.elem_type
|
||||
if method_name == 'pop' {
|
||||
c.fail_if_immutable(node.left)
|
||||
node.receiver_type = left_type.ref()
|
||||
} else {
|
||||
node.receiver_type = left_type
|
||||
}
|
||||
}
|
||||
return node.return_type
|
||||
}
|
||||
|
||||
fn scope_register_it(mut s ast.Scope, pos token.Position, typ ast.Type) {
|
||||
s.register(ast.Var{
|
||||
name: 'it'
|
||||
pos: pos
|
||||
typ: typ
|
||||
is_used: true
|
||||
})
|
||||
}
|
||||
|
||||
fn scope_register_a_b(mut s ast.Scope, pos token.Position, typ ast.Type) {
|
||||
s.register(ast.Var{
|
||||
name: 'a'
|
||||
pos: pos
|
||||
typ: typ.ref()
|
||||
is_used: true
|
||||
})
|
||||
s.register(ast.Var{
|
||||
name: 'b'
|
||||
pos: pos
|
||||
typ: typ.ref()
|
||||
is_used: true
|
||||
})
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import v.ast
|
||||
import v.pref
|
||||
import time
|
||||
import v.util
|
||||
import v.token
|
||||
|
||||
fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
|
||||
if node.generic_names.len > 0 && c.table.cur_concrete_types.len == 0 {
|
||||
@ -334,6 +335,78 @@ fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type {
|
||||
return node.typ
|
||||
}
|
||||
|
||||
pub fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type {
|
||||
// First check everything that applies to both fns and methods
|
||||
// TODO merge logic from method_call and fn_call
|
||||
/*
|
||||
for i, call_arg in node.args {
|
||||
if call_arg.is_mut {
|
||||
c.fail_if_immutable(call_arg.expr)
|
||||
if !arg.is_mut {
|
||||
tok := call_arg.share.str()
|
||||
c.error('`$node.name` parameter `$arg.name` is not `$tok`, `$tok` is not needed`',
|
||||
call_arg.expr.position())
|
||||
} else if arg.typ.share() != call_arg.share {
|
||||
c.error('wrong shared type', call_arg.expr.position())
|
||||
}
|
||||
} else {
|
||||
if arg.is_mut && (!call_arg.is_mut || arg.typ.share() != call_arg.share) {
|
||||
tok := call_arg.share.str()
|
||||
c.error('`$node.name` parameter `$arg.name` is `$tok`, you need to provide `$tok` e.g. `$tok arg${i+1}`',
|
||||
call_arg.expr.position())
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
// Now call `method_call` or `fn_call` for specific checks.
|
||||
old_inside_fn_arg := c.inside_fn_arg
|
||||
c.inside_fn_arg = true
|
||||
mut continue_check := true
|
||||
typ := if node.is_method {
|
||||
c.method_call(mut node)
|
||||
} else {
|
||||
c.fn_call(mut node, mut continue_check)
|
||||
}
|
||||
if !continue_check {
|
||||
return ast.void_type
|
||||
}
|
||||
c.inside_fn_arg = old_inside_fn_arg
|
||||
// autofree: mark args that have to be freed (after saving them in tmp exprs)
|
||||
free_tmp_arg_vars := c.pref.autofree && !c.is_builtin_mod && node.args.len > 0
|
||||
&& !node.args[0].typ.has_flag(.optional)
|
||||
if free_tmp_arg_vars && !c.inside_const {
|
||||
for i, arg in node.args {
|
||||
if arg.typ != ast.string_type {
|
||||
continue
|
||||
}
|
||||
if arg.expr in [ast.Ident, ast.StringLiteral, ast.SelectorExpr] {
|
||||
// Simple expressions like variables, string literals, selector expressions
|
||||
// (`x.field`) can't result in allocations and don't need to be assigned to
|
||||
// temporary vars.
|
||||
// Only expressions like `str + 'b'` need to be freed.
|
||||
continue
|
||||
}
|
||||
node.args[i].is_tmp_autofree = true
|
||||
}
|
||||
// TODO copy pasta from above
|
||||
if node.receiver_type == ast.string_type
|
||||
&& node.left !in [ast.Ident, ast.StringLiteral, ast.SelectorExpr] {
|
||||
node.free_receiver = true
|
||||
}
|
||||
}
|
||||
c.expected_or_type = node.return_type.clear_flag(.optional)
|
||||
c.stmts_ending_with_expression(node.or_block.stmts)
|
||||
c.expected_or_type = ast.void_type
|
||||
if node.or_block.kind == .propagate && !c.table.cur_fn.return_type.has_flag(.optional)
|
||||
&& !c.inside_const {
|
||||
if !c.table.cur_fn.is_main {
|
||||
c.error('to propagate the optional call, `$c.table.cur_fn.name` must return an optional',
|
||||
node.or_block.pos)
|
||||
}
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast.Type {
|
||||
fn_name := node.name
|
||||
if fn_name == 'main' {
|
||||
@ -1340,6 +1413,43 @@ fn (mut c Checker) deprecate_fnmethod(kind string, name string, the_fn ast.Fn, n
|
||||
}
|
||||
}
|
||||
|
||||
fn semicolonize(main string, details string) string {
|
||||
if details == '' {
|
||||
return main
|
||||
}
|
||||
return '$main; $details'
|
||||
}
|
||||
|
||||
fn (mut c Checker) post_process_generic_fns() {
|
||||
// Loop thru each generic function concrete type.
|
||||
// Check each specific fn instantiation.
|
||||
for i in 0 .. c.file.generic_fns.len {
|
||||
mut node := c.file.generic_fns[i]
|
||||
c.mod = node.mod
|
||||
gtypes := c.table.fn_generic_types[node.name]
|
||||
$if trace_post_process_generic_fns ? {
|
||||
eprintln('> post_process_generic_fns $node.mod | $node.name | $gtypes')
|
||||
}
|
||||
for concrete_types in gtypes {
|
||||
c.table.cur_concrete_types = concrete_types
|
||||
c.fn_decl(mut node)
|
||||
if node.name == 'vweb.run' {
|
||||
for ct in concrete_types {
|
||||
if ct !in c.vweb_gen_types {
|
||||
c.vweb_gen_types << ct
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
c.table.cur_concrete_types = []
|
||||
$if trace_post_process_generic_fns ? {
|
||||
if node.generic_names.len > 0 {
|
||||
eprintln(' > fn_decl node.name: $node.name | generic_names: $node.generic_names | ninstances: $node.ninstances')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut c Checker) check_expected_arg_count(mut node ast.CallExpr, f &ast.Fn) ? {
|
||||
nr_args := node.args.len
|
||||
nr_params := if node.is_method && f.params.len > 0 { f.params.len - 1 } else { f.params.len }
|
||||
@ -1378,3 +1488,259 @@ pub fn (mut c Checker) check_expected_arg_count(mut node ast.CallExpr, f &ast.Fn
|
||||
return error('')
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ ast.Type, node ast.CallExpr) {
|
||||
if node.args.len != 1 {
|
||||
c.error('expected 1 argument, but got $node.args.len', node.pos)
|
||||
// Finish early so that it doesn't fail later
|
||||
return
|
||||
}
|
||||
elem_sym := c.table.sym(elem_typ)
|
||||
arg_expr := node.args[0].expr
|
||||
match arg_expr {
|
||||
ast.AnonFn {
|
||||
if arg_expr.decl.params.len > 1 {
|
||||
c.error('function needs exactly 1 argument', arg_expr.decl.pos)
|
||||
} else if is_map && (arg_expr.decl.return_type == ast.void_type
|
||||
|| arg_expr.decl.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) T {...}`', arg_expr.decl.pos)
|
||||
} else if !is_map && (arg_expr.decl.return_type != ast.bool_type
|
||||
|| arg_expr.decl.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`',
|
||||
arg_expr.decl.pos)
|
||||
}
|
||||
}
|
||||
ast.Ident {
|
||||
if arg_expr.kind == .function {
|
||||
func := c.table.find_fn(arg_expr.name) or {
|
||||
c.error('$arg_expr.name does not exist', arg_expr.pos)
|
||||
return
|
||||
}
|
||||
if func.params.len > 1 {
|
||||
c.error('function needs exactly 1 argument', node.pos)
|
||||
} else if is_map
|
||||
&& (func.return_type == ast.void_type || func.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) T {...}`',
|
||||
arg_expr.pos)
|
||||
} else if !is_map
|
||||
&& (func.return_type != ast.bool_type || func.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`',
|
||||
arg_expr.pos)
|
||||
}
|
||||
} else if arg_expr.kind == .variable {
|
||||
if arg_expr.obj is ast.Var {
|
||||
expr := arg_expr.obj.expr
|
||||
if expr is ast.AnonFn {
|
||||
// copied from above
|
||||
if expr.decl.params.len > 1 {
|
||||
c.error('function needs exactly 1 argument', expr.decl.pos)
|
||||
} else if is_map && (expr.decl.return_type == ast.void_type
|
||||
|| expr.decl.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) T {...}`',
|
||||
expr.decl.pos)
|
||||
} else if !is_map && (expr.decl.return_type != ast.bool_type
|
||||
|| expr.decl.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`',
|
||||
expr.decl.pos)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// NOTE: bug accessing typ field on sumtype variant (not cast properly).
|
||||
// leaving this here as the resulting issue is notoriously hard to debug.
|
||||
// if !is_map && arg_expr.info.typ != ast.bool_type {
|
||||
if !is_map && arg_expr.var_info().typ != ast.bool_type {
|
||||
c.error('type mismatch, should be bool', arg_expr.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
ast.CallExpr {
|
||||
if is_map && arg_expr.return_type in [ast.void_type, 0] {
|
||||
c.error('type mismatch, `$arg_expr.name` does not return anything', arg_expr.pos)
|
||||
} else if !is_map && arg_expr.return_type != ast.bool_type {
|
||||
c.error('type mismatch, `$arg_expr.name` must return a bool', arg_expr.pos)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut c Checker) map_builtin_method_call(mut node ast.CallExpr, left_type ast.Type, left_sym ast.TypeSymbol) ast.Type {
|
||||
method_name := node.name
|
||||
mut ret_type := ast.void_type
|
||||
match method_name {
|
||||
'clone', 'move' {
|
||||
if method_name[0] == `m` {
|
||||
c.fail_if_immutable(node.left)
|
||||
}
|
||||
if node.left.is_auto_deref_var() {
|
||||
ret_type = left_type.deref()
|
||||
} else {
|
||||
ret_type = left_type
|
||||
}
|
||||
}
|
||||
'keys' {
|
||||
info := left_sym.info as ast.Map
|
||||
typ := c.table.find_or_register_array(info.key_type)
|
||||
ret_type = ast.Type(typ)
|
||||
}
|
||||
'delete' {
|
||||
c.fail_if_immutable(node.left)
|
||||
if node.args.len != 1 {
|
||||
c.error('expected 1 argument, but got $node.args.len', node.pos)
|
||||
}
|
||||
info := left_sym.info as ast.Map
|
||||
arg_type := c.expr(node.args[0].expr)
|
||||
c.check_expected_call_arg(arg_type, info.key_type, node.language, node.args[0]) or {
|
||||
c.error('$err.msg in argument 1 to `Map.delete`', node.args[0].pos)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
node.receiver_type = left_type.ref()
|
||||
node.return_type = ret_type
|
||||
return node.return_type
|
||||
}
|
||||
|
||||
fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type ast.Type, left_sym ast.TypeSymbol) ast.Type {
|
||||
method_name := node.name
|
||||
mut elem_typ := ast.void_type
|
||||
if method_name == 'slice' && !c.is_builtin_mod {
|
||||
c.error('.slice() is a private method, use `x[start..end]` instead', node.pos)
|
||||
}
|
||||
array_info := left_sym.info as ast.Array
|
||||
elem_typ = array_info.elem_type
|
||||
if method_name in ['filter', 'map', 'any', 'all'] {
|
||||
// position of `it` doesn't matter
|
||||
scope_register_it(mut node.scope, node.pos, elem_typ)
|
||||
} else if method_name == 'sort' {
|
||||
if node.left is ast.CallExpr {
|
||||
c.error('the `sort()` method can be called only on mutable receivers, but `$node.left` is a call expression',
|
||||
node.pos)
|
||||
}
|
||||
c.fail_if_immutable(node.left)
|
||||
// position of `a` and `b` doesn't matter, they're the same
|
||||
scope_register_a_b(mut node.scope, node.pos, elem_typ)
|
||||
|
||||
if node.args.len > 1 {
|
||||
c.error('expected 0 or 1 argument, but got $node.args.len', node.pos)
|
||||
} else if node.args.len == 1 {
|
||||
if node.args[0].expr is ast.InfixExpr {
|
||||
if node.args[0].expr.op !in [.gt, .lt] {
|
||||
c.error('`.sort()` can only use `<` or `>` comparison', node.pos)
|
||||
}
|
||||
left_name := '${node.args[0].expr.left}'[0]
|
||||
right_name := '${node.args[0].expr.right}'[0]
|
||||
if left_name !in [`a`, `b`] || right_name !in [`a`, `b`] {
|
||||
c.error('`.sort()` can only use `a` or `b` as argument, e.g. `arr.sort(a < b)`',
|
||||
node.pos)
|
||||
} else if left_name == right_name {
|
||||
c.error('`.sort()` cannot use same argument', node.pos)
|
||||
}
|
||||
if (node.args[0].expr.left !is ast.Ident
|
||||
&& node.args[0].expr.left !is ast.SelectorExpr
|
||||
&& node.args[0].expr.left !is ast.IndexExpr)
|
||||
|| (node.args[0].expr.right !is ast.Ident
|
||||
&& node.args[0].expr.right !is ast.SelectorExpr
|
||||
&& node.args[0].expr.right !is ast.IndexExpr) {
|
||||
c.error('`.sort()` can only use ident, index or selector as argument, \ne.g. `arr.sort(a < b)`, `arr.sort(a.id < b.id)`, `arr.sort(a[0] < b[0])`',
|
||||
node.pos)
|
||||
}
|
||||
} else {
|
||||
c.error(
|
||||
'`.sort()` requires a `<` or `>` comparison as the first and only argument' +
|
||||
'\ne.g. `users.sort(a.id < b.id)`', node.pos)
|
||||
}
|
||||
} else if !(c.table.sym(elem_typ).has_method('<')
|
||||
|| c.table.unalias_num_type(elem_typ) in [ast.int_type, ast.int_type.ref(), ast.string_type, ast.string_type.ref(), ast.i8_type, ast.i16_type, ast.i64_type, ast.byte_type, ast.rune_type, ast.u16_type, ast.u32_type, ast.u64_type, ast.f32_type, ast.f64_type, ast.char_type, ast.bool_type, ast.float_literal_type, ast.int_literal_type]) {
|
||||
c.error('custom sorting condition must be supplied for type `${c.table.type_to_str(elem_typ)}`',
|
||||
node.pos)
|
||||
}
|
||||
} else if method_name == 'wait' {
|
||||
elem_sym := c.table.sym(elem_typ)
|
||||
if elem_sym.kind == .thread {
|
||||
if node.args.len != 0 {
|
||||
c.error('`.wait()` does not have any arguments', node.args[0].pos)
|
||||
}
|
||||
thread_ret_type := elem_sym.thread_info().return_type
|
||||
if thread_ret_type.has_flag(.optional) {
|
||||
c.error('`.wait()` cannot be called for an array when thread functions return optionals. Iterate over the arrays elements instead and handle each returned optional with `or`.',
|
||||
node.pos)
|
||||
}
|
||||
node.return_type = c.table.find_or_register_array(thread_ret_type)
|
||||
} else {
|
||||
c.error('`$left_sym.name` has no method `wait()` (only thread handles and arrays of them have)',
|
||||
node.left.position())
|
||||
}
|
||||
}
|
||||
// map/filter are supposed to have 1 arg only
|
||||
mut arg_type := left_type
|
||||
for arg in node.args {
|
||||
arg_type = c.check_expr_opt_call(arg.expr, c.expr(arg.expr))
|
||||
}
|
||||
if method_name == 'map' {
|
||||
// check fn
|
||||
c.check_map_and_filter(true, elem_typ, node)
|
||||
arg_sym := c.table.sym(arg_type)
|
||||
ret_type := match arg_sym.info {
|
||||
ast.FnType { arg_sym.info.func.return_type }
|
||||
else { arg_type }
|
||||
}
|
||||
node.return_type = c.table.find_or_register_array(c.unwrap_generic(ret_type))
|
||||
} else if method_name == 'filter' {
|
||||
// check fn
|
||||
c.check_map_and_filter(false, elem_typ, node)
|
||||
} else if method_name in ['any', 'all'] {
|
||||
c.check_map_and_filter(false, elem_typ, node)
|
||||
node.return_type = ast.bool_type
|
||||
} else if method_name == 'clone' {
|
||||
// need to return `array_xxx` instead of `array`
|
||||
// in ['clone', 'str'] {
|
||||
node.receiver_type = left_type.ref()
|
||||
if node.left.is_auto_deref_var() {
|
||||
node.return_type = left_type.deref()
|
||||
} else {
|
||||
node.return_type = node.receiver_type.set_nr_muls(0)
|
||||
}
|
||||
} else if method_name == 'sort' {
|
||||
node.return_type = ast.void_type
|
||||
} else if method_name == 'contains' {
|
||||
// c.warn('use `value in arr` instead of `arr.contains(value)`', node.pos)
|
||||
node.return_type = ast.bool_type
|
||||
} else if method_name == 'index' {
|
||||
node.return_type = ast.int_type
|
||||
} else if method_name in ['first', 'last', 'pop'] {
|
||||
node.return_type = array_info.elem_type
|
||||
if method_name == 'pop' {
|
||||
c.fail_if_immutable(node.left)
|
||||
node.receiver_type = left_type.ref()
|
||||
} else {
|
||||
node.receiver_type = left_type
|
||||
}
|
||||
}
|
||||
return node.return_type
|
||||
}
|
||||
|
||||
fn scope_register_it(mut s ast.Scope, pos token.Position, typ ast.Type) {
|
||||
s.register(ast.Var{
|
||||
name: 'it'
|
||||
pos: pos
|
||||
typ: typ
|
||||
is_used: true
|
||||
})
|
||||
}
|
||||
|
||||
fn scope_register_a_b(mut s ast.Scope, pos token.Position, typ ast.Type) {
|
||||
s.register(ast.Var{
|
||||
name: 'a'
|
||||
pos: pos
|
||||
typ: typ.ref()
|
||||
is_used: true
|
||||
})
|
||||
s.register(ast.Var{
|
||||
name: 'b'
|
||||
pos: pos
|
||||
typ: typ.ref()
|
||||
is_used: true
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user