From dff5b1e58122fd131c5ec38b27d095741559a9a6 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 13 May 2026 17:00:38 +0300 Subject: [PATCH] v2: lifetimes; clone() auto-generation for structs with heap-allocated fields (#26859) --- cmd/v/v.v | 11 +- doc/ownership.md | 20 ++ ...ign_immutable_reference_call_result_err.vv | 4 +- .../tests/disable_explicit_mutability.vv | 1 + ...ixed_array_new_syntax_size_mismatch_err.vv | 2 +- .../tests/if_smartcast_mutation_err.vv | 1 + .../tests/orm_dynamic_unknown_field_err.vv | 2 +- ...ool_processor_callback_array_return_err.vv | 2 +- .../testdata/defer_fn_match_branch_capture.vv | 1 + vlib/v2/ast/ast.v | 22 +- vlib/v2/builder/cache_headers.v | 3 + vlib/v2/eval/eval.v | 7 + vlib/v2/gen/cleanc/cleanc_test.v | 92 +++++++ vlib/v2/gen/cleanc/expr.v | 4 + vlib/v2/gen/cleanc/flag_enum_codegen_test.v | 57 ++++ vlib/v2/gen/cleanc/fn.v | 17 ++ .../gen/cleanc/result_option_codegen_test.v | 17 ++ vlib/v2/gen/cleanc/struct.v | 44 ++- vlib/v2/gen/cleanc/types.v | 41 ++- vlib/v2/gen/v/gen.v | 13 + vlib/v2/parser/parser.v | 2 +- vlib/v2/parser/parser_test.v | 255 ++++++++++++++++++ vlib/v2/parser/type.v | 21 +- vlib/v2/transformer/transformer.v | 187 ++++++++++++- vlib/v2/transformer/transformer_test.v | 193 +++++++++++++ vlib/v2/transformer/types.v | 143 +++++++++- vlib/v2/types/checker.v | 45 +++- vlib/v2/types/checker_test.v | 71 +++++ vlib/v2/types/types.v | 4 + 29 files changed, 1235 insertions(+), 47 deletions(-) create mode 100644 vlib/v2/parser/parser_test.v diff --git a/cmd/v/v.v b/cmd/v/v.v index 420d2430b..ca211d595 100644 --- a/cmd/v/v.v +++ b/cmd/v/v.v @@ -234,16 +234,19 @@ fn launch_v2_compiler(is_verbose bool, args []string, is_ownership bool) { tool_name := if is_ownership { 'v2_ownership' } else { 'v2' } mut v2_exe := os.getenv(delegated_v2_exe_env) if v2_exe == '' { - v2_source := os.join_path(vroot, 'cmd', 'v2', 'v2.v') + v2_main_source := os.join_path(vroot, 'cmd', 'v2', 'v2.v') + v2_cmd_dir := os.join_path(vroot, 'cmd', 'v2') + v2_vlib_dir := os.join_path(vroot, 'vlib', 'v2') v2_exe = cached_v2_executable_path(vroot, is_ownership) v2_exe_dir := os.dir(v2_exe) os.mkdir_all(v2_exe_dir) or { eprintln('cannot create `${v2_exe_dir}`: ${err}') exit(1) } - if util.should_recompile_tool(vexe, v2_source, tool_name, v2_exe) { + if util.should_recompile_tool(vexe, v2_cmd_dir, tool_name, v2_exe) + || util.should_recompile_tool(vexe, v2_vlib_dir, tool_name, v2_exe) { d_flag := if is_ownership { '-d ownership ' } else { '' } - compilation_command := '${os.quoted_path(vexe)} ${d_flag}-o ${os.quoted_path(v2_exe)} ${os.quoted_path(v2_source)}' + compilation_command := '${os.quoted_path(vexe)} ${d_flag}-o ${os.quoted_path(v2_exe)} ${os.quoted_path(v2_main_source)}' if is_verbose { println('Compiling ${tool_name} with: "${compilation_command}"') } @@ -252,7 +255,7 @@ fn launch_v2_compiler(is_verbose bool, args []string, is_ownership bool) { tool_compilation := os.execute(compilation_command) os.chdir(current_work_dir) or {} if tool_compilation.exit_code != 0 { - eprintln('cannot compile `${v2_source}`: ${tool_compilation.exit_code}\n${tool_compilation.output}') + eprintln('cannot compile `${v2_main_source}`: ${tool_compilation.exit_code}\n${tool_compilation.output}') exit(1) } } diff --git a/doc/ownership.md b/doc/ownership.md index bcdebfd62..af5519fd3 100644 --- a/doc/ownership.md +++ b/doc/ownership.md @@ -88,6 +88,26 @@ fn main() { } ``` +### Explicit lifetimes + +Ownership mode also supports explicit named lifetimes with `^name`. + +Use `&^a T` for a borrowed reference with an explicit lifetime and `[^a]` +in generic parameter and argument lists: + +```v ignore +struct Ignore {} + +struct IgnoreMatch[^a] {} + +fn matched_dir_entry[^a](self &^a Ignore) IgnoreMatch[^a] { + return IgnoreMatch[^a]{} +} +``` + +`^` is used instead of Rust's `'` because `'` is already used for string and +character literals in V. + Multiple immutable borrows are allowed: ```v okfmt diff --git a/vlib/v/checker/tests/assign_immutable_reference_call_result_err.vv b/vlib/v/checker/tests/assign_immutable_reference_call_result_err.vv index e4c59eb1c..2b300fa82 100644 --- a/vlib/v/checker/tests/assign_immutable_reference_call_result_err.vv +++ b/vlib/v/checker/tests/assign_immutable_reference_call_result_err.vv @@ -11,7 +11,9 @@ fn rere(a &User) &User { } fn main() { - ja := User{name: 'foo'} + ja := User{ + name: 'foo' + } mut x := rere(ja) x.name = 'bar' } diff --git a/vlib/v/checker/tests/disable_explicit_mutability.vv b/vlib/v/checker/tests/disable_explicit_mutability.vv index f5b6e0f47..0612bb7ce 100644 --- a/vlib/v/checker/tests/disable_explicit_mutability.vv +++ b/vlib/v/checker/tests/disable_explicit_mutability.vv @@ -31,5 +31,6 @@ fn main() { } u8 {} } + println('${counter.value} ${arr.len} ${nums.len} ${inc_copy(1)} ${i}') } diff --git a/vlib/v/checker/tests/fixed_array_new_syntax_size_mismatch_err.vv b/vlib/v/checker/tests/fixed_array_new_syntax_size_mismatch_err.vv index 0547a34fb..78832a5d1 100644 --- a/vlib/v/checker/tests/fixed_array_new_syntax_size_mismatch_err.vv +++ b/vlib/v/checker/tests/fixed_array_new_syntax_size_mismatch_err.vv @@ -1,3 +1,3 @@ fn main() { - _ := [2]int[1 2 3] + _ := [2]int[1, 2, 3] } diff --git a/vlib/v/checker/tests/if_smartcast_mutation_err.vv b/vlib/v/checker/tests/if_smartcast_mutation_err.vv index c799dd931..881a7aa72 100644 --- a/vlib/v/checker/tests/if_smartcast_mutation_err.vv +++ b/vlib/v/checker/tests/if_smartcast_mutation_err.vv @@ -1,4 +1,5 @@ type SmartcastValue = int | string + fn main() { a := SmartcastValue(1) if a is int { diff --git a/vlib/v/checker/tests/orm_dynamic_unknown_field_err.vv b/vlib/v/checker/tests/orm_dynamic_unknown_field_err.vv index 8293d2774..be2063950 100644 --- a/vlib/v/checker/tests/orm_dynamic_unknown_field_err.vv +++ b/vlib/v/checker/tests/orm_dynamic_unknown_field_err.vv @@ -8,7 +8,7 @@ struct User { fn main() { db := sqlite.connect(':memory:') or { panic(err) } where_expr := { - if true { zzz == 'Alice' }, + if true { zzz == 'Alice' } } _ := sql db { dynamic select from User where where_expr diff --git a/vlib/v/checker/tests/pool_processor_callback_array_return_err.vv b/vlib/v/checker/tests/pool_processor_callback_array_return_err.vv index 868c1a242..31fafe05a 100644 --- a/vlib/v/checker/tests/pool_processor_callback_array_return_err.vv +++ b/vlib/v/checker/tests/pool_processor_callback_array_return_err.vv @@ -6,7 +6,7 @@ struct Foo { fn main() { _ := pool.new_pool_processor( - callback: fn (mut pp pool.PoolProcessor, idx int, wid int) []Foo { + callback: fn (mut pp pool.PoolProcessor, idx int, wid int) []main.Foo { _ = wid return [Foo{ page: pp.get_item[int](idx).str() diff --git a/vlib/v/gen/c/testdata/defer_fn_match_branch_capture.vv b/vlib/v/gen/c/testdata/defer_fn_match_branch_capture.vv index 6bb569cf5..ad03e4d57 100644 --- a/vlib/v/gen/c/testdata/defer_fn_match_branch_capture.vv +++ b/vlib/v/gen/c/testdata/defer_fn_match_branch_capture.vv @@ -19,6 +19,7 @@ fn (mut o Object) f(x int) int { } else {} } + return 42 } diff --git a/vlib/v2/ast/ast.v b/vlib/v2/ast/ast.v index a744fdde9..901c735f2 100644 --- a/vlib/v2/ast/ast.v +++ b/vlib/v2/ast/ast.v @@ -33,6 +33,7 @@ pub type Expr = ArrayInitExpr | Keyword | KeywordOperator | LambdaExpr + | LifetimeExpr | LockExpr | MapInitExpr | MatchExpr @@ -94,6 +95,7 @@ pub type Type = AnonStructType | NilType | NoneType | OptionType + | PointerType | ResultType | ThreadType | TupleType @@ -155,6 +157,9 @@ pub fn (expr Expr) name() string { Keyword { expr.tok.str() } + LifetimeExpr { + '^' + expr.name + } ModifierExpr { '${expr.kind} ${expr.expr.name()}' } @@ -239,6 +244,9 @@ pub fn (expr Expr) pos() token.Pos { KeywordOperator { expr.pos } + LifetimeExpr { + expr.pos + } LambdaExpr { expr.pos } @@ -412,7 +420,7 @@ pub: pub struct GenericArgs { pub: lhs Expr - args []Expr // concrete types + args []Expr // concrete types and lifetimes pos token.Pos } @@ -561,6 +569,12 @@ pub: pos token.Pos } +pub struct LifetimeExpr { +pub: + name string + pos token.Pos +} + // name_str returns the parameter name. pub fn (p Parameter) name_str() string { return p.name @@ -1039,6 +1053,12 @@ pub: base_type Expr = empty_expr } +pub struct PointerType { +pub: + base_type Expr = empty_expr + lifetime string +} + pub struct ResultType { pub: base_type Expr = empty_expr diff --git a/vlib/v2/builder/cache_headers.v b/vlib/v2/builder/cache_headers.v index af8624827..d526e7d42 100644 --- a/vlib/v2/builder/cache_headers.v +++ b/vlib/v2/builder/cache_headers.v @@ -1394,6 +1394,9 @@ fn header_type_node_is_usable(node ast.Type) bool { ast.OptionType { node.base_type !is ast.EmptyExpr && header_type_expr_is_usable(node.base_type) } + ast.PointerType { + header_type_expr_is_usable(node.base_type) + } ast.ResultType { node.base_type !is ast.EmptyExpr && header_type_expr_is_usable(node.base_type) } diff --git a/vlib/v2/eval/eval.v b/vlib/v2/eval/eval.v index 417eb8fa3..4897a0bfc 100644 --- a/vlib/v2/eval/eval.v +++ b/vlib/v2/eval/eval.v @@ -5796,6 +5796,13 @@ fn (e &Eval) type_node_name(typ ast.Type) string { ast.OptionType { '?${e.type_expr_name(typ.base_type)}' } + ast.PointerType { + if typ.lifetime != '' { + '&^${typ.lifetime} ${e.type_expr_name(typ.base_type)}' + } else { + '&${e.type_expr_name(typ.base_type)}' + } + } ast.ResultType { '!${e.type_expr_name(typ.base_type)}' } diff --git a/vlib/v2/gen/cleanc/cleanc_test.v b/vlib/v2/gen/cleanc/cleanc_test.v index bb6fdbedf..00d6ff9b4 100644 --- a/vlib/v2/gen/cleanc/cleanc_test.v +++ b/vlib/v2/gen/cleanc/cleanc_test.v @@ -2,6 +2,7 @@ module cleanc import v2.ast +import v2.types fn test_c_string_literal_content_to_c_single_line() { out := c_string_literal_content_to_c('hello') @@ -33,6 +34,97 @@ fn test_c_string_literal_content_to_c_splits_hex_escape_before_hex_digit() { assert out == '"\\x0c""8"' } +fn test_struct_generic_params_need_bindings_returns_false_for_lifetime_only_params() { + params := [ + ast.Expr(ast.LifetimeExpr{ + name: 'a' + }), + ast.Expr(ast.LifetimeExpr{ + name: 'b' + }), + ] + assert !struct_generic_params_need_bindings(params) +} + +fn test_struct_generic_params_need_bindings_returns_true_for_runtime_generic_params() { + params := [ + ast.Expr(ast.LifetimeExpr{ + name: 'a' + }), + ast.Expr(ast.Ident{ + name: 'T' + }), + ] + assert struct_generic_params_need_bindings(params) +} + +fn test_runtime_generic_params_filter_lifetime_params() { + params := [ + ast.Expr(ast.LifetimeExpr{ + name: 'a' + }), + ast.Expr(ast.Ident{ + name: 'T' + }), + ] + args := [ + ast.Expr(ast.LifetimeExpr{ + name: 'a' + }), + ast.Expr(ast.Ident{ + name: 'Value' + }), + ] + filtered_args := runtime_generic_args(args) + assert runtime_generic_param_names(['^a', 'T']) == ['T'] + assert generic_param_names(params) == ['T'] + assert filtered_args.len == 1 + assert filtered_args[0] is ast.Ident + assert (filtered_args[0] as ast.Ident).name == 'Value' +} + +fn test_record_generic_struct_bindings_filters_lifetime_params() { + mut env := types.Environment.new() + mut scope := types.new_scope(unsafe { nil }) + scope.insert('Ref', types.Object(types.Type(types.Struct{ + name: 'Ref' + generic_params: ['^a', 'T'] + }))) + scope.insert('Value', types.Object(types.Type(types.Struct{ + name: 'Value' + }))) + lock env.scopes { + env.scopes['main'] = scope + } + mut g := Gen.new_with_env([], env) + g.record_generic_struct_bindings('Ref', 'Ref', [ + ast.Expr(ast.LifetimeExpr{ + name: 'a' + }), + ast.Expr(ast.Ident{ + name: 'Value' + }), + ]) + bindings := (g.generic_struct_bindings['Ref'] or { panic('missing Ref binding') }).clone() + value_type := bindings['T'] or { panic('missing T binding') } + assert value_type.name() == 'Value' + instances := g.generic_struct_instances['Ref'] + assert instances.len == 1 + assert instances[0].params_key == 'Value' +} + +fn test_expr_type_to_c_lowers_pointer_type() { + mut g := Gen.new([]) + pointer_type := ast.Expr(ast.Type(ast.PointerType{ + base_type: ast.Expr(ast.Ident{ + name: 'Foo' + }) + })) + assert g.expr_type_to_c(pointer_type) == 'Foo*' + assert g.is_pointer_type(pointer_type) + assert g.receiver_type_to_scope_name(pointer_type) == 'Foo' +} + fn test_fixed_array_elem_type_ready_accepts_primitive_alias() { mut g := Gen.new([]) g.primitive_type_aliases['sha3__Lane'] = true diff --git a/vlib/v2/gen/cleanc/expr.v b/vlib/v2/gen/cleanc/expr.v index 42afc0f3e..d55ebf4cf 100644 --- a/vlib/v2/gen/cleanc/expr.v +++ b/vlib/v2/gen/cleanc/expr.v @@ -1612,6 +1612,10 @@ fn (mut g Gen) expr(node ast.Expr) { g.sb.write_string(c_static_v_string_expr_from_c_literal(c_lit)) } } + ast.LifetimeExpr { + g.sb.write_string('lt__') + g.sb.write_string(node.name) + } ast.Ident { g.mark_needed_ierror_wrapper_from_ident(node.name) if node.name == 'nil' { diff --git a/vlib/v2/gen/cleanc/flag_enum_codegen_test.v b/vlib/v2/gen/cleanc/flag_enum_codegen_test.v index 2d9e0de0f..206a26ab5 100644 --- a/vlib/v2/gen/cleanc/flag_enum_codegen_test.v +++ b/vlib/v2/gen/cleanc/flag_enum_codegen_test.v @@ -92,3 +92,60 @@ fn test_generate_c_uses_concrete_map_method_name_in_generic_comptime_body() { assert csrc.contains('Map_string_int__query_item(') assert !csrc.contains('map__query_item(') } + +fn test_generate_c_filters_lifetime_params_from_generic_struct_binding() { + csrc := generate_c_for_test(' +struct Value { + n int +} + +struct Ref[^a, T] { + value T +} + +struct Holder[^a] { + item Ref[^a, Value] +} +') + assert csrc.contains('struct Ref {') + assert csrc.contains('Value value;') + assert !csrc.contains('T value;') +} + +fn test_generate_c_lowers_pointer_type_params_receivers_fields_and_generics() { + csrc := generate_c_for_test(' +struct Foo { + value int +} + +struct Node[T] { + value T +} + +struct Holder { + item &Foo + node &Node[Foo] +} + +fn ptr_value(foo &Foo) int { + return foo.value +} + +fn (foo &Foo) method_value() int { + return foo.value +} + +fn main() { + foo := Foo{} + _ := ptr_value(&foo) + _ := foo.method_value() +} +') + assert csrc.contains('Foo* item;') + assert csrc.contains('Node* node;') + assert csrc.contains('Foo value;') + assert csrc.contains('ptr_value(Foo* foo)') + assert csrc.contains('Foo__method_value(Foo* foo)') + assert !csrc.contains('int item;') + assert !csrc.contains('int ptr_value(int foo)') +} diff --git a/vlib/v2/gen/cleanc/fn.v b/vlib/v2/gen/cleanc/fn.v index 1d3963426..facb8d85e 100644 --- a/vlib/v2/gen/cleanc/fn.v +++ b/vlib/v2/gen/cleanc/fn.v @@ -530,6 +530,9 @@ fn collect_generic_placeholder_names_from_expr(expr ast.Expr, mut seen map[strin collect_generic_placeholder_names_from_expr(expr.key_type, mut seen, mut out) collect_generic_placeholder_names_from_expr(expr.value_type, mut seen, mut out) } + ast.PointerType { + collect_generic_placeholder_names_from_expr(expr.base_type, mut seen, mut out) + } ast.OptionType { collect_generic_placeholder_names_from_expr(expr.base_type, mut seen, mut out) } @@ -972,6 +975,12 @@ fn infer_generic_type_bindings_from_param(param ast.Expr, concrete types.Type, g concrete.value_type, generic_params, mut bindings) } } + ast.PointerType { + if concrete is types.Pointer { + infer_generic_type_bindings_from_param(param.base_type, concrete.base_type, + generic_params, mut bindings) + } + } ast.OptionType { if concrete is types.OptionType { infer_generic_type_bindings_from_param(param.base_type, concrete.base_type, @@ -1004,6 +1013,11 @@ fn direct_generic_placeholder_name(e ast.Expr) string { ast.ModifierExpr { return direct_generic_placeholder_name(e.expr) } + ast.Type { + if e is ast.PointerType { + return direct_generic_placeholder_name(e.base_type) + } + } else {} } @@ -4946,6 +4960,9 @@ fn expr_has_generic_placeholder(e ast.Expr) bool { return expr_has_generic_placeholder(e.key_type) || expr_has_generic_placeholder(e.value_type) } + ast.PointerType { + return expr_has_generic_placeholder(e.base_type) + } ast.OptionType { return expr_has_generic_placeholder(e.base_type) } diff --git a/vlib/v2/gen/cleanc/result_option_codegen_test.v b/vlib/v2/gen/cleanc/result_option_codegen_test.v index 7c48f2857..c0083c944 100644 --- a/vlib/v2/gen/cleanc/result_option_codegen_test.v +++ b/vlib/v2/gen/cleanc/result_option_codegen_test.v @@ -56,3 +56,20 @@ fn find_stop() int { assert csrc.contains('_option_int _or_t') assert csrc.contains('stop_index') } + +fn test_generate_c_expands_builtin_option_clone_if_guard() { + csrc := generate_result_option_c_for_test(' +interface IClone {} + +struct Bag implements IClone { + name ?string +} + +fn copy_bag(b Bag) Bag { + return b.clone() +} +') + assert csrc.contains('builtin__Option_string__clone') + assert csrc.contains('.state == 0') + assert !csrc.contains('if (s)') +} diff --git a/vlib/v2/gen/cleanc/struct.v b/vlib/v2/gen/cleanc/struct.v index d43a7fe08..03c2b5556 100644 --- a/vlib/v2/gen/cleanc/struct.v +++ b/vlib/v2/gen/cleanc/struct.v @@ -7,6 +7,37 @@ module cleanc import v2.ast import v2.types +fn struct_generic_params_need_bindings(params []ast.Expr) bool { + for param in params { + if param !is ast.LifetimeExpr { + return true + } + } + return false +} + +fn runtime_generic_param_names(param_names []string) []string { + mut names := []string{cap: param_names.len} + for name in param_names { + if name.starts_with('^') { + continue + } + names << name + } + return names +} + +fn runtime_generic_args(params []ast.Expr) []ast.Expr { + mut args := []ast.Expr{cap: params.len} + for param in params { + if param is ast.LifetimeExpr { + continue + } + args << param + } + return args +} + // collect_generic_struct_bindings scans all struct fields for GenericType // instantiations (e.g. LinkedList[ValueInfo]) and records the concrete type // bindings so that methods on generic structs can resolve their generic params. @@ -87,6 +118,9 @@ fn (mut g Gen) propagate_generic_bindings(e ast.Expr, parent_bindings map[string g.propagate_generic_bindings(param, parent_bindings) } } + if e is ast.PointerType { + g.propagate_generic_bindings(e.base_type, parent_bindings) + } } ast.PrefixExpr { g.propagate_generic_bindings(e.expr, parent_bindings) @@ -156,6 +190,9 @@ fn (mut g Gen) scan_expr_for_generic_types(e ast.Expr) { g.scan_expr_for_generic_types(e.key_type) g.scan_expr_for_generic_types(e.value_type) } + if e is ast.PointerType { + g.scan_expr_for_generic_types(e.base_type) + } if e is ast.OptionType { g.scan_expr_for_generic_types(e.base_type) } @@ -508,7 +545,8 @@ fn (mut g Gen) gen_struct_decl(node ast.StructDecl) { // Generic structs are emitted using concrete bindings recorded from // GenericType instantiations, falling back to the first binding in env. prev_generic_types := g.active_generic_types.clone() - if node.generic_params.len > 0 { + needs_runtime_generic_bindings := struct_generic_params_need_bindings(node.generic_params) + if needs_runtime_generic_bindings { struct_c_name := g.get_struct_name(node) if struct_c_name in g.generic_struct_bindings { g.active_generic_types = g.generic_struct_bindings[struct_c_name].clone() @@ -539,7 +577,7 @@ fn (mut g Gen) gen_struct_decl(node ast.StructDecl) { // For generic structs with active bindings, verify that all field types // are already emitted. This prevents the last-resort pass from emitting // a generic struct body before its concrete field types are defined. - if node.generic_params.len > 0 && g.active_generic_types.len > 0 { + if needs_runtime_generic_bindings && g.active_generic_types.len > 0 { if !g.struct_fields_resolved(node) { return } @@ -728,7 +766,7 @@ fn (mut g Gen) gen_struct_decl(node ast.StructDecl) { } // Emit additional generic struct instantiations (e.g. Node[StructFieldInfo] // when Node[ValueInfo] was the primary binding). - if node.generic_params.len > 0 { + if needs_runtime_generic_bindings { instances := g.generic_struct_instances[name] for inst in instances { if inst.c_name == name { diff --git a/vlib/v2/gen/cleanc/types.v b/vlib/v2/gen/cleanc/types.v index 7b799f58e..ecb808b3d 100644 --- a/vlib/v2/gen/cleanc/types.v +++ b/vlib/v2/gen/cleanc/types.v @@ -603,6 +603,11 @@ fn (mut g Gen) is_pointer_type(e ast.Expr) bool { if e is ast.ModifierExpr { return g.is_pointer_type(e.expr) } + if e is ast.Type { + if e is ast.PointerType { + return true + } + } if e is ast.Ident { if e.name in ['voidptr', 'charptr', 'byteptr'] || e.name.ends_with('ptr') { return true @@ -1685,6 +1690,9 @@ fn (g &Gen) receiver_type_to_scope_name(typ ast.Expr) string { } } if typ is ast.Type { + if typ is ast.PointerType { + return g.receiver_type_to_scope_name(typ.base_type) + } // Array type: []T -> "[]T" if typ is ast.ArrayType { elem := g.receiver_type_to_scope_name(typ.elem_type) @@ -2593,6 +2601,9 @@ fn (mut g Gen) expr_type_to_c(e ast.Expr) string { if e is ast.ChannelType { return 'chan' } + if e is ast.PointerType { + return g.expr_type_to_c(e.base_type) + '*' + } if e is ast.TupleType { mut elem_types := []string{cap: e.types.len} for t in e.types { @@ -2706,15 +2717,16 @@ fn (g &Gen) is_c_type_name(name string) bool { fn (mut g Gen) record_generic_struct_bindings(struct_base_name string, struct_c_name string, concrete_params []ast.Expr) { // Find the struct decl to get generic param names. env_struct := g.lookup_struct_type(struct_base_name) - generic_param_names := env_struct.generic_params - if generic_param_names.len == 0 || generic_param_names.len != concrete_params.len { + generic_param_names := runtime_generic_param_names(env_struct.generic_params) + concrete_runtime_params := runtime_generic_args(concrete_params) + if generic_param_names.len == 0 || generic_param_names.len != concrete_runtime_params.len { return } // Check that all concrete params are non-placeholder types. mut bindings := map[string]types.Type{} - mut param_c_names := []string{cap: concrete_params.len} + mut param_c_names := []string{cap: concrete_runtime_params.len} for i, param_name in generic_param_names { - concrete_expr := concrete_params[i] + concrete_expr := concrete_runtime_params[i] if is_generic_placeholder_type_name(concrete_expr.name()) { return } @@ -2762,14 +2774,15 @@ fn (mut g Gen) record_generic_struct_bindings(struct_base_name string, struct_c_ // by resolving placeholder params through the parent struct's known bindings. fn (mut g Gen) record_generic_struct_bindings_with_parent(struct_base_name string, struct_c_name string, concrete_params []ast.Expr, parent_bindings map[string]types.Type) { env_struct := g.lookup_struct_type(struct_base_name) - generic_param_names := env_struct.generic_params - if generic_param_names.len == 0 || generic_param_names.len != concrete_params.len { + generic_param_names := runtime_generic_param_names(env_struct.generic_params) + concrete_runtime_params := runtime_generic_args(concrete_params) + if generic_param_names.len == 0 || generic_param_names.len != concrete_runtime_params.len { return } mut bindings := map[string]types.Type{} - mut param_c_names := []string{cap: concrete_params.len} + mut param_c_names := []string{cap: concrete_runtime_params.len} for i, param_name in generic_param_names { - concrete_expr := concrete_params[i] + concrete_expr := concrete_runtime_params[i] expr_name := concrete_expr.name() if is_generic_placeholder_type_name(expr_name) { if parent_type := parent_bindings[expr_name] { @@ -2825,8 +2838,9 @@ fn (mut g Gen) resolve_generic_struct_c_name(base_name string, concrete_params [ if instances.len <= 1 { return base_name } - mut param_c_names := []string{cap: concrete_params.len} - for p in concrete_params { + concrete_runtime_params := runtime_generic_args(concrete_params) + mut param_c_names := []string{cap: concrete_runtime_params.len} + for p in concrete_runtime_params { param_c_names << g.expr_type_to_c(p) } params_key := param_c_names.join('_') @@ -3928,9 +3942,10 @@ fn (mut g Gen) resolve_generic_struct_field_name(expr ast.Expr) string { // Build the params_key from active_generic_types // Find the struct's generic params to know the order env_struct := g.lookup_struct_type(c_name.all_after_last('__')) - if env_struct.generic_params.len > 0 { - mut param_c_names := []string{cap: env_struct.generic_params.len} - for param_name in env_struct.generic_params { + generic_param_names := runtime_generic_param_names(env_struct.generic_params) + if generic_param_names.len > 0 { + mut param_c_names := []string{cap: generic_param_names.len} + for param_name in generic_param_names { if concrete := g.active_generic_types[param_name] { param_c_names << g.types_type_to_c(concrete) } else { diff --git a/vlib/v2/gen/v/gen.v b/vlib/v2/gen/v/gen.v index 748e07462..260c08d7f 100644 --- a/vlib/v2/gen/v/gen.v +++ b/vlib/v2/gen/v/gen.v @@ -584,6 +584,10 @@ fn (mut g Gen) expr(expr ast.Expr) { g.write(')') } } + ast.LifetimeExpr { + g.write('^') + g.write(expr.name) + } ast.LambdaExpr { g.write('|') for i, arg in expr.args { @@ -821,6 +825,15 @@ fn (mut g Gen) expr(expr ast.Expr) { g.expr(expr.base_type) } } + ast.PointerType { + g.write('&') + if expr.lifetime != '' { + g.write('^') + g.write(expr.lifetime) + g.write(' ') + } + g.expr(expr.base_type) + } ast.ResultType { g.write('!') if expr.base_type !is ast.EmptyExpr { diff --git a/vlib/v2/parser/parser.v b/vlib/v2/parser/parser.v index 171a0c15b..8858933db 100644 --- a/vlib/v2/parser/parser.v +++ b/vlib/v2/parser/parser.v @@ -1465,7 +1465,7 @@ fn (mut p Parser) expr_or_type(min_bp token.BindingPower) ast.Expr { // TODO: is there a better way to do this? see uses of `p.exp_pt` exp_pt := p.exp_pt p.exp_pt = true - expr := p.expr(min_bp) + expr := if p.tok == .xor { p.expect_type() } else { p.expr(min_bp) } p.exp_pt = exp_pt return expr } diff --git a/vlib/v2/parser/parser_test.v b/vlib/v2/parser/parser_test.v new file mode 100644 index 000000000..c2f7956fa --- /dev/null +++ b/vlib/v2/parser/parser_test.v @@ -0,0 +1,255 @@ +module parser + +import os +import time +import v2.ast +import v2.pref +import v2.token + +fn parse_code(code string) ast.File { + tmp_file := os.join_path(os.temp_dir(), 'v2_parser_test_${os.getpid()}_${time.now().unix_micro()}.v') + os.write_file(tmp_file, code) or { panic(err) } + defer { + os.rm(tmp_file) or {} + } + prefs := &pref.Preferences{} + mut file_set := token.FileSet.new() + mut p := Parser.new(prefs) + return p.parse_file(tmp_file, mut file_set) +} + +fn first_array_init(code string) ast.ArrayInitExpr { + file := parse_code(code) + assert file.stmts.len == 1 + assert file.stmts[0] is ast.FnDecl + fn_decl := file.stmts[0] as ast.FnDecl + assert fn_decl.stmts.len == 1 + assert fn_decl.stmts[0] is ast.AssignStmt + assign := fn_decl.stmts[0] as ast.AssignStmt + assert assign.rhs.len == 1 + assert assign.rhs[0] is ast.ArrayInitExpr + return assign.rhs[0] as ast.ArrayInitExpr +} + +fn assert_anon_struct_array_type(typ ast.Expr, expect_fixed bool) { + assert typ is ast.Type + type_node := typ as ast.Type + match type_node { + ast.ArrayFixedType { + assert expect_fixed + assert type_node.elem_type is ast.Type + assert (type_node.elem_type as ast.Type) is ast.AnonStructType + } + ast.ArrayType { + assert !expect_fixed + assert type_node.elem_type is ast.Type + assert (type_node.elem_type as ast.Type) is ast.AnonStructType + } + else { + assert false + } + } +} + +fn test_dynamic_array_of_anon_struct_init() { + array_init := first_array_init('fn main() { text_syms := []struct { name string value u64 }{} }') + assert_anon_struct_array_type(array_init.typ, false) +} + +fn test_fixed_array_of_anon_struct_init() { + array_init := first_array_init('fn main() { points := [2]struct { x int y int }{} }') + assert_anon_struct_array_type(array_init.typ, true) +} + +fn test_return_stmt_allows_bare_prefix_exprs() { + file := parse_code('fn ptr(v &int) &int { return &v }') + assert file.stmts.len == 1 + assert file.stmts[0] is ast.FnDecl + fn_decl := file.stmts[0] as ast.FnDecl + assert fn_decl.stmts.len == 1 + assert fn_decl.stmts[0] is ast.ReturnStmt + ret := fn_decl.stmts[0] as ast.ReturnStmt + assert ret.exprs.len == 1 + assert ret.exprs[0] is ast.PrefixExpr + prefix := ret.exprs[0] as ast.PrefixExpr + assert prefix.op == .amp + assert prefix.expr is ast.Ident +} + +fn test_char_literal_assignment_parses() { + file := parse_code('fn main() { c := `\\n` }') + assert file.stmts.len == 1 + assert file.stmts[0] is ast.FnDecl + fn_decl := file.stmts[0] as ast.FnDecl + assert fn_decl.stmts.len == 1 + assert fn_decl.stmts[0] is ast.AssignStmt + assign := fn_decl.stmts[0] as ast.AssignStmt + assert assign.rhs.len == 1 + assert assign.rhs[0] is ast.BasicLiteral + literal := assign.rhs[0] as ast.BasicLiteral + assert literal.kind == .char + assert literal.value == '\\n' +} + +fn test_return_stmt_allows_amp_cast_expr_in_unsafe_method() { + file := parse_code('module main + +struct A { + data voidptr + element_size int +} + +fn (a A) get_unsafe(i int) voidptr { + unsafe { + return &u8(a.data) + u64(i) * u64(a.element_size) + } +}') + assert file.stmts.len == 3 + assert file.stmts[2] is ast.FnDecl +} + +fn test_struct_field_default_allows_amp_struct_init() { + file := parse_code('module main + +struct Foo {} + +struct Bar { + file &Foo = &Foo{} +}') + assert file.stmts.len == 3 + assert file.stmts[2] is ast.StructDecl +} + +fn test_reused_parser_resets_state_between_files() { + tmp1 := os.join_path(os.temp_dir(), 'v2_parser_state_${os.getpid()}_${time.now().unix_micro()}_1.v') + tmp2 := os.join_path(os.temp_dir(), 'v2_parser_state_${os.getpid()}_${time.now().unix_micro()}_2.v') + os.write_file(tmp1, 'module main + +fn main() { + if true {} +} +') or { panic(err) } + os.write_file(tmp2, 'module strings + +// import rand +fn trim_spaces(s string) string { + return s +} +') or { + panic(err) + } + defer { + os.rm(tmp1) or {} + os.rm(tmp2) or {} + } + prefs := &pref.Preferences{} + mut file_set := token.FileSet.new() + mut parser := Parser.new(prefs) + files := parser.parse_files([tmp1, tmp2], mut file_set) + assert files.len == 2 + assert files[1].imports.len == 0 +} + +fn test_explicit_lifetime_generic_params_and_pointer_types_parse() { + file := parse_code('module main + +struct Ignore {} + +struct IgnoreMatch[^a] {} + +fn matched_dir_entry[^a](self &^a Ignore) IgnoreMatch[^a] { + return IgnoreMatch[^a]{} +} +') + assert file.stmts.len == 4 + assert file.stmts[2] is ast.StructDecl + struct_decl := file.stmts[2] as ast.StructDecl + assert struct_decl.generic_params.len == 1 + assert struct_decl.generic_params[0] is ast.LifetimeExpr + lifetime_param := struct_decl.generic_params[0] as ast.LifetimeExpr + assert lifetime_param.name == 'a' + assert file.stmts[3] is ast.FnDecl + fn_decl := file.stmts[3] as ast.FnDecl + assert fn_decl.typ.generic_params.len == 1 + assert fn_decl.typ.generic_params[0] is ast.LifetimeExpr + assert fn_decl.typ.params.len == 1 + assert fn_decl.typ.params[0].typ is ast.Type + param_type := fn_decl.typ.params[0].typ as ast.Type + assert param_type is ast.PointerType + ptr_type := param_type as ast.PointerType + assert ptr_type.lifetime == 'a' + assert ptr_type.base_type is ast.Ident + assert (ptr_type.base_type as ast.Ident).name == 'Ignore' + assert fn_decl.typ.return_type is ast.Type + return_type := fn_decl.typ.return_type as ast.Type + assert return_type is ast.GenericType + return_generic := return_type as ast.GenericType + assert return_generic.name is ast.Ident + assert (return_generic.name as ast.Ident).name == 'IgnoreMatch' + assert return_generic.params.len == 1 + assert return_generic.params[0] is ast.LifetimeExpr + assert (return_generic.params[0] as ast.LifetimeExpr).name == 'a' +} + +fn test_explicit_lifetime_method_with_nested_generic_return_type_parse() { + file := parse_code('module main + +struct Ignore {} + +struct DirEntry {} + +struct Match[T] { + value T +} + +struct IgnoreMatch[^a] { + ig &^a Ignore +} + +fn (ig &^a Ignore) matched_dir_entry[^a](dent &DirEntry) Match[IgnoreMatch[^a]] { + return Match[IgnoreMatch[^a]]{ + value: IgnoreMatch[^a]{ + ig: ig + } + } +} +') + assert file.stmts.len == 6 + assert file.stmts[5] is ast.FnDecl + fn_decl := file.stmts[5] as ast.FnDecl + assert fn_decl.is_method + assert fn_decl.receiver.typ is ast.Type + receiver_type := fn_decl.receiver.typ as ast.Type + assert receiver_type is ast.PointerType + receiver_ptr := receiver_type as ast.PointerType + assert receiver_ptr.lifetime == 'a' + assert receiver_ptr.base_type is ast.Ident + assert (receiver_ptr.base_type as ast.Ident).name == 'Ignore' + assert fn_decl.typ.generic_params.len == 1 + assert fn_decl.typ.generic_params[0] is ast.LifetimeExpr + assert (fn_decl.typ.generic_params[0] as ast.LifetimeExpr).name == 'a' + assert fn_decl.typ.params.len == 1 + assert fn_decl.typ.params[0].typ is ast.Type + dent_type := fn_decl.typ.params[0].typ as ast.Type + assert dent_type is ast.PointerType + dent_ptr := dent_type as ast.PointerType + assert dent_ptr.lifetime == '' + assert dent_ptr.base_type is ast.Ident + assert (dent_ptr.base_type as ast.Ident).name == 'DirEntry' + assert fn_decl.typ.return_type is ast.Type + return_type := fn_decl.typ.return_type as ast.Type + assert return_type is ast.GenericType + outer_generic := return_type as ast.GenericType + assert outer_generic.name is ast.Ident + assert (outer_generic.name as ast.Ident).name == 'Match' + assert outer_generic.params.len == 1 + assert outer_generic.params[0] is ast.Type + inner_type := outer_generic.params[0] as ast.Type + assert inner_type is ast.GenericType + inner_generic := inner_type as ast.GenericType + assert inner_generic.name is ast.Ident + assert (inner_generic.name as ast.Ident).name == 'IgnoreMatch' + assert inner_generic.params.len == 1 + assert inner_generic.params[0] is ast.LifetimeExpr + assert (inner_generic.params[0] as ast.LifetimeExpr).name == 'a' +} diff --git a/vlib/v2/parser/type.v b/vlib/v2/parser/type.v index 9674c8e44..f473a2d0d 100644 --- a/vlib/v2/parser/type.v +++ b/vlib/v2/parser/type.v @@ -21,13 +21,28 @@ fn (mut p Parser) expect_type() ast.Expr { // pub fn (mut p Parser) try_type() !ast.Expr { fn (mut p Parser) try_type() ast.Expr { match p.tok { + // lifetime: `^a` + .xor { + pos := p.pos + p.next() + name := p.expect_name() + return ast.LifetimeExpr{ + name: name + pos: pos + } + } // pointer: `&type` .amp { p.next() - return ast.PrefixExpr{ - op: .amp - expr: p.expect_type() + mut lifetime := '' + if p.tok == .xor { + p.next() + lifetime = p.expect_name() } + return ast.Type(ast.PointerType{ + base_type: p.expect_type() + lifetime: lifetime + }) } // comptime type: `$enum` | `$struct` | etc .dollar { diff --git a/vlib/v2/transformer/transformer.v b/vlib/v2/transformer/transformer.v index 14ad5cf43..54648923b 100644 --- a/vlib/v2/transformer/transformer.v +++ b/vlib/v2/transformer/transformer.v @@ -39,6 +39,9 @@ mut: needed_str_fns map[string]string // Track needed auto-generated clone functions (fn_name -> struct_name) needed_clone_fns map[string]string + // Track needed auto-generated clone functions for sum types and option wrappers. + needed_sumtype_clone_fns map[string]types.SumType + needed_option_clone_fns map[string]types.OptionType // Track needed auto-generated array helper functions needed_array_contains_fns map[string]ArrayMethodInfo needed_array_index_fns map[string]ArrayMethodInfo @@ -214,6 +217,8 @@ pub fn Transformer.new_with_pref(files []ast.File, env &types.Environment, p &pr env: unsafe { env } needed_str_fns: map[string]string{} needed_clone_fns: map[string]string{} + needed_sumtype_clone_fns: map[string]types.SumType{} + needed_option_clone_fns: map[string]types.OptionType{} needed_array_contains_fns: map[string]ArrayMethodInfo{} needed_array_index_fns: map[string]ArrayMethodInfo{} needed_array_last_index_fns: map[string]ArrayMethodInfo{} @@ -255,6 +260,8 @@ pub fn (t &Transformer) new_worker_clone(worker_idx int) &Transformer { synth_pos_counter: -(worker_idx * 100_000) needed_str_fns: map[string]string{} needed_clone_fns: map[string]string{} + needed_sumtype_clone_fns: map[string]types.SumType{} + needed_option_clone_fns: map[string]types.OptionType{} needed_array_contains_fns: map[string]ArrayMethodInfo{} needed_array_index_fns: map[string]ArrayMethodInfo{} needed_array_last_index_fns: map[string]ArrayMethodInfo{} @@ -279,6 +286,12 @@ pub fn (mut t Transformer) merge_worker(w &Transformer) { for k, v in w.needed_clone_fns { t.needed_clone_fns[k] = v } + for k, v in w.needed_sumtype_clone_fns { + t.needed_sumtype_clone_fns[k] = v + } + for k, v in w.needed_option_clone_fns { + t.needed_option_clone_fns[k] = v + } for k, v in w.needed_array_contains_fns { t.needed_array_contains_fns[k] = v } @@ -1008,7 +1021,8 @@ pub fn (mut t Transformer) post_pass(mut result []ast.File) { if t.needed_str_fns.len > 0 { generated_fns << t.generate_str_functions() } - if t.needed_clone_fns.len > 0 { + if t.needed_clone_fns.len > 0 || t.needed_sumtype_clone_fns.len > 0 + || t.needed_option_clone_fns.len > 0 { generated_fns << t.generate_clone_functions() } if t.needed_sort_fns.len > 0 { @@ -12138,6 +12152,16 @@ fn (mut t Transformer) clone_value_expr(expr ast.Expr, typ types.Type) ast.Expr args: [expr] }) } + types.OptionType { + if clone_fn_name := t.auto_option_clone_fn_name_for_type(resolved) { + return ast.Expr(ast.CallExpr{ + lhs: ast.Ident{ + name: clone_fn_name + } + args: [expr] + }) + } + } types.Struct { if clone_fn_name := t.auto_clone_fn_name_for_type(resolved) { return ast.Expr(ast.CallExpr{ @@ -12148,6 +12172,16 @@ fn (mut t Transformer) clone_value_expr(expr ast.Expr, typ types.Type) ast.Expr }) } } + types.SumType { + if clone_fn_name := t.auto_sumtype_clone_fn_name_for_type(resolved) { + return ast.Expr(ast.CallExpr{ + lhs: ast.Ident{ + name: clone_fn_name + } + args: [expr] + }) + } + } else {} } @@ -12188,8 +12222,9 @@ fn (mut t Transformer) generate_struct_clone_fn(fn_name string, struct_name stri value: t.clone_value_expr(field_selector, field.typ) } } - t.register_generated_fn_scope(fn_name, clone_generated_fn_scope_module(struct_name), [ - param_s, + module_name := clone_generated_fn_scope_module(struct_name) + t.register_generated_fn_scope_with_types(fn_name, module_name, [param_s], [ + types.Type(struct_type), ]) return ast.Stmt(ast.FnDecl{ name: fn_name @@ -12214,8 +12249,127 @@ fn (mut t Transformer) generate_struct_clone_fn(fn_name string, struct_name stri }) } +fn (mut t Transformer) generate_sumtype_clone_fn(fn_name string, sum_type types.SumType) ast.Stmt { + param_s := ast.Parameter{ + name: 's' + typ: t.type_to_ast_type_expr(types.Type(sum_type)) + } + module_name := t.clone_generated_fn_scope_module_for_type(types.Type(sum_type)) + t.register_generated_fn_scope_with_types(fn_name, module_name, [param_s], [ + types.Type(sum_type), + ]) + mut branches := []ast.MatchBranch{cap: sum_type.variants.len} + for variant in sum_type.variants { + clone_expr := t.clone_value_expr(ast.Expr(ast.Ident{ + name: 's' + }), variant) + wrapped_expr := ast.Expr(ast.CallExpr{ + lhs: t.type_to_ast_type_expr(types.Type(sum_type)) + args: [clone_expr] + }) + branches << ast.MatchBranch{ + cond: [t.type_to_ast_type_expr(variant)] + stmts: [ + ast.Stmt(ast.ExprStmt{ + expr: wrapped_expr + }), + ] + } + } + return ast.Stmt(ast.FnDecl{ + name: fn_name + typ: ast.FnType{ + params: [param_s] + return_type: t.type_to_ast_type_expr(types.Type(sum_type)) + } + stmts: [ + ast.Stmt(ast.ReturnStmt{ + exprs: [ + ast.Expr(ast.MatchExpr{ + expr: ast.Expr(ast.Ident{ + name: 's' + }) + branches: branches + }), + ] + }), + ] + }) +} + +fn (mut t Transformer) generate_option_clone_fn(fn_name string, opt_type types.OptionType) ast.Stmt { + param_s := ast.Parameter{ + name: 's' + typ: t.type_to_ast_type_expr(types.Type(opt_type)) + } + module_name := t.clone_generated_fn_scope_module_for_type(opt_type.base_type) + t.register_generated_fn_scope_with_types(fn_name, module_name, [param_s], [ + types.Type(opt_type), + ]) + synth_pos := t.next_synth_pos() + value_ident := ast.Ident{ + name: 'value' + pos: synth_pos + } + if_guard := ast.IfExpr{ + cond: ast.Expr(ast.IfGuardExpr{ + stmt: ast.AssignStmt{ + op: .decl_assign + lhs: [ast.Expr(value_ident)] + rhs: [ + ast.Expr(ast.Ident{ + name: 's' + pos: synth_pos + }), + ] + pos: synth_pos + } + pos: synth_pos + }) + stmts: [ + ast.Stmt(ast.ReturnStmt{ + exprs: [ + t.clone_value_expr(ast.Expr(value_ident), opt_type.base_type), + ] + }), + ] + pos: synth_pos + } + return ast.Stmt(ast.FnDecl{ + name: fn_name + typ: ast.FnType{ + params: [param_s] + return_type: t.type_to_ast_type_expr(types.Type(opt_type)) + } + stmts: [ + ast.Stmt(ast.ExprStmt{ + expr: ast.Expr(if_guard) + }), + ast.Stmt(ast.ReturnStmt{ + exprs: [ + ast.Expr(ast.Ident{ + name: 'none' + }), + ] + }), + ] + }) +} + +fn (mut t Transformer) transform_generated_fn(stmt ast.Stmt, module_name string) ast.Stmt { + prev_module := t.cur_module + prev_scope := t.scope + t.cur_module = module_name + t.scope = t.get_module_scope(module_name) or { unsafe { nil } } + transformed := t.transform_stmt(stmt) + t.cur_module = prev_module + t.scope = prev_scope + return transformed +} + fn (mut t Transformer) generate_clone_functions() []ast.Stmt { - mut result := []ast.Stmt{cap: t.needed_clone_fns.len} + mut result := []ast.Stmt{cap: t.needed_clone_fns.len + t.needed_sumtype_clone_fns.len + + t.needed_option_clone_fns.len} mut generated := map[string]bool{} for { mut found_new := false @@ -12226,10 +12380,11 @@ fn (mut t Transformer) generate_clone_functions() []ast.Stmt { generated[fn_name] = true found_new = true if typ := t.lookup_struct_type_any_module(struct_name) { - result << t.generate_struct_clone_fn(fn_name, struct_name, typ) + result << t.transform_generated_fn(t.generate_struct_clone_fn(fn_name, struct_name, typ), + clone_generated_fn_scope_module(struct_name)) continue } - result << ast.Stmt(ast.FnDecl{ + result << t.transform_generated_fn(ast.Stmt(ast.FnDecl{ name: fn_name typ: ast.FnType{ params: [ @@ -12253,7 +12408,25 @@ fn (mut t Transformer) generate_clone_functions() []ast.Stmt { ] }), ] - }) + }), clone_generated_fn_scope_module(struct_name)) + } + for fn_name, sum_type in t.needed_sumtype_clone_fns { + if fn_name in generated { + continue + } + generated[fn_name] = true + found_new = true + result << t.transform_generated_fn(t.generate_sumtype_clone_fn(fn_name, sum_type), + t.clone_generated_fn_scope_module_for_type(types.Type(sum_type))) + } + for fn_name, opt_type in t.needed_option_clone_fns { + if fn_name in generated { + continue + } + generated[fn_name] = true + found_new = true + result << t.transform_generated_fn(t.generate_option_clone_fn(fn_name, opt_type), + t.clone_generated_fn_scope_module_for_type(opt_type.base_type)) } if !found_new { break diff --git a/vlib/v2/transformer/transformer_test.v b/vlib/v2/transformer/transformer_test.v index 953069e7f..fd56b5e4c 100644 --- a/vlib/v2/transformer/transformer_test.v +++ b/vlib/v2/transformer/transformer_test.v @@ -18,6 +18,8 @@ fn create_test_transformer() &Transformer { pref: &vpref.Preferences{} env: unsafe { env } needed_clone_fns: map[string]string{} + needed_sumtype_clone_fns: map[string]types.SumType{} + needed_option_clone_fns: map[string]types.OptionType{} needed_array_contains_fns: map[string]ArrayMethodInfo{} needed_array_index_fns: map[string]ArrayMethodInfo{} needed_array_last_index_fns: map[string]ArrayMethodInfo{} @@ -44,6 +46,8 @@ fn create_transformer_with_vars(vars map[string]types.Type) &Transformer { env: unsafe { env } scope: scope needed_clone_fns: map[string]string{} + needed_sumtype_clone_fns: map[string]types.SumType{} + needed_option_clone_fns: map[string]types.OptionType{} needed_array_contains_fns: map[string]ArrayMethodInfo{} needed_array_index_fns: map[string]ArrayMethodInfo{} needed_array_last_index_fns: map[string]ArrayMethodInfo{} @@ -432,6 +436,70 @@ fn use_clone(value Outer) Outer { assert saw_outer_clone } +fn test_transform_autogenerates_clone_helpers_for_sumtype_and_option_fields() { + files := transform_code_for_test(' +interface IClone {} + +struct Err implements IClone { + msg string +} + +struct Leaf implements IClone { + name string +} + +struct Empty {} + +type Choice = Empty | Leaf + +struct Outer implements IClone { + choice Choice + err ?Err +} + +fn use_clone(value Outer) Outer { + return value.clone() +} +') + assert files.len == 1 + file := files[0] + mut saw_choice_clone := false + mut saw_option_clone := false + mut saw_outer_clone := false + for stmt in file.stmts { + if stmt is ast.FnDecl && stmt.name == 'Choice__clone' { + saw_choice_clone = true + } + if stmt is ast.FnDecl && stmt.name == 'Option_Err__clone' { + saw_option_clone = true + } + if stmt is ast.FnDecl && stmt.name == 'Outer__clone' { + saw_outer_clone = true + assert stmt.stmts.len == 1 + ret := stmt.stmts[0] as ast.ReturnStmt + assert ret.exprs.len == 1 + assert ret.exprs[0] is ast.InitExpr + init := ret.exprs[0] as ast.InitExpr + assert init.fields.len == 2 + assert init.fields[0].name == 'choice' + assert init.fields[0].value is ast.CallExpr + choice_call := init.fields[0].value as ast.CallExpr + assert choice_call.lhs is ast.Ident + assert (choice_call.lhs as ast.Ident).name == 'Choice__clone' + assert init.fields[1].name == 'err' + assert init.fields[1].value is ast.CastExpr + err_cast := init.fields[1].value as ast.CastExpr + assert err_cast.expr is ast.CallExpr + err_call := err_cast.expr as ast.CallExpr + assert err_call.lhs is ast.Ident + assert (err_call.lhs as ast.Ident).name == 'Option_Err__clone' + } + } + assert saw_choice_clone + assert saw_option_clone + assert saw_outer_clone +} + fn test_array_comparison_eq() { // Set up variable types so get_array_type_str can detect them mut t := create_transformer_with_vars({ @@ -4118,3 +4186,128 @@ fn test_else_if_result_guard_keeps_lowered_guard_in_else_branch() { } assert t.type_to_c_name(value_type) == 'ast__ComptTimeConstValue' } + +fn test_transformer_preserves_pointer_lifetime_in_v_syntax_but_not_c_names() { + mut t := create_test_transformer() + ptr_type := types.Type(types.Pointer{ + base_type: types.Type(types.NamedType('Foo')) + lifetime: 'a' + }) + + ast_expr := t.type_to_ast_type_expr(ptr_type) + assert ast_expr is ast.Type + assert (ast_expr as ast.Type) is ast.PointerType + ptr_ast := (ast_expr as ast.Type) as ast.PointerType + assert ptr_ast.lifetime == 'a' + assert ptr_ast.base_type is ast.Ident + assert (ptr_ast.base_type as ast.Ident).name == 'Foo' + + assert t.types_type_to_v(ptr_type) == '&^a Foo' + assert t.type_to_c_name(ptr_type) == 'Fooptr' +} + +fn test_transformer_uses_pointer_type_receiver_name_for_scope_key() { + t := create_test_transformer() + receiver := ast.Expr(ast.Type(ast.PointerType{ + base_type: ast.Expr(ast.Ident{ + name: 'Ignore' + }) + lifetime: 'a' + })) + assert t.get_receiver_type_name(receiver) == 'Ignore' +} + +fn test_transformer_uses_pointer_type_for_generic_specialization_token() { + t := create_test_transformer() + foo_ptr := ast.Expr(ast.Type(ast.PointerType{ + base_type: ast.Expr(ast.Ident{ + name: 'Foo' + }) + })) + bar_ptr := ast.Expr(ast.Type(ast.PointerType{ + base_type: ast.Expr(ast.Ident{ + name: 'Bar' + }) + })) + assert t.generic_specialization_token(foo_ptr) == 'Fooptr' + assert t.generic_specialization_token(bar_ptr) == 'Barptr' + assert t.generic_specialization_suffix([foo_ptr]) == '_T_Fooptr' + assert t.generic_specialization_suffix([bar_ptr]) == '_T_Barptr' +} + +fn test_transformer_preserves_lifetime_method_signature_and_nested_generic_return_type() { + files := transform_code_for_test(' +struct Ignore {} + +struct DirEntry {} + +struct Match[T] { + value T +} + +struct IgnoreMatch[^a] { + ig &^a Ignore +} + +fn (ig &^a Ignore) matched_dir_entry[^a](dent &DirEntry) Match[IgnoreMatch[^a]] { + return Match[IgnoreMatch[^a]]{ + value: IgnoreMatch[^a]{ + ig: ig + } + } +} + +fn main() { + ig := Ignore{} + dent := DirEntry{} + ig.matched_dir_entry(&dent) +} +') + assert files.len == 1 + file := files[0] + mut saw_method := false + mut saw_call := false + for stmt in file.stmts { + if stmt is ast.FnDecl && stmt.name == 'matched_dir_entry' { + saw_method = true + assert stmt.is_method + assert stmt.receiver.typ is ast.Type + receiver_type := stmt.receiver.typ as ast.Type + assert receiver_type is ast.PointerType + receiver_ptr := receiver_type as ast.PointerType + assert receiver_ptr.lifetime == 'a' + assert stmt.typ.generic_params.len == 1 + assert stmt.typ.generic_params[0] is ast.LifetimeExpr + assert (stmt.typ.generic_params[0] as ast.LifetimeExpr).name == 'a' + assert stmt.typ.return_type is ast.Type + return_type := stmt.typ.return_type as ast.Type + assert return_type is ast.GenericType + outer_generic := return_type as ast.GenericType + assert outer_generic.name is ast.Ident + assert (outer_generic.name as ast.Ident).name == 'Match' + assert outer_generic.params.len == 1 + assert outer_generic.params[0] is ast.Type + inner_type := outer_generic.params[0] as ast.Type + assert inner_type is ast.GenericType + inner_generic := inner_type as ast.GenericType + assert inner_generic.name is ast.Ident + assert (inner_generic.name as ast.Ident).name == 'IgnoreMatch' + assert inner_generic.params.len == 1 + assert inner_generic.params[0] is ast.LifetimeExpr + assert (inner_generic.params[0] as ast.LifetimeExpr).name == 'a' + } + if stmt is ast.FnDecl && stmt.name == 'main' { + assert stmt.stmts.len == 3 + assert stmt.stmts[2] is ast.ExprStmt + expr_stmt := stmt.stmts[2] as ast.ExprStmt + assert expr_stmt.expr is ast.CallExpr + call := expr_stmt.expr as ast.CallExpr + assert call.lhs is ast.Ident + assert (call.lhs as ast.Ident).name == 'Ignore__matched_dir_entry' + assert call.args.len == 2 + saw_call = true + } + } + assert saw_method + assert saw_call +} diff --git a/vlib/v2/transformer/types.v b/vlib/v2/transformer/types.v index af97cb348..d9a7daf95 100644 --- a/vlib/v2/transformer/types.v +++ b/vlib/v2/transformer/types.v @@ -130,19 +130,33 @@ fn (t &Transformer) lookup_fn_cached(module_name string, fn_name string) ?types. // register_generated_fn_scope creates a function scope for a transformer-generated function // (e.g. Array_int_contains, Array_string_str) and registers parameter types so cleanc // can resolve them via scope lookup instead of falling back to string-based inference. +fn generated_fn_scope_key(fn_name string, module_name string) string { + if module_name == '' { + return fn_name + } + return '${module_name}__${fn_name}' +} + fn (mut t Transformer) register_generated_fn_scope(fn_name string, module_name string, params []ast.Parameter) { + t.register_generated_fn_scope_with_types(fn_name, module_name, params, []types.Type{}) +} + +fn (mut t Transformer) register_generated_fn_scope_with_types(fn_name string, module_name string, params []ast.Parameter, param_types []types.Type) { parent := t.get_module_scope(module_name) or { return } mut fn_scope := types.new_scope(parent) - for param in params { - type_name := t.expr_to_type_name(param.typ) - if type_name == '' { + for i, param in params { + if i < param_types.len { + fn_scope.insert(param.name, types.Object(param_types[i])) continue } - if param_type := t.c_name_to_type(type_name) { - fn_scope.insert(param.name, types.Object(param_type)) + type_name := t.expr_to_type_name(param.typ) + if type_name != '' { + if param_type := t.c_name_to_type(type_name) { + fn_scope.insert(param.name, types.Object(param_type)) + } } } - key := if module_name == '' { fn_name } else { '${module_name}__${fn_name}' } + key := generated_fn_scope_key(fn_name, module_name) t.cached_fn_scopes[key] = fn_scope } @@ -503,6 +517,49 @@ fn (mut t Transformer) register_needed_clone_struct(st types.Struct) string { return fn_name } +fn (t &Transformer) clone_generated_fn_scope_module_for_type(typ types.Type) string { + resolved := types.resolve_alias(typ) + match resolved { + types.Struct { + return clone_generated_fn_scope_module(t.type_to_c_name(resolved)) + } + types.SumType { + return clone_generated_fn_scope_module(t.type_to_c_name(resolved)) + } + types.OptionType { + return t.clone_generated_fn_scope_module_for_type(resolved.base_type) + } + types.ResultType { + return t.clone_generated_fn_scope_module_for_type(resolved.base_type) + } + types.String, types.Array, types.ArrayFixed, types.Map, types.Pointer, types.Primitive, + types.Char, types.Rune, types.Nil, types.None, types.Void, types.ISize, types.USize { + return 'builtin' + } + else { + return 'main' + } + } +} + +fn (mut t Transformer) register_needed_sumtype_clone(sum_t types.SumType) string { + fn_name := '${t.type_to_c_name(types.Type(sum_t))}__clone' + t.needed_sumtype_clone_fns[fn_name] = sum_t + return fn_name +} + +fn (mut t Transformer) register_needed_option_clone(opt types.OptionType) string { + base_token := t.generic_specialization_token_from_type(opt.base_type) + module_name := t.clone_generated_fn_scope_module_for_type(opt.base_type) + fn_name := if module_name != '' && module_name != 'main' { + '${module_name}__Option_${base_token}__clone' + } else { + 'Option_${base_token}__clone' + } + t.needed_option_clone_fns[fn_name] = opt + return fn_name +} + fn (t &Transformer) clone_fn_name_for_type(typ types.Type) ?string { mut base := typ for base is types.Pointer { @@ -543,6 +600,30 @@ fn (mut t Transformer) auto_clone_fn_name_for_type(typ types.Type) ?string { return fn_name } +fn (mut t Transformer) auto_sumtype_clone_fn_name_for_type(typ types.Type) ?string { + mut base := typ + for base is types.Pointer { + base = (base as types.Pointer).base_type + } + base = types.resolve_alias(base) + if base is types.SumType { + return t.register_needed_sumtype_clone(base as types.SumType) + } + return none +} + +fn (mut t Transformer) auto_option_clone_fn_name_for_type(typ types.Type) ?string { + mut base := typ + for base is types.Pointer { + base = (base as types.Pointer).base_type + } + base = types.resolve_alias(base) + if base is types.OptionType { + return t.register_needed_option_clone(base as types.OptionType) + } + return none +} + // is_flag_enum checks if a type name is a flag enum fn (t &Transformer) is_flag_enum(type_name string) bool { typ := t.lookup_type(type_name) or { return false } @@ -836,6 +917,9 @@ fn (t &Transformer) v_type_name_to_c_name(v_name string) string { // qualify_type_name adds module prefix to type names that need it // e.g., "File" in ast module becomes "ast__File" fn (t &Transformer) qualify_type_name(type_name string) string { + if type_name.starts_with('^') { + return 'lt__' + type_name[1..] + } if type_name.contains('.') { return type_name.replace('.', '__') } @@ -1343,6 +1427,9 @@ fn (t &Transformer) type_expr_to_c_name(typ ast.Expr) string { ast.Ident { return typ.name.replace('.', '__') } + ast.LifetimeExpr { + return 'lt__' + typ.name + } ast.SelectorExpr { if typ.lhs is ast.Ident { return '${(typ.lhs as ast.Ident).name}__${typ.rhs.name}' @@ -1362,6 +1449,12 @@ fn (t &Transformer) type_expr_to_c_name(typ ast.Expr) string { return t.type_expr_to_c_name(typ.expr) } ast.Type { + if typ is ast.PointerType { + base := t.type_expr_to_c_name(typ.base_type) + if base != '' { + return '${base}*' + } + } // Handle composite types like []ast.Attribute, [3]int, map[string]int return t.type_variant_name(typ) } @@ -1552,10 +1645,10 @@ fn (t &Transformer) type_to_ast_type_expr(typ types.Type) ast.Expr { })) } types.Pointer { - return ast.Expr(ast.PrefixExpr{ - op: .amp - expr: t.type_to_ast_type_expr(typ.base_type) - }) + return ast.Expr(ast.Type(ast.PointerType{ + base_type: t.type_to_ast_type_expr(typ.base_type) + lifetime: typ.lifetime + })) } types.OptionType { return ast.Expr(ast.Type(ast.OptionType{ @@ -1799,6 +1892,10 @@ fn (t &Transformer) type_variant_name(typ ast.Type) string { val_name := t.type_expr_name_full(typ.value_type) return 'Map_${key_name}_${val_name}' } + if typ is ast.PointerType { + base_name := t.type_expr_name(typ.base_type) + return '${base_name}ptr' + } if typ is ast.GenericType { // Foo[Bar] -> Foo (used for sumtype matching) return t.type_expr_name(typ.name) @@ -1811,6 +1908,7 @@ fn (t &Transformer) type_variant_name(typ ast.Type) string { ast.NilType { 'NilType' } ast.NoneType { 'NoneType' } ast.OptionType { 'OptionType' } + ast.PointerType { 'PointerType' } ast.ResultType { 'ResultType' } ast.ThreadType { 'ThreadType' } ast.TupleType { 'TupleType' } @@ -1838,6 +1936,10 @@ fn (t &Transformer) type_variant_name_full(typ ast.Type) string { val_name := t.type_expr_name_full(typ.value_type) return 'Map_${key_name}_${val_name}' } + if typ is ast.PointerType { + base_name := t.type_expr_name_full(typ.base_type) + return '${base_name}ptr' + } return t.type_expr_name_full(typ) } @@ -1846,6 +1948,9 @@ fn (t &Transformer) type_expr_name(expr ast.Expr) string { if expr is ast.Ident { return expr.name } + if expr is ast.LifetimeExpr { + return '^' + expr.name + } if expr is ast.SelectorExpr { // ast.Attribute -> 'Attribute' (use short name for matching) return expr.rhs.name @@ -1861,6 +1966,9 @@ fn (t &Transformer) type_expr_name_full(expr ast.Expr) string { if expr is ast.Ident { return expr.name } + if expr is ast.LifetimeExpr { + return 'lt__' + expr.name + } if expr is ast.SelectorExpr { // ast.Attribute -> 'ast__Attribute' (full name with module prefix for C) if expr.lhs is ast.Ident { @@ -2892,6 +3000,9 @@ fn (t &Transformer) generic_specialization_token(expr ast.Expr) string { return sanitize_generic_token_part(t.type_expr_to_c_name(expr)) } ast.Type { + if expr is ast.PointerType { + return t.generic_specialization_token(expr.base_type) + 'ptr' + } return sanitize_generic_token_part(expr.name()) } else { @@ -2982,6 +3093,9 @@ fn (t &Transformer) get_receiver_type_name(typ ast.Expr) string { } if typ is ast.Type { // Handle wrapped type variants (GenericType, etc.) + if typ is ast.PointerType { + return t.get_receiver_type_name(typ.base_type) + } if typ is ast.GenericType { // Type[T] -> Type return t.get_receiver_type_name(typ.name) @@ -4061,6 +4175,9 @@ fn (t &Transformer) types_type_to_v(typ types.Type) string { } types.Pointer { base := t.types_type_to_v(typ.base_type) + if typ.lifetime != '' { + return '&^${typ.lifetime} ' + base + } return '&' + base } types.Array { @@ -4126,6 +4243,12 @@ fn (t &Transformer) types_type_to_v(typ types.Type) string { types.None { return 'void' } + types.NamedType { + if string(typ).starts_with('^') { + return string(typ) + } + return c_name_to_v_name(string(typ)) + } else { return 'int' } diff --git a/vlib/v2/types/checker.v b/vlib/v2/types/checker.v index 65ba1ed8b..80fcacce2 100644 --- a/vlib/v2/types/checker.v +++ b/vlib/v2/types/checker.v @@ -424,6 +424,9 @@ pub fn Checker.new(prefs &pref.Preferences, file_set &token.FileSet, env &Enviro // Builtin types and main module types are not prefixed. fn (c &Checker) qualify_type_name(name string) string { // Don't qualify builtin or main module types + if name.starts_with('^') { + return name + } if c.cur_file_module == 'builtin' || c.cur_file_module == '' || c.cur_file_module == 'main' { return name } @@ -435,9 +438,18 @@ fn (c &Checker) type_ref_name(expr ast.Expr) string { ast.Ident { return c.qualify_type_name(expr.name) } + ast.LifetimeExpr { + return '^' + expr.name + } ast.SelectorExpr { return expr.name().replace('.', '__') } + ast.Type { + if expr is ast.PointerType { + return c.type_ref_name(expr.base_type) + } + return 'Type' + } else { return expr.name().replace('.', '__') } @@ -917,6 +929,8 @@ fn (mut c Checker) decl(decl ast.Stmt) { for gp in decl.generic_params { if gp is ast.Ident { generic_params << gp.name + } else if gp is ast.LifetimeExpr { + generic_params << '^' + gp.name } } obj := Struct{ @@ -1971,6 +1985,9 @@ fn (mut c Checker) expr_impl(expr ast.Expr) Type { } return typ } + ast.LifetimeExpr { + return Type(NamedType('^' + expr.name)) + } ast.IfExpr { // if guard if expr.cond is ast.IfGuardExpr { @@ -2268,6 +2285,13 @@ fn (mut c Checker) type_node_expr(type_expr ast.Type) Type { base_type: c.type_expr(opt_type.base_type) } } + if type_expr is ast.PointerType { + ptr_type := type_expr as ast.PointerType + return Pointer{ + base_type: c.type_expr(ptr_type.base_type) + lifetime: ptr_type.lifetime + } + } if type_expr is ast.ResultType { res_type := type_expr as ast.ResultType return ResultType{ @@ -2717,7 +2741,13 @@ fn (mut c Checker) process_pending_struct_decls() { // Insert generic type parameters into scope so field types can reference them mut generic_params := []string{} for gp in pending.decl.generic_params { - gp_name := if gp is ast.Ident { gp.name } else { '' } + gp_name := if gp is ast.Ident { + gp.name + } else if gp is ast.LifetimeExpr { + '^' + gp.name + } else { + '' + } if gp_name != '' { generic_params << gp_name c.scope.insert(gp_name, Type(NamedType(gp_name))) @@ -2927,6 +2957,8 @@ fn collect_fn_generic_params(decl ast.FnDecl) []string { for gp in decl.typ.generic_params { if gp is ast.Ident && gp.name !in params { params << gp.name + } else if gp is ast.LifetimeExpr && '^' + gp.name !in params { + params << '^' + gp.name } } if !decl.is_method { @@ -2942,6 +2974,8 @@ fn collect_receiver_generic_params(mut params []string, expr ast.Expr) { for arg in expr.args { if arg is ast.Ident && arg.name !in params { params << arg.name + } else if arg is ast.LifetimeExpr && '^' + arg.name !in params { + params << '^' + arg.name } } collect_receiver_generic_params(mut params, expr.lhs) @@ -2949,6 +2983,11 @@ fn collect_receiver_generic_params(mut params []string, expr ast.Expr) { ast.PrefixExpr { collect_receiver_generic_params(mut params, expr.expr) } + ast.Type { + if expr is ast.PointerType { + collect_receiver_generic_params(mut params, expr.base_type) + } + } else {} } } @@ -3832,6 +3871,7 @@ fn fix_self_ref_type(t Type, self_name string, self_fields []Field, self_embedde if fixed := fix_self_ref_type(t.base_type, self_name, self_fields, self_embedded) { return Type(Pointer{ base_type: fixed + lifetime: t.lifetime }) } } @@ -3939,6 +3979,7 @@ fn (mut c Checker) substitute_generic_type_with_stack(t Type, generic_type_map m return Type(Pointer{ base_type: c.substitute_generic_type_with_stack(t.base_type, generic_type_map, stack) + lifetime: t.lifetime }) } ResultType { @@ -4692,6 +4733,8 @@ fn (mut c Checker) fn_type_with_insert_params(fn_type ast.FnType, attributes FnT if generic_param is ast.Ident { // generic_params << NamedType(generic_param.name) generic_params << generic_param.name + } else if generic_param is ast.LifetimeExpr { + generic_params << '^' + generic_param.name } else { // TODO: correct position c.error_with_pos('expecting identifier', token.Pos{}) diff --git a/vlib/v2/types/checker_test.v b/vlib/v2/types/checker_test.v index 12ffd98e2..9bdcdedd2 100644 --- a/vlib/v2/types/checker_test.v +++ b/vlib/v2/types/checker_test.v @@ -1095,3 +1095,74 @@ fn test_scope_insert_replaces_module_placeholder_with_function() { obj := scope.lookup_parent('optimize', 0) or { panic('missing optimize') } assert obj is Fn } + +fn test_explicit_lifetime_syntax_in_fn_types_and_generic_args() { + code := ' +struct Ignore {} + +struct IgnoreMatch[^a] {} + +fn matched_dir_entry[^a](self &^a Ignore) IgnoreMatch[^a] { + return IgnoreMatch[^a]{} +} +' + env := check_code(code) + fn_type := env.lookup_fn('main', 'matched_dir_entry') or { panic('missing matched_dir_entry') } + assert '^a' in fn_type.generic_params + assert fn_type.params.len == 1 + assert fn_type.params[0].typ is Pointer + ptr := fn_type.params[0].typ as Pointer + assert ptr.lifetime == 'a' + assert ptr.base_type.name() == 'Ignore' + assert has_type_matching(env, fn (t Type) bool { + return t is Struct && t.name == 'IgnoreMatch' && t.generic_params == ['^a'] + }) +} + +fn test_explicit_lifetime_method_receiver_and_nested_generic_return_type() { + code := ' +struct Ignore {} + +struct DirEntry {} + +struct Match[T] { + value T +} + +struct IgnoreMatch[^a] { + ig &^a Ignore +} + +fn (ig &^a Ignore) matched_dir_entry[^a](dent &DirEntry) Match[IgnoreMatch[^a]] { + return Match[IgnoreMatch[^a]]{ + value: IgnoreMatch[^a]{ + ig: ig + } + } +} + +fn main() { + ig := Ignore{} + dent := DirEntry{} + ig.matched_dir_entry(&dent) +} +' + env := check_code(code) + method := env.lookup_method('Ignore', 'matched_dir_entry') or { + panic('missing Ignore.matched_dir_entry') + } + assert '^a' in method.generic_params + assert method.params.len == 1 + assert method.params[0].typ is Pointer + dent_param := method.params[0].typ as Pointer + assert dent_param.lifetime == '' + assert dent_param.base_type.name() == 'DirEntry' + return_type := method.get_return_type() or { panic('missing matched_dir_entry return type') } + assert return_type.name() == 'Match' + assert has_type_matching(env, fn (t Type) bool { + return t is Struct && t.name == 'IgnoreMatch' && t.generic_params == ['^a'] + }) + assert has_type_matching(env, fn (t Type) bool { + return t is Struct && t.name == 'Match' + }) +} diff --git a/vlib/v2/types/types.v b/vlib/v2/types/types.v index 145b6eb7c..d992dea43 100644 --- a/vlib/v2/types/types.v +++ b/vlib/v2/types/types.v @@ -208,6 +208,7 @@ pub: pub struct Pointer { pub: base_type Type + lifetime string } // struct String { @@ -737,6 +738,9 @@ fn (t OptionType) name() string { } fn (t Pointer) name() string { + if t.lifetime != '' { + return '&^${t.lifetime} ' + t.base_type.name() + } return '&' + t.base_type.name() } -- 2.39.5