mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
451 lines
13 KiB
V
451 lines
13 KiB
V
// Copyright (c) 2019-2023 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
|
|
|
|
// error_type_name returns a proper type name reference for error messages
|
|
// ? => Option type
|
|
// ! => Result type
|
|
// others => type `name`
|
|
[inline]
|
|
fn (mut c Checker) error_type_name(exp_type ast.Type) string {
|
|
return if exp_type == ast.void_type.set_flag(.result) {
|
|
'Result type'
|
|
} else if exp_type == ast.void_type.set_flag(.option) {
|
|
'Option type'
|
|
} else {
|
|
'type `${c.table.type_to_str(exp_type)}`'
|
|
}
|
|
}
|
|
|
|
// TODO: non deferred
|
|
fn (mut c Checker) return_stmt(mut node ast.Return) {
|
|
if c.table.cur_fn == unsafe { nil } {
|
|
return
|
|
}
|
|
c.expected_type = c.table.cur_fn.return_type
|
|
mut expected_type := c.unwrap_generic(c.expected_type)
|
|
if expected_type != 0 && c.table.sym(expected_type).kind == .alias {
|
|
unaliased_type := c.table.unaliased_type(expected_type)
|
|
if unaliased_type.has_flag(.option) || unaliased_type.has_flag(.result) {
|
|
expected_type = unaliased_type
|
|
}
|
|
}
|
|
expected_type_sym := c.table.sym(expected_type)
|
|
if node.exprs.len > 0 && c.table.cur_fn.return_type == ast.void_type {
|
|
c.error('unexpected argument, current function does not return anything', node.exprs[0].pos())
|
|
return
|
|
} else if node.exprs.len > 1 && c.table.cur_fn.return_type == ast.void_type.set_flag(.option) {
|
|
c.error('can only return `none` from an Option-only return function', node.exprs[0].pos())
|
|
return
|
|
} else if node.exprs.len > 1 && c.table.cur_fn.return_type == ast.void_type.set_flag(.result) {
|
|
c.error('functions with Result-only return types can only return an error', node.exprs[0].pos())
|
|
return
|
|
} else if node.exprs.len == 0 && !(c.expected_type == ast.void_type
|
|
|| expected_type_sym.kind == .void) {
|
|
stype := c.table.type_to_str(expected_type)
|
|
arg := if expected_type_sym.kind == .multi_return { 'arguments' } else { 'argument' }
|
|
c.error('expected `${stype}` ${arg}', node.pos)
|
|
return
|
|
}
|
|
if node.exprs.len == 0 {
|
|
return
|
|
}
|
|
exp_is_option := expected_type.has_flag(.option)
|
|
exp_is_result := expected_type.has_flag(.result)
|
|
mut expected_types := [expected_type]
|
|
if expected_type_sym.info is ast.MultiReturn {
|
|
expected_types = expected_type_sym.info.types.clone()
|
|
if c.table.cur_concrete_types.len > 0 {
|
|
expected_types = expected_types.map(c.unwrap_generic(it))
|
|
}
|
|
}
|
|
mut got_types := []ast.Type{}
|
|
mut expr_idxs := []int{}
|
|
for i, mut expr in node.exprs {
|
|
mut typ := c.expr(mut expr)
|
|
if typ == 0 {
|
|
return
|
|
}
|
|
// Handle `return unsafe { none }`
|
|
if mut expr is ast.UnsafeExpr {
|
|
if mut expr.expr is ast.None {
|
|
c.error('cannot return `none` in unsafe block', expr.expr.pos)
|
|
}
|
|
}
|
|
if typ == ast.void_type {
|
|
c.error('`${expr}` used as value', node.pos)
|
|
return
|
|
}
|
|
// Unpack multi return types
|
|
sym := c.table.sym(typ)
|
|
if sym.kind == .multi_return {
|
|
if i > 0 || i != node.exprs.len - 1 {
|
|
c.error('cannot use multi-return with other return types', expr.pos())
|
|
}
|
|
for t in sym.mr_info().types {
|
|
got_types << t
|
|
expr_idxs << i
|
|
}
|
|
} else {
|
|
if mut expr is ast.Ident {
|
|
if mut expr.obj is ast.Var {
|
|
if expr.obj.smartcasts.len > 0 {
|
|
typ = c.unwrap_generic(expr.obj.smartcasts.last())
|
|
}
|
|
}
|
|
}
|
|
got_types << typ
|
|
expr_idxs << i
|
|
}
|
|
}
|
|
node.types = got_types
|
|
$if debug_manualfree ? {
|
|
cfn := c.table.cur_fn
|
|
if cfn.is_manualfree {
|
|
pnames := cfn.params.map(it.name)
|
|
for expr in node.exprs {
|
|
if expr is ast.Ident {
|
|
if expr.name in pnames {
|
|
c.note('returning a parameter in a fn marked with `[manualfree]` can cause double freeing in the caller',
|
|
node.pos)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// allow `none` & `error` return types for function that returns option
|
|
option_type_idx := c.table.type_idxs['_option']
|
|
result_type_idx := c.table.type_idxs['_result']
|
|
got_types_0_idx := got_types[0].idx()
|
|
if exp_is_option && got_types_0_idx == ast.error_type_idx {
|
|
c.warn('Option and Result types have been split, use `!Foo` to return errors',
|
|
node.pos)
|
|
} else if exp_is_result && got_types_0_idx == ast.none_type_idx {
|
|
c.warn('Option and Result types have been split, use `?` to return none', node.pos)
|
|
}
|
|
if (exp_is_option
|
|
&& got_types_0_idx in [ast.none_type_idx, ast.error_type_idx, option_type_idx])
|
|
|| (exp_is_result && got_types_0_idx in [ast.error_type_idx, result_type_idx]) {
|
|
return
|
|
}
|
|
if expected_types.len > 0 && expected_types.len != got_types.len {
|
|
// `fn foo() !(int, string) { return Err{} }`
|
|
if (exp_is_option || exp_is_result) && node.exprs.len == 1 {
|
|
mut expr_ := node.exprs[0]
|
|
got_type := c.expr(mut expr_)
|
|
got_type_sym := c.table.sym(got_type)
|
|
if got_type_sym.kind == .struct_
|
|
&& c.type_implements(got_type, ast.error_type, node.pos) {
|
|
node.exprs[0] = ast.CastExpr{
|
|
expr: node.exprs[0]
|
|
typname: 'IError'
|
|
typ: ast.error_type
|
|
expr_type: got_type
|
|
pos: node.pos
|
|
}
|
|
node.types[0] = ast.error_type
|
|
return
|
|
}
|
|
}
|
|
arg := if expected_types.len == 1 { 'argument' } else { 'arguments' }
|
|
midx := imax(0, imin(expected_types.len, expr_idxs.len - 1))
|
|
mismatch_pos := node.exprs[expr_idxs[midx]].pos()
|
|
c.error('expected ${expected_types.len} ${arg}, but got ${got_types.len}', mismatch_pos)
|
|
return
|
|
}
|
|
for i, exp_type in expected_types {
|
|
exprv := node.exprs[expr_idxs[i]]
|
|
if exprv is ast.Ident && exprv.or_expr.kind == .propagate_option {
|
|
if exp_type.has_flag(.option) {
|
|
c.warn('unwrapping option is redundant as the function returns option',
|
|
node.pos)
|
|
} else {
|
|
c.error('should not unwrap option var on return, it could be none', node.pos)
|
|
}
|
|
}
|
|
got_type := c.unwrap_generic(got_types[i])
|
|
if got_type.has_flag(.option)
|
|
&& got_type.clear_flag(.option) != exp_type.clear_flag(.option) {
|
|
pos := node.exprs[expr_idxs[i]].pos()
|
|
c.error('cannot use `${c.table.type_to_str(got_type)}` as ${c.error_type_name(exp_type)} in return argument',
|
|
pos)
|
|
}
|
|
if got_type.has_flag(.result) && (!exp_type.has_flag(.result)
|
|
|| c.table.type_to_str(got_type) != c.table.type_to_str(exp_type)) {
|
|
pos := node.exprs[expr_idxs[i]].pos()
|
|
c.error('cannot use `${c.table.type_to_str(got_type)}` as ${c.error_type_name(exp_type)} in return argument',
|
|
pos)
|
|
}
|
|
if exprv is ast.ComptimeCall && exprv.method_name == 'tmpl'
|
|
&& c.table.final_sym(exp_type).kind != .string {
|
|
c.error('cannot use `string` as type `${c.table.type_to_str(exp_type)}` in return argument',
|
|
exprv.pos)
|
|
}
|
|
if node.exprs[expr_idxs[i]] !is ast.ComptimeCall {
|
|
got_type_sym := c.table.sym(got_type)
|
|
exp_type_sym := c.table.sym(exp_type)
|
|
pos := node.exprs[expr_idxs[i]].pos()
|
|
if c.check_types(got_type, exp_type) {
|
|
if exp_type.is_unsigned() && got_type.is_int_literal() {
|
|
if node.exprs[expr_idxs[i]] is ast.IntegerLiteral {
|
|
var := (node.exprs[expr_idxs[i]] as ast.IntegerLiteral).val
|
|
if var[0] == `-` {
|
|
c.note('cannot use a negative value as value of ${c.error_type_name(exp_type)} in return argument',
|
|
pos)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if exp_type_sym.kind == .interface_ {
|
|
if c.type_implements(got_type, exp_type, node.pos) {
|
|
if !got_type.is_any_kind_of_pointer() && got_type_sym.kind != .interface_
|
|
&& !c.inside_unsafe {
|
|
c.mark_as_referenced(mut &node.exprs[expr_idxs[i]], true)
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
exp_final_sym := c.table.final_sym(exp_type)
|
|
got_final_sym := c.table.final_sym(got_type)
|
|
if exp_final_sym.kind == .array_fixed && got_final_sym.kind == .array_fixed {
|
|
got_arr_sym := c.table.sym(c.cast_to_fixed_array_ret(got_type, got_final_sym))
|
|
if (exp_final_sym.info as ast.ArrayFixed).is_compatible(got_arr_sym.info as ast.ArrayFixed) {
|
|
continue
|
|
}
|
|
}
|
|
// `fn foo() !int { return Err{} }`
|
|
if got_type_sym.kind == .struct_
|
|
&& c.type_implements(got_type, ast.error_type, node.pos) {
|
|
node.exprs[expr_idxs[i]] = ast.CastExpr{
|
|
expr: node.exprs[expr_idxs[i]]
|
|
typname: 'IError'
|
|
typ: ast.error_type
|
|
expr_type: got_type
|
|
pos: node.pos
|
|
}
|
|
node.types[expr_idxs[i]] = ast.error_type
|
|
continue
|
|
}
|
|
got_type_name := if got_type_sym.kind == .function {
|
|
'${c.table.type_to_str(got_type)}'
|
|
} else {
|
|
got_type_sym.name
|
|
}
|
|
c.error('cannot use `${got_type_name}` as ${c.error_type_name(exp_type)} in return argument',
|
|
pos)
|
|
}
|
|
}
|
|
if got_type.is_any_kind_of_pointer() && !exp_type.is_any_kind_of_pointer()
|
|
&& !c.table.unaliased_type(exp_type).is_any_kind_of_pointer() {
|
|
pos := node.exprs[expr_idxs[i]].pos()
|
|
if node.exprs[expr_idxs[i]].is_auto_deref_var() {
|
|
continue
|
|
}
|
|
c.add_error_detail('use `return *pointer` instead of `return pointer`, and just `return value` instead of `return &value`')
|
|
c.error('fn `${c.table.cur_fn.name}` expects you to return a non reference type `${c.table.type_to_str(exp_type)}`, but you are returning `${c.table.type_to_str(got_type)}` instead',
|
|
pos)
|
|
}
|
|
if exp_type.is_any_kind_of_pointer() && !got_type.is_any_kind_of_pointer()
|
|
&& !c.table.unaliased_type(got_type).is_any_kind_of_pointer()
|
|
&& got_type != ast.int_literal_type && !c.pref.translated && !c.file.is_translated {
|
|
pos := node.exprs[expr_idxs[i]].pos()
|
|
if node.exprs[expr_idxs[i]].is_auto_deref_var() {
|
|
continue
|
|
}
|
|
c.error('fn `${c.table.cur_fn.name}` expects you to return a reference type `${c.table.type_to_str(exp_type)}`, but you are returning `${c.table.type_to_str(got_type)}` instead',
|
|
pos)
|
|
}
|
|
if exp_type.is_ptr() && got_type.is_ptr() {
|
|
mut r_expr := &node.exprs[expr_idxs[i]]
|
|
if mut r_expr is ast.Ident {
|
|
c.fail_if_stack_struct_action_outside_unsafe(mut r_expr, 'returned')
|
|
}
|
|
}
|
|
}
|
|
if exp_is_option && node.exprs.len > 0 {
|
|
expr0 := node.exprs[0]
|
|
if expr0 is ast.CallExpr {
|
|
if expr0.or_block.kind == .propagate_option && node.exprs.len == 1 {
|
|
c.error('`?` is not needed, use `return ${expr0.name}()`', expr0.pos)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut c Checker) find_unreachable_statements_after_noreturn_calls(stmts []ast.Stmt) {
|
|
mut prev_stmt_was_noreturn_call := false
|
|
for stmt in stmts {
|
|
if stmt is ast.ExprStmt {
|
|
if stmt.expr is ast.CallExpr {
|
|
if prev_stmt_was_noreturn_call {
|
|
c.error('unreachable code after a [noreturn] call', stmt.pos)
|
|
return
|
|
}
|
|
prev_stmt_was_noreturn_call = stmt.expr.is_noreturn
|
|
}
|
|
} else {
|
|
prev_stmt_was_noreturn_call = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// Note: has_top_return/1 should be called on *already checked* stmts,
|
|
// which do have their stmt.expr.is_noreturn set properly:
|
|
fn has_top_return(stmts []ast.Stmt) bool {
|
|
for stmt in stmts {
|
|
match stmt {
|
|
ast.Return {
|
|
return true
|
|
}
|
|
ast.Block {
|
|
if has_top_return(stmt.stmts) {
|
|
return true
|
|
}
|
|
}
|
|
ast.ExprStmt {
|
|
if stmt.expr is ast.CallExpr {
|
|
// do not ignore panic() calls on non checked stmts
|
|
if stmt.expr.is_noreturn
|
|
|| (stmt.expr.is_method == false && stmt.expr.name == 'panic') {
|
|
return true
|
|
}
|
|
} else if stmt.expr is ast.ComptimeCall {
|
|
if stmt.expr.method_name == 'compile_error' {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
else {}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
fn (mut c Checker) check_noreturn_fn_decl(mut node ast.FnDecl) {
|
|
if !node.is_noreturn {
|
|
return
|
|
}
|
|
if node.no_body {
|
|
return
|
|
}
|
|
if node.return_type != ast.void_type {
|
|
c.error('[noreturn] functions cannot have return types', node.pos)
|
|
}
|
|
if uses_return_stmt(node.stmts) {
|
|
c.error('[noreturn] functions cannot use return statements', node.pos)
|
|
}
|
|
if node.stmts.len != 0 {
|
|
mut is_valid_end_of_noreturn_fn := false
|
|
last_stmt := node.stmts.last()
|
|
match last_stmt {
|
|
ast.ExprStmt {
|
|
if last_stmt.expr is ast.CallExpr {
|
|
if last_stmt.expr.should_be_skipped {
|
|
c.error('[noreturn] functions cannot end with a skippable `[if ..]` call',
|
|
last_stmt.pos)
|
|
return
|
|
}
|
|
if last_stmt.expr.is_noreturn {
|
|
is_valid_end_of_noreturn_fn = true
|
|
}
|
|
}
|
|
}
|
|
ast.ForStmt {
|
|
if last_stmt.is_inf && last_stmt.stmts.len == 0 {
|
|
is_valid_end_of_noreturn_fn = true
|
|
}
|
|
}
|
|
else {}
|
|
}
|
|
if !is_valid_end_of_noreturn_fn {
|
|
c.error('[noreturn] functions should end with a call to another [noreturn] function, or with an infinite `for {}` loop',
|
|
last_stmt.pos)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
fn uses_return_stmt(stmts []ast.Stmt) bool {
|
|
if stmts.len == 0 {
|
|
return false
|
|
}
|
|
for stmt in stmts {
|
|
match stmt {
|
|
ast.Return {
|
|
return true
|
|
}
|
|
ast.Block {
|
|
if uses_return_stmt(stmt.stmts) {
|
|
return true
|
|
}
|
|
}
|
|
ast.ExprStmt {
|
|
match stmt.expr {
|
|
ast.CallExpr {
|
|
if uses_return_stmt(stmt.expr.or_block.stmts) {
|
|
return true
|
|
}
|
|
}
|
|
ast.MatchExpr {
|
|
for b in stmt.expr.branches {
|
|
if uses_return_stmt(b.stmts) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
ast.SelectExpr {
|
|
for b in stmt.expr.branches {
|
|
if uses_return_stmt(b.stmts) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
ast.IfExpr {
|
|
for b in stmt.expr.branches {
|
|
if uses_return_stmt(b.stmts) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
else {}
|
|
}
|
|
}
|
|
ast.ForStmt {
|
|
if uses_return_stmt(stmt.stmts) {
|
|
return true
|
|
}
|
|
}
|
|
ast.ForCStmt {
|
|
if uses_return_stmt(stmt.stmts) {
|
|
return true
|
|
}
|
|
}
|
|
ast.ForInStmt {
|
|
if uses_return_stmt(stmt.stmts) {
|
|
return true
|
|
}
|
|
}
|
|
else {}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
fn is_noreturn_callexpr(expr ast.Expr) bool {
|
|
if expr is ast.CallExpr {
|
|
return expr.is_noreturn
|
|
}
|
|
return false
|
|
}
|
|
|
|
fn imin(a int, b int) int {
|
|
return if a < b { a } else { b }
|
|
}
|
|
|
|
fn imax(a int, b int) int {
|
|
return if a < b { b } else { a }
|
|
}
|