diff --git a/cmd/tools/vtimeout_test.v b/cmd/tools/vtimeout_test.v index c8704a9e5e..2e252d982e 100644 --- a/cmd/tools/vtimeout_test.v +++ b/cmd/tools/vtimeout_test.v @@ -1,3 +1,4 @@ +// vtest retry: 3 import os import time diff --git a/doc/docs.md b/doc/docs.md index 24e98f1fab..f0bb2ae461 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -6138,6 +6138,10 @@ that are substituted at compile time: - `@BUILD_DATE` => replaced with the build date, for example '2024-09-13' . - `@BUILD_TIME` => replaced with the build time, for example '12:32:07' . - `@BUILD_TIMESTAMP` => replaced with the build timestamp, for example '1726219885' . +- `@OS` => replaced with the OS type, for example 'linux' . +- `@CCOMPILER` => replaced with the C compiler type, for example 'gcc' . +- `@BACKEND` => replaced with current language backend, for example 'c' or 'golang' . +- `@PLATFORM` => replaced with the platform type, for example 'amd64' . Note: `@BUILD_DATE`, `@BUILD_TIME`, `@BUILD_TIMESTAMP` represent times in the UTC timezone. By default, they are based on the current time of the compilation/build. They can be overridden by setting the environment variable `SOURCE_DATE_EPOCH`. That is also useful while making diff --git a/vlib/flag/flag_to.v b/vlib/flag/flag_to.v index 876d65d995..6baa1992d3 100644 --- a/vlib/flag/flag_to.v +++ b/vlib/flag/flag_to.v @@ -934,9 +934,7 @@ pub fn (fm FlagMapper) to_struct[T](defaults ?T) !T { .f64() } $else $if field.typ is bool { if arg := f.arg { - if arg != '' { - return error('can not assign `${arg}` to bool field `${field.name}`') - } + return error('can not assign `${arg}` to bool field `${field.name}`') } result.$(field.name) = !the_default.$(field.name) } $else $if field.typ is string { @@ -1037,7 +1035,7 @@ fn (mut fm FlagMapper) map_v(flag_ctx FlagContext, field StructField) !bool { next := flag_ctx.next if field.hints.has(.is_bool) { - if flag_name == field.match_name { + if flag_name == field.match_name || flag_name == field.short { arg := if flag_raw.contains('=') { flag_raw.all_after('=') } else { '' } if arg != '' { return error('flag `${flag_raw}` can not be assigned to bool field "${field.name}"') diff --git a/vlib/flag/flag_to_tail_bool_test.v b/vlib/flag/flag_to_tail_bool_test.v index a67e923f44..d28176c2ea 100644 --- a/vlib/flag/flag_to_tail_bool_test.v +++ b/vlib/flag/flag_to_tail_bool_test.v @@ -1,14 +1,16 @@ import flag -const args_bool_short = ['some.exe', '-h'] -const args_bool_long = ['some.exe', '-help'] - struct CliOptions { show_help bool @[long: 'help'; short: h] } -fn test_short_tail_bool() { - cli_options, unmatched := flag.to_struct[CliOptions](args_bool_short, +struct CliOptions2 { + show_help bool @[long: 'help'; short: h] + long string +} + +fn test_v_style_short_tail_bool() { + cli_options, unmatched := flag.to_struct[CliOptions](['some.exe', '-h'], skip: 1 style: .v mode: .relaxed @@ -24,8 +26,8 @@ fn test_short_tail_bool() { } } -fn test_long_tail_bool() { - cli_options, unmatched := flag.to_struct[CliOptions](args_bool_long, +fn test_v_style_long_tail_bool() { + cli_options, unmatched := flag.to_struct[CliOptions](['some.exe', '-help'], skip: 1 style: .v mode: .relaxed @@ -40,3 +42,39 @@ fn test_long_tail_bool() { assert false } } + +fn test_v_style_short_bool() { + cli_options, unmatched := flag.to_struct[CliOptions2](['some.exe', '-h', '-long', 'val'], + skip: 1 + style: .v + mode: .relaxed + )! + + if unmatched.len > 0 { + assert false + } + if cli_options.show_help { + assert true + } else { + assert false + } + assert cli_options.long == 'val' +} + +fn test_v_style_long_bool() { + cli_options, unmatched := flag.to_struct[CliOptions2](['some.exe', '-help', '-long', 'val'], + skip: 1 + style: .v + mode: .relaxed + )! + + if unmatched.len > 0 { + assert false + } + if cli_options.show_help { + assert true + } else { + assert false + } + assert cli_options.long == 'val' +} diff --git a/vlib/net/tcp.c.v b/vlib/net/tcp.c.v index 10dd28c54d..ca24b81b86 100644 --- a/vlib/net/tcp.c.v +++ b/vlib/net/tcp.c.v @@ -158,10 +158,13 @@ pub fn (c TcpConn) read_ptr(buf_ptr &u8, len int) !int { -1 } } - $if trace_tcp ? { - eprintln('<<< TcpConn.read_ptr | c.sock.handle: ${c.sock.handle} | buf_ptr: ${ptr_str(buf_ptr)} len: ${len} | res: ${res}') - } + ecode := error_code() if res > 0 { + $if trace_tcp ? { + eprintln( + '<<< TcpConn.read_ptr | c.sock.handle: ${c.sock.handle} | buf_ptr: ${ptr_str(buf_ptr)} | len: ${len} | res: ${res} |\n' + + unsafe { buf_ptr.vstring_with_len(len) }) + } $if trace_tcp_data_read ? { eprintln( '<<< TcpConn.read_ptr | 1 data.len: ${res:6} | hex: ${unsafe { buf_ptr.vbytes(res) }.hex()} | data: ' + @@ -169,7 +172,7 @@ pub fn (c TcpConn) read_ptr(buf_ptr &u8, len int) !int { } return res } - code := if should_ewouldblock { int(error_ewouldblock) } else { error_code() } + code := if should_ewouldblock { int(error_ewouldblock) } else { ecode } if code in [int(error_ewouldblock), int(error_eagain), C.EINTR] { c.wait_for_read()! res = $if is_coroutine ? { @@ -178,7 +181,9 @@ pub fn (c TcpConn) read_ptr(buf_ptr &u8, len int) !int { C.recv(c.sock.handle, voidptr(buf_ptr), len, msg_dontwait) } $if trace_tcp ? { - eprintln('<<< TcpConn.read_ptr | c.sock.handle: ${c.sock.handle} | buf_ptr: ${ptr_str(buf_ptr)} len: ${len} | res: ${res}') + eprintln( + '<<< TcpConn.read_ptr | c.sock.handle: ${c.sock.handle} | buf_ptr: ${ptr_str(buf_ptr)} | len: ${len} | res: ${res} | code: ${code} |\n' + + unsafe { buf_ptr.vstring_with_len(len) }) } $if trace_tcp_data_read ? { if res > 0 { @@ -234,11 +239,11 @@ pub fn (mut c TcpConn) write_ptr(b &u8, len int) !int { } $else { C.send(c.sock.handle, ptr, remaining, msg_nosignal) } + code := error_code() $if trace_tcp_data_write ? { eprintln('>>> TcpConn.write_ptr | data chunk, total_sent: ${total_sent:6}, remaining: ${remaining:6}, ptr: ${voidptr(ptr):x} => sent: ${sent:6}') } if sent < 0 { - code := error_code() $if trace_tcp_send_failures ? { eprintln('>>> TcpConn.write_ptr | send_failure, data.len: ${len:6}, total_sent: ${total_sent:6}, remaining: ${remaining:6}, ptr: ${voidptr(ptr):x}, c.write_timeout: ${c.write_timeout:3} => sent: ${sent:6}, error code: ${code:3}') } @@ -456,9 +461,8 @@ pub fn (mut l TcpListener) accept_only() !&TcpConn { } $else { C.accept(l.sock.handle, 0, 0) } - + code := error_code() if !l.is_blocking && new_handle <= 0 { - code := error_code() if code in [int(error_einprogress), int(error_ewouldblock), int(error_eagain), C.EINTR] { l.wait_for_accept()! new_handle = $if is_coroutine ? { @@ -642,10 +646,10 @@ fn (mut s TcpSocket) connect(a Addr) ! { } $else { C.connect(s.handle, voidptr(&a), a.len()) } + ecode := error_code() if res == 0 { return } - ecode := error_code() // On nix non-blocking sockets we expect einprogress // On windows we expect res == -1 && error_code() == ewouldblock if (is_windows && ecode == int(error_ewouldblock)) || (!is_windows && res == -1 diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index e6858794d9..3d01708eed 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -2190,16 +2190,15 @@ pub fn (mut t Table) unwrap_generic_type_ex(typ Type, generic_names []string, co Interface { // resolve generic types inside methods mut imethods := ts.info.methods.clone() + gn_names := t.get_real_generic_names(typ, generic_names) for mut method in imethods { - if unwrap_typ := t.convert_generic_type(method.return_type, generic_names, - concrete_types) + if unwrap_typ := t.convert_generic_type(method.return_type, gn_names, + concrete_types[..gn_names.len]) { method.return_type = unwrap_typ } for mut param in method.params { - if unwrap_typ := t.convert_generic_type(param.typ, generic_names, - concrete_types) - { + if unwrap_typ := t.convert_generic_type(param.typ, gn_names, concrete_types) { param.typ = unwrap_typ } } @@ -2289,6 +2288,7 @@ pub fn (mut t Table) generic_insts_to_concrete() { fields[i].typ = t.unwrap_generic_type(fields[i].typ, generic_names, info.concrete_types) } + if t_typ := t.convert_generic_type(fields[i].typ, generic_names, info.concrete_types) { @@ -2464,6 +2464,18 @@ pub fn (mut t Table) generic_insts_to_concrete() { } } +// Extracts all generic names from Type[B] => B when [B] is present +// otherwise generic_names is returned +pub fn (t &Table) get_real_generic_names(typ Type, generic_names []string) []string { + if typ.has_flag(.generic) { + typ_name := t.type_to_str(typ) + if typ_name.contains('>[') { + return typ_name.split('>[')[1].all_before_last(']').split(',') + } + } + return generic_names +} + // Extracts all type names from given types, notice that MultiReturn will be decompose // and will not included in returned string pub fn (t &Table) get_generic_names(generic_types []Type) []string { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 0233816cef..3fccd9661b 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -4030,6 +4030,18 @@ fn (mut c Checker) at_expr(mut node ast.AtExpr) ast.Type { .build_timestamp { node.val = util.stable_build_time.unix().str() } + .os { + node.val = pref.get_host_os().lower() + } + .ccompiler { + node.val = c.pref.ccompiler_type.str() + } + .backend { + node.val = c.pref.backend.str() + } + .platform { + node.val = c.pref.arch.str() + } .unknown { c.error('unknown @ identifier: ${node.name}. Available identifiers: ${token.valid_at_tokens}', node.pos) diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 69eff4fead..3b2b3aa245 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -1472,6 +1472,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. node.pos) } mut has_decompose := false + mut nr_multi_values := 0 for i, mut call_arg in node.args { if func.params.len == 0 { continue @@ -1482,21 +1483,22 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. if !func.is_variadic && has_decompose { c.error('cannot have parameter after array decompose', node.pos) } + param_i := i + nr_multi_values param := if func.is_variadic && i >= func.params.len - 1 { func.params.last() } else { - func.params[i] + func.params[param_i] } // registers if the arg must be passed by ref to disable auto deref args call_arg.should_be_ptr = param.typ.is_ptr() && !param.is_mut if func.is_variadic && call_arg.expr is ast.ArrayDecompose { - if i > func.params.len - 1 { + if param_i > func.params.len - 1 { c.error('too many arguments in call to `${func.name}`', node.pos) } } has_decompose = call_arg.expr is ast.ArrayDecompose already_checked := node.language != .js && call_arg.expr is ast.CallExpr - if func.is_variadic && i >= func.params.len - 1 { + if func.is_variadic && param_i >= func.params.len - 1 { param_sym := c.table.sym(param.typ) mut expected_type := param.typ if param_sym.info is ast.Array { @@ -1688,15 +1690,16 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. multi_param := if func.is_variadic && i >= func.params.len - 1 { func.params.last() } else { - func.params[n + i] + func.params[n + param_i] } c.check_expected_call_arg(curr_arg, c.unwrap_generic(multi_param.typ), node.language, call_arg) or { - c.error('${err.msg()} in argument ${i + n + 1} to `${fn_name}` from ${c.table.type_to_str(arg_typ)}', + c.error('${err.msg()} in argument ${param_i + n + 1} to `${fn_name}` from ${c.table.type_to_str(arg_typ)}', call_arg.pos) continue out } } + nr_multi_values += arg_typ_sym.info.types.len - 1 continue } else if param_typ_sym.info is ast.Struct && arg_typ_sym.info is ast.Struct && param_typ_sym.info.is_anon { @@ -1788,13 +1791,14 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. continue } */ - c.error('${err.msg()} in argument ${i + 1} to `${fn_name}`', call_arg.pos) + c.error('${err.msg()} in argument ${i + nr_multi_values + 1} to `${fn_name}`', + call_arg.pos) } if final_param_sym.kind == .struct && arg_typ !in [ast.voidptr_type, ast.nil_type] && !c.check_multiple_ptr_match(arg_typ, param.typ, param, call_arg) { got_typ_str, expected_typ_str := c.get_string_names_of(arg_typ, param.typ) - c.error('cannot use `${got_typ_str}` as `${expected_typ_str}` in argument ${i + 1} to `${fn_name}`', - call_arg.pos) + c.error('cannot use `${got_typ_str}` as `${expected_typ_str}` in argument ${i + + nr_multi_values + 1} to `${fn_name}`', call_arg.pos) } // Warn about automatic (de)referencing, which will be removed soon. if func.language != .c && !c.inside_unsafe && !(call_arg.is_mut && param.is_mut) { @@ -2924,6 +2928,17 @@ fn (mut c Checker) check_expected_arg_count(mut node ast.CallExpr, f &ast.Fn) ! return error('') } } + } else if node.args.len > 1 && node.args.any(it.expr is ast.CallExpr + && it.expr.nr_ret_values > 1) { + mut check_args := 0 + for arg in node.args { + if arg.expr is ast.CallExpr && arg.expr.nr_ret_values > 0 { + check_args += arg.expr.nr_ret_values + } else { + check_args += 1 + } + } + nr_args = check_args } if min_required_params < 0 { min_required_params = 0 @@ -2966,7 +2981,7 @@ fn (mut c Checker) check_expected_arg_count(mut node ast.CallExpr, f &ast.Fn) ! ) return error('') } else if !f.is_variadic && nr_args > nr_params { - unexpected_args_pos := node.args[min_required_params].pos.extend(node.args.last().pos) + unexpected_args_pos := node.args[int_min(min_required_params, node.args.len - 1)].pos.extend(node.args.last().pos) // c.error('3expected ${min_required_params} arguments, but got ${nr_args}', unexpected_args_pos) c.fn_call_error_have_want( nr_params: min_required_params diff --git a/vlib/v/checker/tests/multi_return_arg_missing_err.out b/vlib/v/checker/tests/multi_return_arg_missing_err.out new file mode 100644 index 0000000000..bf1936a620 --- /dev/null +++ b/vlib/v/checker/tests/multi_return_arg_missing_err.out @@ -0,0 +1,18 @@ +vlib/v/checker/tests/multi_return_arg_missing_err.vv:12:14: error: expected 2 arguments, but got 3 + 10 | + 11 | fn main() { + 12 | expect_2(1, returning_2()) + | ~~~~~~~~~~~~~ + 13 | expect_2(returning_2(), 1) + 14 | expect_4(returning_2(), returning_2()) +Details: have (int literal, (int, int)) + want (int, int) +vlib/v/checker/tests/multi_return_arg_missing_err.vv:13:26: error: expected 2 arguments, but got 3 + 11 | fn main() { + 12 | expect_2(1, returning_2()) + 13 | expect_2(returning_2(), 1) + | ^ + 14 | expect_4(returning_2(), returning_2()) + 15 | } +Details: have ((int, int), int literal) + want (int, int) diff --git a/vlib/v/checker/tests/multi_return_arg_missing_err.vv b/vlib/v/checker/tests/multi_return_arg_missing_err.vv new file mode 100644 index 0000000000..64a8db1a9f --- /dev/null +++ b/vlib/v/checker/tests/multi_return_arg_missing_err.vv @@ -0,0 +1,15 @@ +fn expect_2(a int, b int) { +} + +fn expect_4(a int, b int, c int, d int) { +} + +fn returning_2() (int, int) { + return 1, 1 +} + +fn main() { + expect_2(1, returning_2()) + expect_2(returning_2(), 1) + expect_4(returning_2(), returning_2()) +} diff --git a/vlib/v/checker/tests/multi_return_err.out b/vlib/v/checker/tests/multi_return_err.out index 86848492be..a16e11e40c 100644 --- a/vlib/v/checker/tests/multi_return_err.out +++ b/vlib/v/checker/tests/multi_return_err.out @@ -5,24 +5,20 @@ vlib/v/checker/tests/multi_return_err.vv:18:10: error: cannot use `f64` as `int` | ~~~~~~~~~~ 19 | my_func3(my_func2(), 'foo') 20 | my_func4('foo', my_func2()) -vlib/v/checker/tests/multi_return_err.vv:19:2: error: expected 3 arguments, but got 2 +vlib/v/checker/tests/multi_return_err.vv:19:11: error: cannot use `f64` as `int` in argument 2 to `my_func3` from (int, f64) 17 | fn main() { 18 | my_func(my_func2()) 19 | my_func3(my_func2(), 'foo') - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | ~~~~~~~~~~ 20 | my_func4('foo', my_func2()) 21 | my_func(my_func5()) -Details: have ((int, f64), string) - want (int, int, string) -vlib/v/checker/tests/multi_return_err.vv:20:2: error: expected 3 arguments, but got 2 +vlib/v/checker/tests/multi_return_err.vv:20:18: error: cannot use `f64` as `int` in argument 3 to `my_func4` from (int, f64) 18 | my_func(my_func2()) 19 | my_func3(my_func2(), 'foo') 20 | my_func4('foo', my_func2()) - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | ~~~~~~~~~~ 21 | my_func(my_func5()) 22 | my_func(my_func6()) -Details: have (string, (int, f64)) - want (string, int, int) vlib/v/checker/tests/multi_return_err.vv:21:2: error: expected 2 arguments, but got 1 19 | my_func3(my_func2(), 'foo') 20 | my_func4('foo', my_func2()) diff --git a/vlib/v/fmt/tests/import_duplicate_input.vv b/vlib/v/fmt/tests/import_duplicate_input.vv deleted file mode 100644 index 2d248cb482..0000000000 --- a/vlib/v/fmt/tests/import_duplicate_input.vv +++ /dev/null @@ -1,18 +0,0 @@ -import math -import os -import math -// keep comment -import gg -import gg { MouseButton } -import time { Duration } -import time { Duration } -import math.complex { Complex } -import math.complex { Complex } - -const mypi = math.pi -const mb = MouseButton{} -const complex = Complex{} - -fn main() { - println(os.path_separator) -} diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index e03e10d3a0..79cd19c325 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -1107,6 +1107,7 @@ pub fn (mut g Gen) get_sumtype_variant_name(typ ast.Type, sym ast.TypeSymbol) st pub fn (mut g Gen) write_typeof_functions() { g.writeln('') g.writeln('// >> typeof() support for sum types / interfaces') + mut already_generated_ifaces := map[string]bool{} for ityp, sym in g.table.type_symbols { if sym.kind == .sum_type { if g.pref.skip_unused && sym.idx !in g.table.used_features.used_syms { @@ -1177,6 +1178,10 @@ pub fn (mut g Gen) write_typeof_functions() { if g.pref.skip_unused && sym.idx !in g.table.used_features.used_syms { continue } + if sym.cname in already_generated_ifaces { + continue + } + already_generated_ifaces[sym.cname] = true g.definitions.writeln('${g.static_non_parallel}char * v_typeof_interface_${sym.cname}(int sidx);') if g.pref.parallel_cc { g.extern_out.writeln('extern char * v_typeof_interface_${sym.cname}(int sidx);') @@ -1805,11 +1810,15 @@ static inline void __${sym.cname}_pushval(${sym.cname} ch, ${push_arg} val) { interface_non_generic_syms << sym } } + mut already_generated_ifaces := map[string]bool{} for sym in interface_non_generic_syms { if g.pref.skip_unused && sym.idx !in g.table.used_features.used_syms { continue } - g.write_interface_typesymbol_declaration(sym) + if sym.cname !in already_generated_ifaces { + g.write_interface_typesymbol_declaration(sym) + already_generated_ifaces[sym.cname] = true + } } } @@ -7859,6 +7868,7 @@ fn (mut g Gen) interface_table() string { } mut sb := strings.new_builder(100) mut conversion_functions := strings.new_builder(100) + mut already_generated_ifaces := map[string]bool{} interfaces := g.table.type_symbols.filter(it.kind == .interface && it.info is ast.Interface) for isym in interfaces { inter_info := isym.info as ast.Interface @@ -7868,6 +7878,10 @@ fn (mut g Gen) interface_table() string { if g.pref.skip_unused && isym.idx !in g.table.used_features.used_syms { continue } + if isym.cname in already_generated_ifaces { + continue + } + already_generated_ifaces[isym.cname] = true // interface_name is for example Speaker interface_name := isym.cname // generate a struct that references interface methods diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index a2156e7a49..a7a286a72b 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -432,6 +432,9 @@ fn (mut w Walker) expr(node_ ast.Expr) { if !w.uses_array && !w.is_direct_array_access { w.uses_array = true } + if node.elem_type.has_flag(.option) { + w.used_option++ + } } ast.Assoc { w.exprs(node.exprs) @@ -1180,6 +1183,9 @@ pub fn (mut w Walker) mark_by_sym(isym ast.TypeSymbol) { w.mark_by_type(isym.info.key_type) w.mark_by_type(isym.info.value_type) w.features.used_maps++ + if isym.info.value_type.has_flag(.option) { + w.used_option++ + } } ast.Alias { w.mark_by_type(isym.info.parent_type) @@ -1295,9 +1301,6 @@ fn (mut w Walker) mark_resource_dependencies() { w.fn_by_name('strings.new_builder') w.uses_free[ast.string_type] = true } - if w.uses_eq { - w.fn_by_name('fast_string_eq') - } if w.features.auto_str_ptr { w.fn_by_name('isnil') w.fn_by_name('tos4') @@ -1594,6 +1597,9 @@ pub fn (mut w Walker) finalize(include_panic_deps bool) { w.mark_by_sym_name('StrIntpData') w.mark_by_sym_name('StrIntpMem') } + if w.uses_eq { + w.fn_by_name('fast_string_eq') + } // remove unused symbols w.remove_unused_fn_generic_types() diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index 33e2bc2207..67ca8dfd4f 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -511,6 +511,10 @@ fn (mut p Parser) at() ast.AtExpr { '@BUILD_DATE' { token.AtKind.build_date } '@BUILD_TIME' { token.AtKind.build_time } '@BUILD_TIMESTAMP' { token.AtKind.build_timestamp } + '@OS' { token.AtKind.os } + '@CCOMPILER' { token.AtKind.ccompiler } + '@BACKEND' { token.AtKind.backend } + '@PLATFORM' { token.AtKind.platform } else { token.AtKind.unknown } } expr := ast.AtExpr{ diff --git a/vlib/v/parser/module.v b/vlib/v/parser/module.v index 69df3d099f..472b7e7c2e 100644 --- a/vlib/v/parser/module.v +++ b/vlib/v/parser/module.v @@ -310,6 +310,11 @@ fn (mut p Parser) import_syms(mut parent ast.Import) { for p.tok.kind == .name { pos := p.tok.pos() alias := p.check_name() + if alias in p.imported_symbols { + p.error_with_pos('cannot register symbol `${alias}`, it was already imported', + pos) + return + } p.imported_symbols[alias] = parent.mod + '.' + alias // so we can work with this in fmt+checker parent.syms << ast.ImportSymbol{ diff --git a/vlib/v/parser/tests/module_import_same_symbol2_err.out b/vlib/v/parser/tests/module_import_same_symbol2_err.out new file mode 100644 index 0000000000..db6ba12a74 --- /dev/null +++ b/vlib/v/parser/tests/module_import_same_symbol2_err.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/module_import_same_symbol2_err.vv:1:20: error: cannot register symbol `max`, it was already imported + 1 | import math { max, max } + | ~~~ diff --git a/vlib/v/parser/tests/module_import_same_symbol2_err.vv b/vlib/v/parser/tests/module_import_same_symbol2_err.vv new file mode 100644 index 0000000000..9a6c870663 --- /dev/null +++ b/vlib/v/parser/tests/module_import_same_symbol2_err.vv @@ -0,0 +1 @@ +import math { max, max } diff --git a/vlib/v/parser/tests/module_import_same_symbol_err.out b/vlib/v/parser/tests/module_import_same_symbol_err.out new file mode 100644 index 0000000000..1f2a754a02 --- /dev/null +++ b/vlib/v/parser/tests/module_import_same_symbol_err.out @@ -0,0 +1,4 @@ +vlib/v/parser/tests/module_import_same_symbol_err.vv:2:17: error: cannot register symbol `max`, it was already imported + 1 | import math { max } + 2 | import arrays { max } + | ~~~ diff --git a/vlib/v/parser/tests/module_import_same_symbol_err.vv b/vlib/v/parser/tests/module_import_same_symbol_err.vv new file mode 100644 index 0000000000..88bcf786e9 --- /dev/null +++ b/vlib/v/parser/tests/module_import_same_symbol_err.vv @@ -0,0 +1,2 @@ +import math { max } +import arrays { max } diff --git a/vlib/v/scanner/tests/unknown_comptime_var_err.out b/vlib/v/scanner/tests/unknown_comptime_var_err.out index 3c2f9df3da..861ea2e093 100644 --- a/vlib/v/scanner/tests/unknown_comptime_var_err.out +++ b/vlib/v/scanner/tests/unknown_comptime_var_err.out @@ -5,4 +5,5 @@ vlib/v/scanner/tests/unknown_comptime_var_err.vv:2:9: error: @ must be used befo 3 | } Details: available compile time variables: @VROOT, @VMODROOT, @VEXEROOT, @FN, @METHOD, @MOD, @STRUCT, @VEXE, @FILE, @DIR, @LINE, @COLUMN, @VHASH, @VCURRENTHASH, @VMOD_FILE, @VMODHASH, -@FILE_LINE, @LOCATION, @BUILD_DATE, @BUILD_TIME, @BUILD_TIMESTAMP +@FILE_LINE, @LOCATION, @BUILD_DATE, @BUILD_TIME, @BUILD_TIMESTAMP, @OS, @CCOMPILER, +@BACKEND, @PLATFORM diff --git a/vlib/v/tests/comptime/comptime_at_test.v b/vlib/v/tests/comptime/comptime_at_test.v index a6a761bc39..eb680717c8 100644 --- a/vlib/v/tests/comptime/comptime_at_test.v +++ b/vlib/v/tests/comptime/comptime_at_test.v @@ -198,3 +198,27 @@ fn test_at_build_date_time_timestamp() { now_utc := dump(time.utc().unix()) assert now_utc >= bts.i64() } + +fn test_at_os() { + println('Current OS is ${@OS}') + assert @OS in ['termux', 'android', 'wasm32_emscripten', 'linux', 'ios', 'macos', 'windows', + 'freebsd', 'openbsd', 'netbsd', 'dragonfly', 'serenity', 'plan9', 'vinix', 'solaris', 'haiku', + 'js_node', 'js_freestanding', 'js_browser'] +} + +fn test_at_ccompiler() { + println('Current C Compiler is ${@CCOMPILER}') + assert @CCOMPILER in ['gcc', 'tinyc', 'clang', 'emcc', 'mingw', 'msvc', 'cplusplus'] +} + +fn test_at_backend() { + println('Current language backend is ${@BACKEND}') + assert @BACKEND in ['c', 'golang', 'interpret', 'js_node', 'js_browser', 'js_freestanding', + 'native', 'wasm'] +} + +fn test_at_platform() { + println('Current Platform is ${@PLATFORM}') + assert @PLATFORM in ['amd64', 'arm64', 'arm32', 'rv64', 'rv32', 'i386', 's390x', 'ppc64le', + 'loongarch64', 'js_node', 'js_browser', 'js_freestanding', 'wasm32'] +} diff --git a/vlib/v/tests/generics/generic_interface_field_test.v b/vlib/v/tests/generics/generic_interface_field_test.v new file mode 100644 index 0000000000..752a6acb28 --- /dev/null +++ b/vlib/v/tests/generics/generic_interface_field_test.v @@ -0,0 +1,79 @@ +pub struct Range[T] { +pub: + from T + to T + inclusive bool +pub mut: + step T = T(1) + i T +mut: + started bool +} + +pub fn (mut r Range[T]) next[T]() ?T { + if !r.started { + r.started = true + assert r.step > 0 + if r.from > r.to { + r.step = -r.step + } + r.i = r.from + } + i := r.i + next_i := i + r.step + if r.inclusive { + if r.step < 0 && i < r.to { + return none + } + if r.step > 0 && i > r.to { + return none + } + } else { + if r.step < 0 && i <= r.to { + return none + } + if r.step > 0 && i >= r.to { + return none + } + } + r.i = next_i + return i +} + +pub interface Iterator[T] { +mut: + next() ?T +} + +pub struct Zip[A, B] { +mut: + a Iterator[A] + b Iterator[B] +} + +pub struct Pair[A, B] { + a A + b B +} + +pub fn (mut z Zip[A, B]) next[A, B]() ?Pair[A, B] { + a := z.a.next()? + b := z.b.next()? + return Pair[A, B]{a, b} +} + +fn test_main() { + mut r1 := Range{ + from: 5 + to: 10 + } + _ := Iterator[int](r1) + + mut r2 := Range{ + from: 10 + to: 5 + } + _ := Iterator[int](r2) + + _ := Zip[int, int]{} +} diff --git a/vlib/v/tests/options/option_map_val_test.v b/vlib/v/tests/options/option_map_val_test.v new file mode 100644 index 0000000000..fb045bba90 --- /dev/null +++ b/vlib/v/tests/options/option_map_val_test.v @@ -0,0 +1,6 @@ +fn test_main() { + mut m := map[int]?u32{} + if c := m[0] { + println('c: ${c} not none!') + } +} diff --git a/vlib/v/tests/skip_unused/option_array_init.run.out b/vlib/v/tests/skip_unused/option_array_init.run.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vlib/v/tests/skip_unused/option_array_init.skip_unused.run.out b/vlib/v/tests/skip_unused/option_array_init.skip_unused.run.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vlib/v/tests/skip_unused/option_array_init.vv b/vlib/v/tests/skip_unused/option_array_init.vv new file mode 100644 index 0000000000..d63fbd543a --- /dev/null +++ b/vlib/v/tests/skip_unused/option_array_init.vv @@ -0,0 +1,6 @@ +fn main() { + mut a := []?u32{len: 0xFF} + if c := a[0] { + println('c: ${c} not none!') + } +} diff --git a/vlib/v/token/token.v b/vlib/v/token/token.v index 386d874cd6..f8fcad1c47 100644 --- a/vlib/v/token/token.v +++ b/vlib/v/token/token.v @@ -189,6 +189,10 @@ pub enum AtKind { build_date build_time build_timestamp + os + ccompiler + backend + platform } pub const assign_tokens = [Kind.assign, .decl_assign, .plus_assign, .minus_assign, .mult_assign, @@ -197,7 +201,8 @@ pub const assign_tokens = [Kind.assign, .decl_assign, .plus_assign, .minus_assig pub const valid_at_tokens = ['@VROOT', '@VMODROOT', '@VEXEROOT', '@FN', '@METHOD', '@MOD', '@STRUCT', '@VEXE', '@FILE', '@DIR', '@LINE', '@COLUMN', '@VHASH', '@VCURRENTHASH', '@VMOD_FILE', - '@VMODHASH', '@FILE_LINE', '@LOCATION', '@BUILD_DATE', '@BUILD_TIME', '@BUILD_TIMESTAMP'] + '@VMODHASH', '@FILE_LINE', '@LOCATION', '@BUILD_DATE', '@BUILD_TIME', '@BUILD_TIMESTAMP', '@OS', + '@CCOMPILER', '@BACKEND', '@PLATFORM'] pub const token_str = build_token_str()