diff --git a/vlib/net/websocket/websocket_test.v b/vlib/net/websocket/websocket_test.v index a082d8bc9e..18b6a78893 100644 --- a/vlib/net/websocket/websocket_test.v +++ b/vlib/net/websocket/websocket_test.v @@ -14,7 +14,11 @@ pub mut: // They have their own specialized CI runner. const github_job = os.getenv('GITHUB_JOB') -const should_skip = github_job != '' && github_job != 'websocket_tests' +const should_skip = get_should_skip() + +fn get_should_skip() bool { + return github_job != '' && github_job != 'websocket_tests' +} // tests with internal ws servers fn test_ws_ipv6() { diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index e1c6f4dd74..d82d1bd167 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -505,16 +505,32 @@ pub const ( int_literal_type = new_type(int_literal_type_idx) thread_type = new_type(thread_type_idx) error_type = new_type(error_type_idx) - charptr_types = [charptr_type, new_type(char_type_idx).set_nr_muls(1)] - byteptr_types = [byteptr_type, new_type(byte_type_idx).set_nr_muls(1)] - voidptr_types = [voidptr_type, new_type(voidptr_type_idx).set_nr_muls(1)] + charptr_types = new_charptr_types() + byteptr_types = new_byteptr_types() + voidptr_types = new_voidptr_types() cptr_types = merge_types(voidptr_types, byteptr_types, charptr_types) ) +fn new_charptr_types() []Type { + return [ast.charptr_type, new_type(ast.char_type_idx).set_nr_muls(1)] +} + +fn new_byteptr_types() []Type { + return [ast.byteptr_type, new_type(ast.byte_type_idx).set_nr_muls(1)] +} + +fn new_voidptr_types() []Type { + return [ast.voidptr_type, new_type(ast.voidptr_type_idx).set_nr_muls(1)] +} + pub fn merge_types(params ...[]Type) []Type { mut res := []Type{cap: params.len} for types in params { - res << types + for t in types { + if t !in res { + res << t + } + } } return res } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index ca3dd122b7..17447de8ce 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -219,6 +219,7 @@ struct GlobalConstDef { init string // init later (in _vinit) dep_names []string // the names of all the consts, that this const depends on order int // -1 for simple defines, string literals, anonymous function names, extern declarations etc + line int // the line number in the source code, where the const was defined; used to resolve order ambiguities } pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { @@ -4425,9 +4426,11 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) { mod: field.mod def: '$styp $const_name = $val; // fixed array const' dep_names: g.dependent_var_names(field_expr) + // line: field.pos.line_nr } } else { - g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) + g.const_decl_init_later(field.pos.line_nr, field.mod, name, field.expr, + field.typ, false) } } ast.StringLiteral { @@ -4437,15 +4440,18 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) { def: 'string $const_name; // a string literal, inited later' init: '\t$const_name = $val;' order: -1 + // line: field.pos.line_nr } } ast.CallExpr { if field.expr.return_type.has_flag(.optional) { g.inside_const_optional = true unwrap_option := field.expr.or_block.kind != .absent - g.const_decl_init_later(field.mod, name, field.expr, field.typ, unwrap_option) + g.const_decl_init_later(field.pos.line_nr, field.mod, name, field.expr, + field.typ, unwrap_option) } else { - g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) + g.const_decl_init_later(field.pos.line_nr, field.mod, name, field.expr, + field.typ, false) } g.inside_const_optional = false } @@ -4463,8 +4469,8 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) { use_cache_mode := g.pref.build_mode == .build_module || g.pref.use_cache if !use_cache_mode { if ct_value := field.comptime_expr_value() { - if g.const_decl_precomputed(field.mod, name, field.name, ct_value, - field.typ) + if g.const_decl_precomputed(field.pos.line_nr, field.mod, name, + field.name, ct_value, field.typ) { continue } @@ -4473,16 +4479,18 @@ fn (mut g Gen) const_decl(node ast.ConstDecl) { if field.is_simple_define_const() { // "Simple" expressions are not going to need multiple statements, // only the ones which are inited later, so it's safe to use expr_string - g.const_decl_simple_define(field.mod, field.name, g.expr_string(field_expr)) + g.const_decl_simple_define(field.pos.line_nr, field.mod, field.name, + g.expr_string(field_expr)) } else { - g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) + g.const_decl_init_later(field.pos.line_nr, field.mod, name, field.expr, + field.typ, false) } } } } } -fn (mut g Gen) const_decl_precomputed(mod string, name string, field_name string, ct_value ast.ComptTimeConstValue, typ ast.Type) bool { +fn (mut g Gen) const_decl_precomputed(line_nr int, mod string, name string, field_name string, ct_value ast.ComptTimeConstValue, typ ast.Type) bool { mut styp := g.typ(typ) cname := if g.pref.translated && !g.is_builtin_mod { name } else { '_const_$name' } $if trace_const_precomputed ? { @@ -4490,13 +4498,13 @@ fn (mut g Gen) const_decl_precomputed(mod string, name string, field_name string } match ct_value { i8 { - g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) + g.const_decl_write_precomputed(line_nr, mod, styp, cname, field_name, ct_value.str()) } i16 { - g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) + g.const_decl_write_precomputed(line_nr, mod, styp, cname, field_name, ct_value.str()) } int { - g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) + g.const_decl_write_precomputed(line_nr, mod, styp, cname, field_name, ct_value.str()) } i64 { if typ == ast.i64_type { @@ -4508,32 +4516,35 @@ fn (mut g Gen) const_decl_precomputed(mod string, name string, field_name string // with -cstrict. Add checker errors for overflows instead, // so V can catch them earlier, instead of relying on the // C compiler for that. - g.const_decl_simple_define(mod, name, ct_value.str()) + g.const_decl_simple_define(line_nr, mod, name, ct_value.str()) return true } if typ == ast.u64_type { - g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str() + 'U') + g.const_decl_write_precomputed(line_nr, mod, styp, cname, field_name, + ct_value.str() + 'U') } else { - g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) + g.const_decl_write_precomputed(line_nr, mod, styp, cname, field_name, + ct_value.str()) } } u8 { - g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) + g.const_decl_write_precomputed(line_nr, mod, styp, cname, field_name, ct_value.str()) } u16 { - g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) + g.const_decl_write_precomputed(line_nr, mod, styp, cname, field_name, ct_value.str()) } u32 { - g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) + g.const_decl_write_precomputed(line_nr, mod, styp, cname, field_name, ct_value.str()) } u64 { - g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str() + 'U') + g.const_decl_write_precomputed(line_nr, mod, styp, cname, field_name, ct_value.str() + + 'U') } f32 { - g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) + g.const_decl_write_precomputed(line_nr, mod, styp, cname, field_name, ct_value.str()) } f64 { - g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) + g.const_decl_write_precomputed(line_nr, mod, styp, cname, field_name, ct_value.str()) } rune { rune_code := u32(ct_value) @@ -4542,14 +4553,16 @@ fn (mut g Gen) const_decl_precomputed(mod string, name string, field_name string return false } escval := util.smart_quote(u8(rune_code).ascii_str(), false) - g.const_decl_write_precomputed(mod, styp, cname, field_name, "'$escval'") + g.const_decl_write_precomputed(line_nr, mod, styp, cname, field_name, + "'$escval'") } else { - g.const_decl_write_precomputed(mod, styp, cname, field_name, u32(ct_value).str()) + g.const_decl_write_precomputed(line_nr, mod, styp, cname, field_name, + u32(ct_value).str()) } } string { escaped_val := util.smart_quote(ct_value, false) - // g.const_decl_write_precomputed(styp, cname, '_SLIT("$escaped_val")') + // g.const_decl_write_precomputed(line_nr, styp, cname, '_SLIT("$escaped_val")') // TODO: ^ the above for strings, cause: // `error C2099: initializer is not a constant` errors in MSVC, // so fall back to the delayed initialisation scheme: @@ -4570,14 +4583,14 @@ fn (mut g Gen) const_decl_precomputed(mod string, name string, field_name string return true } -fn (mut g Gen) const_decl_write_precomputed(mod string, styp string, cname string, field_name string, ct_value string) { +fn (mut g Gen) const_decl_write_precomputed(line_nr int, mod string, styp string, cname string, field_name string, ct_value string) { g.global_const_defs[util.no_dots(field_name)] = GlobalConstDef{ mod: mod def: '$styp $cname = $ct_value; // precomputed' } } -fn (mut g Gen) const_decl_simple_define(mod string, name string, val string) { +fn (mut g Gen) const_decl_simple_define(line_nr int, mod string, name string, val string) { // Simple expressions should use a #define // so that we don't pollute the binary with unnecessary global vars // Do not do this when building a module, otherwise the consts @@ -4607,7 +4620,7 @@ fn (mut g Gen) const_decl_simple_define(mod string, name string, val string) { } } -fn (mut g Gen) const_decl_init_later(mod string, name string, expr ast.Expr, typ ast.Type, unwrap_option bool) { +fn (mut g Gen) const_decl_init_later(line_nr int, mod string, name string, expr ast.Expr, typ ast.Type, unwrap_option bool) { // Initialize more complex consts in `void _vinit/2{}` // (C doesn't allow init expressions that can't be resolved at compile time). mut styp := g.typ(typ) @@ -4628,10 +4641,24 @@ fn (mut g Gen) const_decl_init_later(mod string, name string, expr ast.Expr, typ init.writeln(g.expr_string_surround('\t$cname = ', expr, ';')) } } + mut order := 0 + mut line := 0 + if expr is ast.CallExpr { + // TODO: analyse the function *bodies* too, to know if and what consts are used in them, + // in order to fill in dep_names properly, and to detect initialisation cycles statically + // in the checker. + // For now, just keep the source code order for `const c = f()`, + // so that programs importing `rand` and `cmd/tools/vshader.v` could run properly: + order = 1 + line = line_nr + } g.global_const_defs[util.no_dots(name)] = GlobalConstDef{ mod: mod def: '$styp $cname; // inited later' init: init.str() + dep_names: g.dependent_var_names(expr) + order: order + line: line } if g.is_autofree { sym := g.table.sym(typ) @@ -4689,6 +4716,7 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { mod: node.mod def: '$fn_type_name = ${g.table.sym(field.typ).name}; // global2' order: -1 + // line: node.pos.line_nr } continue } @@ -4703,6 +4731,7 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { mod: node.mod def: def_builder.str() order: -1 + // line: node.pos.line_nr } continue } @@ -4735,6 +4764,7 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { def: def_builder.str() init: init dep_names: g.dependent_var_names(field.expr) + // line: node.pos.line_nr } } } @@ -5112,13 +5142,22 @@ fn (mut g Gen) write_types(symbols []&ast.TypeSymbol) { } fn (mut g Gen) sort_globals_consts() { + util.timing_start(@METHOD) + defer { + util.timing_measure(@METHOD) + } g.sorted_global_const_names.clear() + mut orders := map[i64]bool{} mut dep_graph := depgraph.new_dep_graph() for var_name, var_info in g.global_const_defs { - dep_graph.add_with_value(var_name, var_info.dep_names, var_info.order) + final_order := (var_info.order + 2) * 10000 + var_info.line + dep_graph.add_with_value(var_name, var_info.dep_names, final_order) + orders[final_order] = true } dep_graph_sorted := dep_graph.resolve() - for order in [-1, 0] { + mut all_orders := orders.keys() + all_orders.sort() + for order in all_orders { for node in dep_graph_sorted.nodes { if node.value == order { g.sorted_global_const_names << node.name diff --git a/vlib/v/tests/const_array_init_order_test.v b/vlib/v/tests/const_array_init_order_test.v new file mode 100644 index 0000000000..07cf98c995 --- /dev/null +++ b/vlib/v/tests/const_array_init_order_test.v @@ -0,0 +1,10 @@ +const ( + cst3 = [[[cst1, cst2]]] + cst1 = [11] + cst2 = [22] +) + +fn test_const_array_init_order() { + println(cst3) + assert cst3 == [[[[11], [22]]]] +} diff --git a/vlib/v/tests/const_function_call_init_order_test.v b/vlib/v/tests/const_function_call_init_order_test.v new file mode 100644 index 0000000000..e22a3bf463 --- /dev/null +++ b/vlib/v/tests/const_function_call_init_order_test.v @@ -0,0 +1,20 @@ +import os + +const ( + shdc_exe_name = 'sokol-shdc.exe' + tool_name = os.file_name(os.executable()) + cache_dir = os.join_path(os.cache_dir(), 'v', tool_name) + shdc = shdc_exe() +) + +fn shdc_exe() string { + return os.join_path(cache_dir, shdc_exe_name) +} + +fn test_const_order() { + // Ensures that cache_dir is initialised *first*, and *then* shdc + // even though `shdc` is initialised with the "simpler" call expression `shdc_exe()`, + // that seemingly does not use any other consts: + assert cache_dir.len > 1 + assert shdc != '/sokol-shdc.exe' +}