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()