From ae944cc283e97bc995df613e6644ed507ab2aa23 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 14 May 2026 10:52:00 +0300 Subject: [PATCH] v2: fix tests --- ...ol_processor_callback_array_return_err.out | 2 +- vlib/v2/builder/parse.v | 26 +- vlib/v2/builder/util.v | 81 ++- vlib/v2/gen/cleanc/cleanc.v | 3 +- vlib/v2/gen/cleanc/expr.v | 10 + vlib/v2/gen/cleanc/flag_enum_codegen_test.v | 170 +++++ vlib/v2/gen/cleanc/fn.v | 581 ++++++++++++++++-- .../gen/cleanc/result_option_codegen_test.v | 32 +- vlib/v2/gen/cleanc/stmt.v | 17 +- vlib/v2/transformer/transformer.v | 6 + vlib/v2/types/checker.v | 119 +++- 11 files changed, 937 insertions(+), 110 deletions(-) diff --git a/vlib/v/checker/tests/pool_processor_callback_array_return_err.out b/vlib/v/checker/tests/pool_processor_callback_array_return_err.out index 193fd1d3f..b6ca5727e 100644 --- a/vlib/v/checker/tests/pool_processor_callback_array_return_err.out +++ b/vlib/v/checker/tests/pool_processor_callback_array_return_err.out @@ -2,6 +2,6 @@ vlib/v/checker/tests/pool_processor_callback_array_return_err.vv:9:3: error: can 7 | fn main() { 8 | _ := pool.new_pool_processor( 9 | callback: fn (mut pp pool.PoolProcessor, idx int, wid int) []Foo { - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 | _ = wid 11 | return [Foo{ diff --git a/vlib/v2/builder/parse.v b/vlib/v2/builder/parse.v index e5e17cf40..f50dc32ae 100644 --- a/vlib/v2/builder/parse.v +++ b/vlib/v2/builder/parse.v @@ -11,15 +11,27 @@ fn should_expand_single_file_input(input string) bool { if os.file_name(input).ends_with('_test.v') { return true } - module_name := file_module_name(input) or { return false } - return module_name != 'main' + module_name := file_module_name(input) + return module_name.len > 0 && !string_bytes_eq(module_name, 'main') } -fn file_module_name(path string) ?string { - content := os.read_file(path) or { return none } +fn string_bytes_eq(a string, b string) bool { + if a.len != b.len { + return false + } + for i in 0 .. a.len { + if a[i] != b[i] { + return false + } + } + return true +} + +fn file_module_name(path string) string { + content := os.read_file(path) or { return '' } for raw_line in content.split_into_lines() { line := raw_line.trim_space() - if line == '' || line.starts_with('//') { + if line.len == 0 || line.starts_with('//') { continue } if line.starts_with('module ') { @@ -27,7 +39,7 @@ fn file_module_name(path string) ?string { } break } - return none + return '' } fn (mut b Builder) parse_files(files []string) []ast.File { @@ -61,7 +73,7 @@ fn (mut b Builder) parse_files(files []string) []ast.File { mut expanded_user_files := []string{} mut seen_user_files := map[string]bool{} for input in files { - if input == '' { + if input.len == 0 { continue } if os.is_dir(input) { diff --git a/vlib/v2/builder/util.v b/vlib/v2/builder/util.v index 212c29a65..6eb836888 100644 --- a/vlib/v2/builder/util.v +++ b/vlib/v2/builder/util.v @@ -11,14 +11,14 @@ fn list_dir_entries(path string) []string { } pub fn get_v_files_from_dir(dir string, user_defines []string, target_os string) []string { - if dir == '' { + if dir.len == 0 { return []string{} } mod_files := list_dir_entries(dir) mut v_files := []string{} mut defaults := []string{} for file in mod_files { - if file == '' { + if file.len == 0 { continue } // Include .v files (including .c.v), exclude .js.v and test files @@ -52,12 +52,12 @@ pub fn get_v_files_from_dir(dir string, user_defines []string, target_os string) } } path := os.join_path(dir, file) - if path == '' { + if path.len == 0 { continue } // Collect _default.c.v files separately; they are only included when // no platform-specific variant exists (e.g. _darwin.c.v, _linux.c.v). - if file.contains('default.c.v') { + if string_bytes_has_suffix(file, 'default.c.v') { defaults << path } else { v_files << path @@ -65,55 +65,68 @@ pub fn get_v_files_from_dir(dir string, user_defines []string, target_os string) } // Add _default.c.v files only when no platform-specific variant was selected. for dfile in defaults { - no_postfix := fname_without_platform_postfix(dfile) - mut has_specialized := false - for vf in v_files { - if fname_without_platform_postfix(vf) == no_postfix { - has_specialized = true - break - } - } - if !has_specialized { + if !has_specialized_platform_file(dfile, v_files) { v_files << dfile } } return v_files } -// fname_without_platform_postfix strips the platform-specific suffix from a file path, -// so that e.g. "free_memory_impl_darwin.c.v" and "free_memory_impl_default.c.v" both -// map to the same key, allowing detection of specialized vs default variants. -fn fname_without_platform_postfix(file string) string { - return file.replace_each([ - 'default.c.v', - '_', +fn has_specialized_platform_file(default_file string, files []string) bool { + default_suffix := 'default.c.v' + if !string_bytes_has_suffix(default_file, default_suffix) { + return false + } + prefix := default_file[..default_file.len - default_suffix.len] + platform_suffixes := [ 'nix.c.v', - '_', 'windows.c.v', - '_', 'linux.c.v', - '_', 'darwin.c.v', - '_', 'macos.c.v', - '_', 'android.c.v', - '_', 'termux.c.v', - '_', 'android_outside_termux.c.v', - '_', 'freebsd.c.v', - '_', 'openbsd.c.v', - '_', 'netbsd.c.v', - '_', 'dragonfly.c.v', - '_', 'solaris.c.v', - '_', - ]) + ] + for file in files { + for suffix in platform_suffixes { + if file.len == prefix.len + suffix.len && string_bytes_has_prefix(file, prefix) + && string_bytes_has_suffix(file, suffix) { + return true + } + } + } + return false +} + +fn string_bytes_has_prefix(s string, prefix string) bool { + if s.len < prefix.len { + return false + } + for i in 0 .. prefix.len { + if s[i] != prefix[i] { + return false + } + } + return true +} + +fn string_bytes_has_suffix(s string, suffix string) bool { + if s.len < suffix.len { + return false + } + start := s.len - suffix.len + for i in 0 .. suffix.len { + if s[start + i] != suffix[i] { + return false + } + } + return true } // extract_define_feature extracts the feature name from a _d_ or _notd_ filename. diff --git a/vlib/v2/gen/cleanc/cleanc.v b/vlib/v2/gen/cleanc/cleanc.v index bcef8f640..fbbb21755 100644 --- a/vlib/v2/gen/cleanc/cleanc.v +++ b/vlib/v2/gen/cleanc/cleanc.v @@ -605,11 +605,12 @@ pub fn (mut g Gen) gen_passes_1_to_4() { 'T': types.Type(types.f64_) } g.discover_comptime_generic_specs() + g.discover_nested_generic_specs() + g.build_generic_spec_index() g.collect_fn_signatures() g.collect_c_file_fn_keys() g.collect_runtime_const_targets() g.register_builder_methods() - g.build_generic_spec_index() stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'setup') // Pre-collect all global variable names so they can be module-qualified diff --git a/vlib/v2/gen/cleanc/expr.v b/vlib/v2/gen/cleanc/expr.v index c53bba984..5e579f161 100644 --- a/vlib/v2/gen/cleanc/expr.v +++ b/vlib/v2/gen/cleanc/expr.v @@ -2033,6 +2033,16 @@ fn (mut g Gen) expr(node ast.Expr) { // the two operations cancel out. This avoids &(rvalue) errors when // the deref gets null-guard expansion for sum type data pointers. inner := g.unwrap_parens(node.expr) + if inner is ast.Ident { + source_type := g.get_expr_type(inner).trim_space() + if local_type := g.get_local_var_c_type(inner.name) { + if local_type.ends_with('*') + && (inner.name in g.cur_fn_mut_params || !source_type.ends_with('*')) { + g.expr(inner) + return + } + } + } if inner is ast.PrefixExpr && inner.op == .mul { if g.expr_is_pointer(inner.expr) || g.expr_produces_pointer(inner.expr) { g.expr(inner.expr) diff --git a/vlib/v2/gen/cleanc/flag_enum_codegen_test.v b/vlib/v2/gen/cleanc/flag_enum_codegen_test.v index 32b9ef977..b738d0ff3 100644 --- a/vlib/v2/gen/cleanc/flag_enum_codegen_test.v +++ b/vlib/v2/gen/cleanc/flag_enum_codegen_test.v @@ -216,6 +216,176 @@ fn main() { assert csrc.contains('int Mapper__helper_T_Schema(Mapper* m) {') } +fn test_generate_c_specializes_implicit_generic_function_calls_from_all_args() { + csrc := generate_c_for_test(' +struct Left {} +struct RightA {} +struct RightB {} + +fn pair[T, U](x T, y U) int { + _ = x + _ = y + return 1 +} + +fn main() { + left := Left{} + a := RightA{} + b := RightB{} + _ = pair(left, a) + _ = pair(left, b) +} +') + assert csrc.contains('int pair_T_Left_RightA(Left x, RightA y);') + assert csrc.contains('int pair_T_Left_RightB(Left x, RightB y);') + assert csrc.contains('pair_T_Left_RightA(left, a)') + assert csrc.contains('pair_T_Left_RightB(left, b)') + assert !csrc.contains('pair(left, a)') + assert !csrc.contains('pair(left, b)') +} + +fn test_generate_c_does_not_specialize_plain_generic_function_for_struct_fields() { + csrc := generate_c_for_test(' +enum Pattern { + a +} + +struct Thing { + pattern Pattern + named bool +} + +fn (thing Thing) find_at() int { + _ = thing + return 1 +} + +fn find[M](matcher M) int { + return matcher.find_at() +} + +fn main() { + thing := Thing{} + _ = find(thing) +} +') + assert csrc.contains('int find_T_Thing(Thing matcher) {') + assert csrc.contains('return Thing__find_at((matcher));') + assert !csrc.contains('find_T_Pattern') + assert !csrc.contains('find_T_bool') + assert !csrc.contains('Pattern__find_at') + assert !csrc.contains('bool__find_at') +} + +fn test_generate_c_specializes_nested_generic_calls_from_active_bindings() { + csrc := generate_c_for_test(' +struct Matcher {} +struct Captures {} + +fn outer[M, T](matcher M, mut caps T) { + inner(matcher, mut caps) +} + +fn inner[M, T](matcher M, mut caps T) { + _ = matcher + _ = caps +} + +fn main() { + matcher := Matcher{} + mut caps := Captures{} + outer(matcher, mut caps) +} +') + assert csrc.contains('void outer_T_Matcher_Captures(Matcher matcher, Captures* caps) {') + assert csrc.contains('inner_T_Matcher_Captures(matcher, caps);') + assert csrc.contains('void inner_T_Matcher_Captures(Matcher matcher, Captures* caps) {') + assert !csrc.contains('inner(matcher, caps)') +} + +fn test_generate_c_emits_value_receiver_methods_used_by_interface_wrappers() { + csrc := generate_c_for_test(' +interface Counter { + len() int +} + +struct Counts {} + +fn (counts Counts) len() int { + _ = counts + return 1 +} + +fn use_counter(counter Counter) int { + return counter.len() +} + +fn main() { + counts := Counts{} + _ = use_counter(counts) +} +') + assert csrc.contains('int Counts__len(Counts counts) {') + assert csrc.contains('static int __iface_wrap_Counter_Counts_len(void* _obj) {') + assert csrc.contains('return Counts__len(*(((Counts*)_obj)));') +} + +fn test_generate_c_preserves_void_result_or_block_side_effects() { + csrc := generate_c_for_test(' +fn may_fail() ? { + return none +} + +fn main() { + mut saw_error := false + may_fail() or { + saw_error = true + } + _ = saw_error +} +') + assert csrc.contains('saw_error = true;') +} + +fn test_generate_c_passes_mut_generic_param_address_as_existing_pointer() { + csrc := generate_c_for_test(' +struct Captures {} + +fn visit[T](mut value T, cb fn (&T)) { + cb(&value) +} + +fn main() { + mut captures := Captures{} + visit(mut captures, fn (_captures &Captures) {}) +} +') + assert csrc.contains('cb(value);') + assert !csrc.contains('cb(&value);') +} + +fn test_generate_c_captures_mut_param_as_existing_pointer() { + csrc := generate_c_for_test(' +fn touch(mut dst []u8) { + dst << u8(1) +} + +fn outer(mut dst []u8) { + cb := fn [mut dst] () { + touch(mut dst) + } + cb() +} + +fn main() { + mut dst := []u8{} + outer(mut dst) +} +') + assert csrc.contains('_capture_0 = dst;') + assert !csrc.contains('_capture_0 = &dst;') +} + fn test_generate_c_rewrites_continue_in_generic_comptime_field_loop() { code := [ 'struct Schema {', diff --git a/vlib/v2/gen/cleanc/fn.v b/vlib/v2/gen/cleanc/fn.v index 50a81add0..db7b4f138 100644 --- a/vlib/v2/gen/cleanc/fn.v +++ b/vlib/v2/gen/cleanc/fn.v @@ -204,6 +204,25 @@ fn (mut g Gen) should_emit_fn_decl(module_name string, decl ast.FnDecl) bool { return true } } + if g.force_emit_fn_names.len > 0 && decl.is_method { + recv_name := g.receiver_type_to_scope_name(decl.receiver.typ) + if recv_name.len > 0 { + mut candidates := []string{} + candidates << '${recv_name}__${sanitize_fn_ident(decl.name)}' + if module_name.len > 0 && module_name != 'builtin' && module_name != 'main' { + candidates << '${module_name}__${recv_name}__${sanitize_fn_ident(decl.name)}' + } + recv_c_name := g.expr_type_to_c(decl.receiver.typ).trim_right('*') + if recv_c_name.len > 0 { + candidates << '${recv_c_name}__${sanitize_fn_ident(decl.name)}' + } + for c_name in candidates { + if c_name in g.force_emit_fn_names { + return true + } + } + } + } // Check if this function was force-requested by generated code (e.g. map str functions). if g.force_emit_fn_names.len > 0 && decl.name == 'str' && decl.is_method { // Build the C function name for method str: ReceiverType__str @@ -912,46 +931,453 @@ fn (mut g Gen) discover_comptime_generic_specs() { if g.env == unsafe { nil } { return } - // For each generic function specialization where T is a struct, - // discover field types and register additional specializations - // so that comptime $for field in T.fields { decode_value(field) } - // can call decode_value_T_. - for key, spec_list in g.env.generic_types { - for spec in spec_list { - for param_name, concrete_type in spec { - if concrete_type is types.Struct { - struct_type := concrete_type as types.Struct - if struct_type.fields.len == 0 { + for file in g.files { + for stmt in file.stmts { + match stmt { + ast.FnDecl { + generic_params := g.generic_fn_param_names(stmt) + if generic_params.len == 0 { continue } - mut seen_types := map[string]bool{} - // Mark existing specs as seen to avoid duplicates - for existing_spec in spec_list { - if existing_t := existing_spec[param_name] { - seen_types[existing_t.name()] = true + for param_name in generic_params { + if !fn_uses_comptime_field_loop(stmt, param_name) { + continue } + g.discover_comptime_generic_specs_for_param(stmt, param_name) } - for already in g.late_generic_specs[key] { - if already_t := already[param_name] { - seen_types[already_t.name()] = true - } + } + else {} + } + } + } +} + +fn (mut g Gen) discover_comptime_generic_specs_for_param(node ast.FnDecl, param_name string) { + for key, spec_list in g.env.generic_types { + if !g.generic_key_matches_decl(node, key) { + continue + } + for spec in spec_list { + concrete_type := spec[param_name] or { continue } + if concrete_type !is types.Struct { + continue + } + struct_type := concrete_type as types.Struct + if struct_type.fields.len == 0 { + continue + } + mut seen_types := map[string]bool{} + // Mark existing specs as seen to avoid duplicates. + for existing_spec in spec_list { + if existing_t := existing_spec[param_name] { + seen_types[existing_t.name()] = true + } + } + for already in g.late_generic_specs[key] { + if already_t := already[param_name] { + seen_types[already_t.name()] = true + } + } + for field in struct_type.fields { + field_type_name := field.typ.name() + if field_type_name in seen_types { + continue + } + seen_types[field_type_name] = true + g.late_generic_specs[key] << { + param_name: field.typ + } + } + } + } +} + +fn fn_uses_comptime_field_loop(node ast.FnDecl, type_name string) bool { + return stmts_use_comptime_field_loop(node.stmts, type_name) +} + +fn stmts_use_comptime_field_loop(stmts []ast.Stmt, type_name string) bool { + for stmt in stmts { + if stmt_uses_comptime_field_loop(stmt, type_name) { + return true + } + } + return false +} + +fn stmt_uses_comptime_field_loop(stmt ast.Stmt, type_name string) bool { + match stmt { + ast.BlockStmt { + return stmts_use_comptime_field_loop(stmt.stmts, type_name) + } + ast.ComptimeStmt { + return stmt_uses_comptime_field_loop(stmt.stmt, type_name) + } + ast.ExprStmt { + return expr_uses_comptime_field_loop(stmt.expr, type_name) + } + ast.ForStmt { + match stmt.init { + ast.ForInStmt { + if expr_is_comptime_fields_selector(stmt.init.expr, type_name) { + return true } - for field in struct_type.fields { - field_type_name := field.typ.name() - if field_type_name in seen_types { + } + else {} + } + + return stmts_use_comptime_field_loop(stmt.stmts, type_name) + } + ast.ReturnStmt { + for expr in stmt.exprs { + if expr_uses_comptime_field_loop(expr, type_name) { + return true + } + } + } + else {} + } + + return false +} + +fn expr_uses_comptime_field_loop(expr ast.Expr, type_name string) bool { + match expr { + ast.ComptimeExpr { + return expr_uses_comptime_field_loop(expr.expr, type_name) + } + ast.IfExpr { + if stmts_use_comptime_field_loop(expr.stmts, type_name) { + return true + } + if expr.else_expr !is ast.EmptyExpr { + return expr_uses_comptime_field_loop(expr.else_expr, type_name) + } + } + ast.MatchExpr { + for branch in expr.branches { + if stmts_use_comptime_field_loop(branch.stmts, type_name) { + return true + } + } + } + else {} + } + + return false +} + +fn expr_is_comptime_fields_selector(expr ast.Expr, type_name string) bool { + if expr is ast.SelectorExpr { + return expr.rhs.name == 'fields' && expr.lhs.name() == type_name + } + return false +} + +fn (mut g Gen) discover_nested_generic_specs() { + for _ in 0 .. 8 { + mut added := false + for file in g.files { + for stmt in file.stmts { + match stmt { + ast.FnDecl { + if g.generic_fn_param_names(stmt).len == 0 { continue } - seen_types[field_type_name] = true - g.late_generic_specs[key] << { - param_name: field.typ + specs := g.generic_binding_maps_for_decl(stmt) + if specs.len == 0 { + continue } + prev_active := g.active_generic_types.clone() + for spec in specs { + g.active_generic_types = spec.clone() + local_types := caller_generic_local_types(stmt, spec) + if g.discover_nested_generic_specs_in_stmts(stmt.stmts, local_types) { + added = true + } + } + g.active_generic_types = prev_active.clone() } + else {} } } } + if !added { + return + } } } +fn (mut g Gen) generic_binding_maps_for_decl(node ast.FnDecl) []map[string]types.Type { + generic_params := g.generic_fn_param_names(node) + if generic_params.len == 0 || g.env == unsafe { nil } { + return []map[string]types.Type{} + } + mut specs := []map[string]types.Type{} + mut seen := map[string]bool{} + for key, spec_list in g.env.generic_types { + if !g.generic_key_matches_decl(node, key) { + continue + } + for spec in spec_list { + if generic_binding_map_complete(spec, generic_params) { + spec_key := generic_binding_map_key(spec, generic_params) + if spec_key !in seen { + seen[spec_key] = true + specs << spec.clone() + } + } + } + } + for key, spec_list in g.late_generic_specs { + if !g.generic_key_matches_decl(node, key) { + continue + } + for spec in spec_list { + if generic_binding_map_complete(spec, generic_params) { + spec_key := generic_binding_map_key(spec, generic_params) + if spec_key !in seen { + seen[spec_key] = true + specs << spec.clone() + } + } + } + } + return specs +} + +fn generic_binding_map_complete(spec map[string]types.Type, generic_params []string) bool { + for param_name in generic_params { + concrete := spec[param_name] or { return false } + if concrete.name() == param_name || type_contains_generic_placeholder(concrete) { + return false + } + } + return true +} + +fn generic_binding_map_key(spec map[string]types.Type, generic_params []string) string { + mut parts := []string{cap: generic_params.len} + for param_name in generic_params { + concrete := spec[param_name] or { continue } + parts << '${param_name}=${concrete.name()}' + } + return parts.join(';') +} + +fn caller_generic_local_types(node ast.FnDecl, bindings map[string]types.Type) map[string]types.Type { + mut local_types := map[string]types.Type{} + for param in node.typ.params { + placeholder := direct_generic_placeholder_name(param.typ) + if placeholder == '' { + continue + } + if concrete := bindings[placeholder] { + local_types[param.name] = concrete + } + } + if node.is_method { + placeholder := direct_generic_placeholder_name(node.receiver.typ) + if placeholder != '' { + if concrete := bindings[placeholder] { + local_types[node.receiver.name] = concrete + } + } + } + return local_types +} + +fn (mut g Gen) discover_nested_generic_specs_in_stmts(stmts []ast.Stmt, local_types map[string]types.Type) bool { + mut added := false + for stmt in stmts { + if g.discover_nested_generic_specs_in_stmt(stmt, local_types) { + added = true + } + } + return added +} + +fn (mut g Gen) discover_nested_generic_specs_in_stmt(stmt ast.Stmt, local_types map[string]types.Type) bool { + match stmt { + ast.AssignStmt { + mut added := false + for expr in stmt.rhs { + if g.discover_nested_generic_specs_in_expr(expr, local_types) { + added = true + } + } + return added + } + ast.BlockStmt { + return g.discover_nested_generic_specs_in_stmts(stmt.stmts, local_types) + } + ast.ComptimeStmt { + return g.discover_nested_generic_specs_in_stmt(stmt.stmt, local_types) + } + ast.ExprStmt { + return g.discover_nested_generic_specs_in_expr(stmt.expr, local_types) + } + ast.ForStmt { + return g.discover_nested_generic_specs_in_stmts(stmt.stmts, local_types) + } + ast.ReturnStmt { + mut added := false + for expr in stmt.exprs { + if g.discover_nested_generic_specs_in_expr(expr, local_types) { + added = true + } + } + return added + } + else {} + } + + return false +} + +fn (mut g Gen) discover_nested_generic_specs_in_expr(expr ast.Expr, local_types map[string]types.Type) bool { + match expr { + ast.CallExpr { + mut added := g.discover_nested_generic_specs_from_call(expr, local_types) + for arg in expr.args { + if g.discover_nested_generic_specs_in_expr(arg, local_types) { + added = true + } + } + return added + } + ast.ComptimeExpr { + return g.discover_nested_generic_specs_in_expr(expr.expr, local_types) + } + ast.IfExpr { + mut added := g.discover_nested_generic_specs_in_stmts(expr.stmts, local_types) + if expr.else_expr !is ast.EmptyExpr { + if g.discover_nested_generic_specs_in_expr(expr.else_expr, local_types) { + added = true + } + } + return added + } + ast.MatchExpr { + mut added := false + for branch in expr.branches { + if g.discover_nested_generic_specs_in_stmts(branch.stmts, local_types) { + added = true + } + } + return added + } + ast.ModifierExpr { + return g.discover_nested_generic_specs_in_expr(expr.expr, local_types) + } + ast.OrExpr { + return g.discover_nested_generic_specs_in_expr(expr.expr, local_types) + } + ast.ParenExpr { + return g.discover_nested_generic_specs_in_expr(expr.expr, local_types) + } + else {} + } + + return false +} + +fn (mut g Gen) discover_nested_generic_specs_from_call(expr ast.CallExpr, local_types map[string]types.Type) bool { + callee := g.generic_fn_decl_for_call(expr) or { return false } + generic_params := g.generic_fn_param_names(callee) + if generic_params.len == 0 { + return false + } + mut bindings := map[string]types.Type{} + call_args := generic_call_args_for_decl(expr, callee) + arg_offset := if callee.is_method && call_args.len == callee.typ.params.len + 1 { 1 } else { 0 } + for i, param in callee.typ.params { + arg_idx := i + arg_offset + if arg_idx >= call_args.len || !expr_has_generic_placeholder(param.typ) { + continue + } + concrete := g.nested_generic_arg_type(call_args[arg_idx], local_types) or { continue } + infer_generic_type_bindings_from_param(param.typ, concrete, generic_params, mut bindings) + } + if !generic_binding_map_complete(bindings, generic_params) { + return false + } + return g.add_late_generic_spec_for_decl(callee, bindings) +} + +fn (mut g Gen) generic_fn_decl_for_call(expr ast.CallExpr) ?ast.FnDecl { + lhs := expr.lhs + if lhs is ast.Ident { + return g.find_generic_fn_decl_by_base_name(sanitize_fn_ident(lhs.name)) + } + if lhs is ast.SelectorExpr { + if lhs.lhs is ast.Ident && g.is_module_ident(lhs.lhs.name) { + mod_name := g.resolve_module_name(lhs.lhs.name) + return g.find_generic_fn_decl_by_base_name('${mod_name}__${sanitize_fn_ident(lhs.rhs.name)}') + } + } + return none +} + +fn generic_call_args_for_decl(expr ast.CallExpr, decl ast.FnDecl) []ast.Expr { + mut args := []ast.Expr{cap: expr.args.len + 1} + if decl.is_method && expr.lhs is ast.SelectorExpr { + args << expr.lhs.lhs + } + args << expr.args + return args +} + +fn (mut g Gen) nested_generic_arg_type(arg ast.Expr, local_types map[string]types.Type) ?types.Type { + base_arg := if arg is ast.ModifierExpr { + (arg as ast.ModifierExpr).expr + } else { + arg + } + if base_arg is ast.Ident { + if concrete := local_types[base_arg.name] { + return concrete + } + } + pos := base_arg.pos() + if pos.id != 0 { + if typ := g.env.get_expr_type(pos.id) { + resolved := g.resolve_active_generic_type_value(typ) + if !type_contains_generic_placeholder(resolved) { + return resolved + } + } + } + if raw := g.get_raw_type(base_arg) { + resolved := g.resolve_active_generic_type_value(raw) + if !type_contains_generic_placeholder(resolved) { + return resolved + } + } + return none +} + +fn (mut g Gen) add_late_generic_spec_for_decl(node ast.FnDecl, bindings map[string]types.Type) bool { + generic_params := g.generic_fn_param_names(node) + if !generic_binding_map_complete(bindings, generic_params) { + return false + } + key := node.name + spec_key := generic_binding_map_key(bindings, generic_params) + for existing in g.env.generic_types[key] { + if generic_binding_map_key(existing, generic_params) == spec_key { + return false + } + } + for existing in g.late_generic_specs[key] { + if generic_binding_map_key(existing, generic_params) == spec_key { + return false + } + } + g.late_generic_specs[key] << bindings.clone() + return true +} + // build_generic_spec_index precomputes a reverse index from function names // to matching keys in env.generic_types. This avoids O(n*m) iteration // in generic_fn_specializations (called per generic fn per file). @@ -1328,6 +1754,25 @@ fn (mut g Gen) generic_call_arg_type_name(arg ast.Expr) string { return arg_type_name.trim_right('*') } +fn (g &Gen) resolve_active_generic_type_value(typ types.Type) types.Type { + match typ { + types.NamedType { + if concrete := g.active_generic_types[string(typ)] { + return concrete + } + } + types.Pointer { + return types.Type(types.Pointer{ + base_type: g.resolve_active_generic_type_value(typ.base_type) + lifetime: typ.lifetime + }) + } + else {} + } + + return typ +} + fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Expr) ?string { if name == '' { return none @@ -1576,24 +2021,57 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp if !expr_has_valid_data(arg) { continue } + mut concrete := types.Type(types.void_) + mut has_concrete := false + if g.active_generic_types.len > 0 && arg is ast.Ident { + if local_type := g.get_local_var_c_type(arg.name) { + if typ := g.lookup_type_by_c_name(local_type.trim_right('*')) { + concrete = typ + has_concrete = true + } + } + } pos := arg.pos() - if pos.id == 0 { - continue + if !has_concrete && pos.id != 0 { + if typ := g.env.get_expr_type(pos.id) { + resolved := g.resolve_active_generic_type_value(typ) + if !type_contains_generic_placeholder(resolved) { + concrete = resolved + has_concrete = true + } + } } - concrete := g.env.get_expr_type(pos.id) or { + if !has_concrete { if raw := g.get_raw_type(arg) { - raw - } else { - continue + resolved := g.resolve_active_generic_type_value(raw) + if !type_contains_generic_placeholder(resolved) { + concrete = resolved + has_concrete = true + } } } + if !has_concrete && arg is ast.Ident { + if local_type := g.get_local_var_c_type(arg.name) { + if typ := g.lookup_type_by_c_name(local_type.trim_right('*')) { + concrete = typ + has_concrete = true + } + } + } + if !has_concrete { + continue + } + if param.is_mut && concrete is types.Pointer { + concrete = concrete.base_type + } infer_generic_type_bindings_from_param(param.typ, concrete, generic_params, mut bindings) } if bindings.len != generic_params.len { return none } candidate := g.specialized_fn_name(decl, bindings) - if candidate in g.fn_param_is_ptr || candidate in g.fn_return_types { + if candidate in g.fn_param_is_ptr || candidate in g.fn_return_types + || g.has_generic_fn_specialization(decl, candidate) { return candidate } // specialized_fn_name uses g.cur_module (caller's module) which may differ @@ -1606,14 +2084,24 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp } } if suffixes.len > 0 { - alt_candidate := '${name}_${suffixes.join('_')}' - if alt_candidate in g.fn_param_is_ptr || alt_candidate in g.fn_return_types { + alt_candidate := '${name}_T_${suffixes.join('_')}' + if alt_candidate in g.fn_param_is_ptr || alt_candidate in g.fn_return_types + || g.has_generic_fn_specialization(decl, alt_candidate) { return alt_candidate } } return none } +fn (mut g Gen) has_generic_fn_specialization(decl ast.FnDecl, name string) bool { + for spec in g.generic_fn_specializations(decl) { + if spec.name == name { + return true + } + } + return false +} + // register_builder_methods ensures strings__Builder methods are known to // fn_param_is_ptr so call sites emit &sb for the receiver. The Builder // is a typedef for array and its methods take a mutable (pointer) receiver. @@ -5143,11 +5631,12 @@ fn fn_literal_c_param_name(idx int) string { } struct FnLiteralCaptureInfo { - name string - c_type string - assign_expr ast.Expr - is_mut bool - storage_name string + name string + c_type string + assign_expr ast.Expr + is_mut bool + needs_address bool + storage_name string } fn (mut g Gen) fn_literal_capture_infos(anon_name string, captured_vars []ast.Expr) []FnLiteralCaptureInfo { @@ -5176,12 +5665,14 @@ fn (mut g Gen) fn_literal_capture_infos(anon_name string, captured_vars []ast.Ex if is_mut && !c_type.ends_with('*') { c_type = ensure_ptr_suffix(c_type) } + needs_address := is_mut && !(name in g.cur_fn_mut_params && c_type.ends_with('*')) infos << FnLiteralCaptureInfo{ - name: name - c_type: c_type - assign_expr: assign_expr - is_mut: is_mut - storage_name: '${anon_name}_capture_${i}' + name: name + c_type: c_type + assign_expr: assign_expr + is_mut: is_mut + needs_address: needs_address + storage_name: '${anon_name}_capture_${i}' } } return infos @@ -5295,7 +5786,7 @@ fn (mut g Gen) gen_fn_literal(node ast.FnLiteral) { g.sb.write_string('({ ') for capture in capture_infos { g.sb.write_string('${capture.storage_name} = ') - if capture.is_mut { + if capture.needs_address { g.sb.write_u8(`&`) } g.expr(capture.assign_expr) diff --git a/vlib/v2/gen/cleanc/result_option_codegen_test.v b/vlib/v2/gen/cleanc/result_option_codegen_test.v index 19f9d8c84..016250d7f 100644 --- a/vlib/v2/gen/cleanc/result_option_codegen_test.v +++ b/vlib/v2/gen/cleanc/result_option_codegen_test.v @@ -199,7 +199,7 @@ fn use(m Match[Item]) bool { ') assert csrc.contains('typedef struct _option_sample__Item _option_sample__Item;') assert csrc.contains('struct _option_sample__Item') - assert csrc.contains('_option_sample__Item sample__Match_T_sample__Item__inner') + assert csrc.contains('_option_sample__Item sample__Match__inner') } fn test_generate_c_emits_struct_str_function_when_interpolated() { @@ -247,6 +247,36 @@ fn test_unused_error() { assert !csrc.contains('Term__str((err).term)') } +fn test_generate_c_does_not_force_emit_unused_interface_candidate_method_body() { + csrc := generate_markused_c_for_test(' +interface Describer { + msg() string +} + +struct Inner { + text string +} + +fn (inner Inner) str() string { + return inner.text +} + +struct ErrorLike { + inner Inner +} + +fn (err ErrorLike) msg() string { + return err.inner.str() +} + +fn main() { + _ = 1 +} +') + assert !csrc.contains('string ErrorLike__msg(ErrorLike err)') + assert !csrc.contains('Inner__str((err).inner)') +} + fn test_generate_c_borrows_option_field_unwrap_payload_without_temp() { csrc := generate_result_option_c_for_test(' struct Holder { diff --git a/vlib/v2/gen/cleanc/stmt.v b/vlib/v2/gen/cleanc/stmt.v index e88a6c0df..3eadec0a8 100644 --- a/vlib/v2/gen/cleanc/stmt.v +++ b/vlib/v2/gen/cleanc/stmt.v @@ -215,7 +215,8 @@ fn (mut g Gen) gen_stmt(node ast.Stmt) { if (expr_type == '' || expr_type == 'int') && expr is ast.Ident { expr_type = g.get_local_var_c_type(expr.name) or { expr_type } } - if expr is ast.Ident && expr.name == 'err' { + if expr is ast.Ident && expr.name == 'err' + && (expr_type == 'IError' || expr_type == 'builtin__IError') { g.sb.write_string('return (${g.cur_fn_ret_type}){ .is_error=true, .err=') g.expr(expr) g.sb.writeln(' };') @@ -284,10 +285,16 @@ fn (mut g Gen) gen_stmt(node ast.Stmt) { } // `return err` propagates the error from the or-block if expr is ast.Ident && expr.name == 'err' { - g.sb.write_string('return (${g.cur_fn_ret_type}){ .is_error=true, .err=') - g.expr(expr) - g.sb.writeln(' };') - return + mut err_type := g.get_expr_type(expr) + if err_type == '' || err_type == 'int' { + err_type = g.get_local_var_c_type(expr.name) or { err_type } + } + if err_type == 'IError' || err_type == 'builtin__IError' { + g.sb.write_string('return (${g.cur_fn_ret_type}){ .is_error=true, .err=') + g.expr(expr) + g.sb.writeln(' };') + return + } } ierror_base := g.ierror_concrete_base_for_expr(expr) if ierror_base != '' { diff --git a/vlib/v2/transformer/transformer.v b/vlib/v2/transformer/transformer.v index b1260a54d..4d09fbd7d 100644 --- a/vlib/v2/transformer/transformer.v +++ b/vlib/v2/transformer/transformer.v @@ -4847,6 +4847,9 @@ fn (mut t Transformer) expand_direct_or_expr_assign(stmt ast.AssignStmt, or_expr // relying on later transform_stmt to avoid double smartcast transformation. transformed_or_stmts := t.transform_stmts(or_expr.stmts) if_stmts << transformed_or_stmts + } else if is_void_result { + transformed_or_stmts := t.transform_stmts(or_expr.stmts) + if_stmts << transformed_or_stmts } else if is_result && t.cur_fn_returns_result && t.or_block_ends_with_error_value(or_expr.stmts) { or_side_effect_stmts, or_value := t.get_or_block_stmts_and_value(or_expr.stmts) @@ -7069,6 +7072,9 @@ fn (mut t Transformer) expand_single_or_expr(or_expr ast.OrExpr, mut prefix_stmt rhs: [t.or_fallback_value_for_data(or_value, data_type)] } } + } else { + transformed_or_stmts := t.transform_stmts(or_expr.stmts) + if_stmts << transformed_or_stmts } prefix_stmts << ast.ExprStmt{ expr: ast.IfExpr{ diff --git a/vlib/v2/types/checker.v b/vlib/v2/types/checker.v index c5d2d8084..7084defff 100644 --- a/vlib/v2/types/checker.v +++ b/vlib/v2/types/checker.v @@ -4,6 +4,7 @@ module types import time +import strings import strconv import v2.ast import v2.errors @@ -172,7 +173,15 @@ pub fn (e &Environment) lookup_method(type_name string, method_name string) ?FnT } pub fn (e &Environment) methods_for_type(type_name string) []&Fn { - return e.method_cache[type_name] or { []&Fn{} } + if methods := map_get_fns_by_string(e.method_cache, type_name) { + return methods + } + rlock e.methods { + if methods := map_get_fns_by_string(e.methods, type_name) { + return methods + } + } + return []&Fn{} } pub fn (mut e Environment) add_method_for_type(type_name string, method &Fn) { @@ -233,12 +242,20 @@ pub fn (mut e Environment) set_fn_scope_by_key(key string, scope &Scope) { // get_fn_scope retrieves the scope for a function by its qualified name pub fn (e &Environment) get_fn_scope(module_name string, fn_name string) ?&Scope { key := if module_name == '' { fn_name } else { '${module_name}__${fn_name}' } - return e.fn_scope_cache[key] or { return none } + return e.get_fn_scope_by_key(key) } // get_scope retrieves a module scope by exact module name. pub fn (e &Environment) get_scope(module_name string) ?&Scope { - return e.scope_cache[module_name] or { return none } + if scope := map_get_scope_by_string(e.scope_cache, module_name) { + return scope + } + rlock e.scopes { + if scope := map_get_scope_by_string(e.scopes, module_name) { + return scope + } + } + return none } // register_global adds or updates a module global in the type environment. @@ -252,7 +269,39 @@ pub fn (mut e Environment) register_global(module_name string, name string, typ // get_fn_scope_by_key retrieves a function scope by its fully-qualified key. pub fn (e &Environment) get_fn_scope_by_key(key string) ?&Scope { - return e.fn_scope_cache[key] or { return none } + if scope := map_get_scope_by_string(e.fn_scope_cache, key) { + return scope + } + rlock e.fn_scopes { + if scope := map_get_scope_by_string(e.fn_scopes, key) { + return scope + } + } + return none +} + +fn map_get_scope_by_string(m map[string]&Scope, key string) ?&Scope { + if scope := m[key] { + return scope + } + for map_key, scope in m { + if map_key == key { + return scope + } + } + return none +} + +fn map_get_fns_by_string(m map[string][]&Fn, key string) ?[]&Fn { + if methods := m[key] { + return methods + } + for map_key, methods in m { + if map_key == key { + return methods + } + } + return none } // snapshot_scopes returns a non-shared copy of the scopes map. @@ -360,7 +409,6 @@ mut: expecting_method bool // Temporary recursion guard for debugging expression cycles. expr_depth int - expr_stack []string field_method_lookup_stack []string // Whether we are inside an unsafe{} block inside_unsafe bool @@ -1082,12 +1130,13 @@ fn (c &Checker) for_in_value_type(iter_type Type) Type { fn (mut c Checker) expr(expr ast.Expr) Type { c.expr_depth++ - c.expr_stack << expr.name() - if c.expr_depth > 2000 { - start := if c.expr_stack.len > 40 { c.expr_stack.len - 40 } else { 0 } + if c.expr_depth > 50 { eprintln('checker expr recursion depth=${c.expr_depth}') - for i := start; i < c.expr_stack.len; i++ { - eprintln(' ${c.expr_stack[i]}') + pos := expr.pos() + if pos.is_valid() { + file := c.file_set.file(pos) + position := file.position(pos) + eprintln(' at ${position.filename}:${position.line}:${position.column}') } panic('checker expr recursion') } @@ -1098,9 +1147,6 @@ fn (mut c Checker) expr(expr ast.Expr) Type { c.env.set_expr_type(pos.id, typ) } c.expr_depth-- - if c.expr_stack.len > 0 { - c.expr_stack.delete_last() - } return typ } @@ -4470,19 +4516,60 @@ fn (mut c Checker) unwrap_smartcast_expr(expr ast.Expr) ast.Expr { } fn smartcast_lookup_name(expr ast.Expr) string { + len := smartcast_lookup_name_len(expr) + if len <= 0 || len > 1024 { + return '' + } + mut sb := strings.new_builder(len) + write_smartcast_lookup_name(expr, mut sb, false) + return sb.str() +} + +fn smartcast_lookup_name_len(expr ast.Expr) int { match expr { ast.AsCastExpr { - return smartcast_lookup_name(expr.expr) + return smartcast_lookup_name_len(expr.expr) } ast.SelectorExpr { - return smartcast_lookup_name(expr.lhs) + '.' + expr.rhs.name + lhs_len := smartcast_lookup_name_len(expr.lhs) + if lhs_len <= 0 || expr.rhs.name.len <= 0 || expr.rhs.name.len > 256 { + return 0 + } + return lhs_len + 1 + expr.rhs.name.len + } + ast.Ident { + return if expr.name.len <= 256 { expr.name.len } else { 0 } } else { - return expr.name() + return 0 } } } +fn write_smartcast_lookup_name(expr ast.Expr, mut sb strings.Builder, wrote bool) bool { + mut did_write := wrote + match expr { + ast.AsCastExpr { + did_write = write_smartcast_lookup_name(expr.expr, mut sb, did_write) + } + ast.SelectorExpr { + did_write = write_smartcast_lookup_name(expr.lhs, mut sb, did_write) + if did_write { + sb.write_u8(`.`) + } + sb.write_string(expr.rhs.name) + did_write = true + } + ast.Ident { + sb.write_string(expr.name) + did_write = true + } + else {} + } + + return did_write +} + // TODO: fn (mut c Checker) unwrap_expr(expr ast.Expr) ast.Expr { match expr { -- 2.39.5