diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 95100b2280..3605810568 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -305,10 +305,11 @@ pub fn (c &Checker) get_default_fmt(ftyp, typ table.Type) byte { } else if typ.is_pointer() { return `p` } else { - sym := c.table.get_type_symbol(ftyp) + mut sym := c.table.get_type_symbol(ftyp) if sym.kind == .alias { // string aliases should be printable info := sym.info as table.Alias + sym = c.table.get_type_symbol(info.parent_type) if info.parent_type == table.string_type { return `s` } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index f88327a292..8ccf1a4f43 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -562,8 +562,8 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type { right_type := c.expr(infix_expr.right) // right_type = c.unwrap_genric(c.expr(infix_expr.right)) infix_expr.right_type = right_type - right := c.table.get_type_symbol(right_type) - left := c.table.get_type_symbol(left_type) + mut right := c.table.get_type_symbol(right_type) + mut left := c.table.get_type_symbol(left_type) left_default := c.table.get_type_symbol(c.table.mktyp(left_type)) left_pos := infix_expr.left.position() right_pos := infix_expr.right.position() @@ -610,21 +610,47 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type { return table.bool_type } .plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe { // binary operators that expect matching types + if right.info is table.Alias && + (right.info as table.Alias).language != .c && c.mod == c.table.type_to_str(right_type).split('.')[0] { + right = c.table.get_type_symbol((right.info as table.Alias).parent_type) + } + if left.info is table.Alias && + (left.info as table.Alias).language != .c && c.mod == c.table.type_to_str(left_type).split('.')[0] { + left = c.table.get_type_symbol((left.info as table.Alias).parent_type) + } if left.kind in [.array, .array_fixed, .map, .struct_] { if left.has_method(infix_expr.op.str()) { - return_type = left_type + if method := left.find_method(infix_expr.op.str()) { + return_type = method.return_type + } else { + return_type = left_type + } } else { 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`', left_pos) + if left_name == right_name { + c.error('operation `$left_name` $infix_expr.op.str() `$right_name` does not exist, please define it', + left_pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', left_pos) + } } } else if right.kind in [.array, .array_fixed, .map, .struct_] { if right.has_method(infix_expr.op.str()) { - return_type = right_type + if method := right.find_method(infix_expr.op.str()) { + return_type = method.return_type + } else { + return_type = right_type + } } else { 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`', right_pos) + if left_name == right_name { + c.error('operation `$left_name` $infix_expr.op.str() `$right_name` does not exist, please define it', + right_pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', right_pos) + } } } else { promoted_type := c.promote(c.table.unalias_num_type(left_type), c.table.unalias_num_type(right_type)) diff --git a/vlib/v/checker/tests/.gitignore b/vlib/v/checker/tests/.gitignore index 07d7f52ac5..868d19e77b 100644 --- a/vlib/v/checker/tests/.gitignore +++ b/vlib/v/checker/tests/.gitignore @@ -1,3 +1,4 @@ *.v *.c -!*_test.v \ No newline at end of file +!*_test.v +!modules/**/*.v \ No newline at end of file diff --git a/vlib/v/checker/tests/modules/overload_return_type.out b/vlib/v/checker/tests/modules/overload_return_type.out new file mode 100644 index 0000000000..de1dc3e0c9 --- /dev/null +++ b/vlib/v/checker/tests/modules/overload_return_type.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/modules/overload_return_type/main.v:8:11: error: cannot assign `int` to `two` of type `Point` + 6 | one := Point {x:1, y:2} + 7 | mut two := Point {x:5, y:1} + 8 | two = one + two + | ~~~~~~~~~ + 9 | } diff --git a/vlib/v/checker/tests/modules/overload_return_type/main.v b/vlib/v/checker/tests/modules/overload_return_type/main.v new file mode 100644 index 0000000000..fe27e3ee3f --- /dev/null +++ b/vlib/v/checker/tests/modules/overload_return_type/main.v @@ -0,0 +1,9 @@ +module main + +import point { Point } + +fn main() { + one := Point {x:1, y:2} + mut two := Point {x:5, y:1} + two = one + two +} diff --git a/vlib/v/checker/tests/modules/overload_return_type/point.v b/vlib/v/checker/tests/modules/overload_return_type/point.v new file mode 100644 index 0000000000..5f6da09637 --- /dev/null +++ b/vlib/v/checker/tests/modules/overload_return_type/point.v @@ -0,0 +1,11 @@ +module point + +pub struct Point { + mut: + x int + y int +} + +pub fn (a Point) +(b Point) int { + return a.x + b.x +} diff --git a/vlib/v/checker/tests/overload_return_type.out b/vlib/v/checker/tests/overload_return_type.out new file mode 100644 index 0000000000..b15bd4967e --- /dev/null +++ b/vlib/v/checker/tests/overload_return_type.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/overload_return_type.vv:14:11: error: cannot assign `int` to `two` of type `Point` + 12 | mut one := Point {x:1, y:2} + 13 | mut two := Point {x:5, y:1} + 14 | two = one + two + | ~~~~~~~~~ + 15 | } diff --git a/vlib/v/checker/tests/overload_return_type.vv b/vlib/v/checker/tests/overload_return_type.vv new file mode 100644 index 0000000000..f9ae2f11dd --- /dev/null +++ b/vlib/v/checker/tests/overload_return_type.vv @@ -0,0 +1,15 @@ +struct Point { + mut: + x int + y int +} + +fn (a Point) +(b Point) int { + return a.x + b.x +} + +fn main() { + mut one := Point {x:1, y:2} + mut two := Point {x:5, y:1} + two = one + two +} diff --git a/vlib/v/compiler_errors_test.v b/vlib/v/compiler_errors_test.v index 893929045a..7c5bea9eaf 100644 --- a/vlib/v/compiler_errors_test.v +++ b/vlib/v/compiler_errors_test.v @@ -23,6 +23,7 @@ struct TaskDescription { mut: is_error bool is_skipped bool + is_module bool expected string found___ string took time.Duration @@ -33,25 +34,28 @@ fn test_all() { vroot := os.dir(vexe) os.chdir(vroot) classic_dir := 'vlib/v/checker/tests' - classic_tests := get_tests_in_dir(classic_dir) + classic_tests := get_tests_in_dir(classic_dir, false) global_dir := '$classic_dir/globals' - global_tests := get_tests_in_dir(global_dir) + global_tests := get_tests_in_dir(global_dir, false) + module_dir := '$classic_dir/modules' + module_tests := get_tests_in_dir(module_dir, true) run_dir := '$classic_dir/run' - run_tests := get_tests_in_dir(run_dir) + run_tests := get_tests_in_dir(run_dir, false) parser_dir := 'vlib/v/parser/tests' - parser_tests := get_tests_in_dir(parser_dir) + parser_tests := get_tests_in_dir(parser_dir, false) // -prod so that warns are errors mut tasks := []TaskDescription{} - tasks << new_tasks(vexe, classic_dir, '-prod', '.out', classic_tests) - tasks << new_tasks(vexe, global_dir, '--enable-globals', '.out', global_tests) + tasks << new_tasks(vexe, classic_dir, '-prod', '.out', classic_tests, false) + tasks << new_tasks(vexe, global_dir, '--enable-globals', '.out', global_tests, false) tasks << - new_tasks(vexe, classic_dir, '--enable-globals run', '.run.out', ['globals_error.vv']) - tasks << new_tasks(vexe, run_dir, 'run', '.run.out', run_tests) - tasks << new_tasks(vexe, parser_dir, '-prod', '.out', parser_tests) + new_tasks(vexe, classic_dir, '--enable-globals run', '.run.out', ['globals_error.vv'], false) + tasks << new_tasks(vexe, module_dir, '-prod run', '.out', module_tests, true) + tasks << new_tasks(vexe, run_dir, 'run', '.run.out', run_tests, false) + tasks << new_tasks(vexe, parser_dir, '-prod', '.out', parser_tests, false) tasks.run() } -fn new_tasks(vexe, dir, voptions, result_extension string, tests []string) []TaskDescription { +fn new_tasks(vexe, dir, voptions, result_extension string, tests []string, is_module bool) []TaskDescription { paths := vtest.filter_vtest_only(tests, { basepath: dir }) @@ -63,6 +67,7 @@ fn new_tasks(vexe, dir, voptions, result_extension string, tests []string) []Tas voptions: voptions result_extension: result_extension path: path + is_module: is_module } } return res @@ -155,6 +160,11 @@ fn (mut task TaskDescription) execute() { } task.expected = clean_line_endings(expected) task.found___ = clean_line_endings(res.output) + $if windows { + if task.is_module { + task.found___ = task.found___.replace_once('\\', '/') + } + } if task.expected != task.found___ { task.is_error = true } @@ -178,11 +188,16 @@ fn diff_content(s1, s2 string) { println('============\n') } -fn get_tests_in_dir(dir string) []string { +fn get_tests_in_dir(dir string, is_module bool) []string { files := os.ls(dir) or { panic(err) } - mut tests := files.filter(it.ends_with('.vv')) + mut tests := files + if !is_module { + tests = files.filter(it.ends_with('.vv')) + } else { + tests = files.filter(!it.ends_with('.out')) + } tests.sort() return tests } diff --git a/vlib/v/gen/auto_str_methods.v b/vlib/v/gen/auto_str_methods.v index b5edd9085f..f8c2215f9a 100644 --- a/vlib/v/gen/auto_str_methods.v +++ b/vlib/v/gen/auto_str_methods.v @@ -8,8 +8,12 @@ import v.util // already generated styp, reuse it fn (mut g Gen) gen_str_for_type_with_styp(typ table.Type, styp string) string { - sym := g.table.get_type_symbol(typ) - str_fn_name := styp_to_str_fn_name(styp) + mut sym := g.table.get_type_symbol(typ) + mut str_fn_name := styp_to_str_fn_name(styp) + if sym.info is table.Alias { + sym = g.table.get_type_symbol((sym.info as table.Alias).parent_type) + str_fn_name = styp_to_str_fn_name(sym.name.replace('.', '__')) + } sym_has_str_method, str_method_expects_ptr, str_nr_args := sym.str_method_info() // generate for type if sym_has_str_method && str_method_expects_ptr && str_nr_args == 1 { diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 7a63cb6ce6..b791f24994 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -2542,9 +2542,16 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { a := left_sym.name[0].is_capital() || left_sym.name.contains('.') b := left_sym.kind != .alias c := left_sym.kind == .alias && (left_sym.info as table.Alias).language == .c - if node.op in [.plus, .minus, .mul, .div, .mod] && ((a && b) || c) { + // Check if aliased type is a struct + d := !b && + g.typ((left_sym.info as table.Alias).parent_type).split('__').last()[0].is_capital() + if node.op in [.plus, .minus, .mul, .div, .mod] && ((a && b) || c || d) { // Overloaded operators - g.write(g.typ(left_type)) + g.write(g.typ(if !d { + left_type + } else { + (left_sym.info as table.Alias).parent_type + })) g.write('_') g.write(util.replace_op(node.op.str())) g.write('(') @@ -4119,7 +4126,10 @@ fn (g &Gen) sort_structs(typesa []table.TypeSymbol) []table.TypeSymbol { } fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype table.Type) ?bool { - sym := g.table.get_type_symbol(etype) + mut sym := g.table.get_type_symbol(etype) + if sym.info is table.Alias { + sym = g.table.get_type_symbol((sym.info as table.Alias).parent_type) + } sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() if etype.has_flag(.variadic) { str_fn_name := g.gen_str_for_type(etype) diff --git a/vlib/v/tests/modules/methods_struct_another_module/methods_struct_test.v b/vlib/v/tests/modules/methods_struct_another_module/methods_struct_test.v new file mode 100644 index 0000000000..0c17cabdc8 --- /dev/null +++ b/vlib/v/tests/modules/methods_struct_another_module/methods_struct_test.v @@ -0,0 +1,16 @@ +module main + +import point { Point } + +fn test_operator_overloading() { + one := Point {x:1, y:2} + two := Point {x:5, y:1} + sum := one + two + assert sum.x == 6 + assert sum.y == 3 +} + +fn test_str_method() { + one := Point {x:1, y:2} + assert '$one' == '1 2' +} \ No newline at end of file diff --git a/vlib/v/tests/modules/methods_struct_another_module/point.v b/vlib/v/tests/modules/methods_struct_another_module/point.v new file mode 100644 index 0000000000..5ba3697ba9 --- /dev/null +++ b/vlib/v/tests/modules/methods_struct_another_module/point.v @@ -0,0 +1,18 @@ +module point + +pub struct Point { + pub mut: + x int + y int +} + +pub fn (a Point) +(b Point) Point { + return Point { + x: a.x + b.x + y: a.y + b.y + } +} + +pub fn (a Point) str() string { + return '${a.x} ${a.y}' +} \ No newline at end of file