diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 225548986d..2a4cfe0c5c 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -833,6 +833,19 @@ pub fn (mut c Checker) call_fn(call_expr mut ast.CallExpr) table.Type { if fn_name == 'println' || fn_name == 'print' { c.expected_type = table.string_type call_expr.args[0].typ = c.expr(call_expr.args[0].expr) + /* + // TODO: optimize `struct T{} fn (t &T) str() string {return 'abc'} mut a := []&T{} a << &T{} println(a[0])` + // It currently generates: + // `println(T_str_no_ptr(*(*(T**)array_get(a, 0))));` + // ... which works, but could be just: + // `println(T_str(*(T**)array_get(a, 0)));` + prexpr := call_expr.args[0].expr + prtyp := call_expr.args[0].typ + prtyp_sym := c.table.get_type_symbol(prtyp) + prtyp_is_ptr := prtyp.is_ptr() + prhas_str, prexpects_ptr, prnr_args := prtyp_sym.str_method_info() + eprintln('>>> println hack typ: ${prtyp} | sym.name: ${prtyp_sym.name} | is_ptr: $prtyp_is_ptr | has_str: $prhas_str | expects_ptr: $prexpects_ptr | nr_args: $prnr_args | expr: ${prexpr.str()} ') + */ return f.return_type } // TODO: typ optimize.. this node can get processed more than once diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index c02124e4ff..f1ce9a899a 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -2578,6 +2578,7 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { } } else if specs[i] == `s` { sym := g.table.get_type_symbol(node.expr_types[i]) + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() if node.expr_types[i].flag_is(.variadic) { str_fn_name := g.gen_str_for_type(node.expr_types[i]) g.write('${str_fn_name}(') @@ -2599,17 +2600,24 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { g.enum_expr(expr) g.write('")') } - } else if sym.has_method('str') || sym.kind in [.array, .array_fixed, .map, .struct_] { + } else if sym_has_str_method || sym.kind in [.array, .array_fixed, .map, .struct_] { is_p := node.expr_types[i].is_ptr() val_type := if is_p { node.expr_types[i].deref() } else { node.expr_types[i] } str_fn_name := g.gen_str_for_type(val_type) - if is_p { - g.write('string_add(_SLIT("&"), ${str_fn_name}(*(') - } else { - g.write('${str_fn_name}(') + if is_p && str_method_expects_ptr { + g.write('string_add(_SLIT("&"), ${str_fn_name}( (') + } + if is_p && !str_method_expects_ptr { + g.write('string_add(_SLIT("&"), ${str_fn_name}( *(') + } + if !is_p && !str_method_expects_ptr { + g.write('${str_fn_name}( ') + } + if !is_p && str_method_expects_ptr { + g.write('${str_fn_name}( &') } g.expr(expr) - if sym.kind == .struct_ && !sym.has_method('str') { + if sym.kind == .struct_ && !sym_has_str_method { if is_p { g.write('),0))') } else { @@ -3179,7 +3187,7 @@ fn (mut g Gen) as_cast(node ast.AsCast) { expr_type_sym := g.table.get_type_symbol(node.expr_type) if expr_type_sym.kind == .sum_type { /* - g.write('/* as */ *($styp*)') + g.write('*($styp*)') g.expr(node.expr) g.write('.obj') */ @@ -3220,9 +3228,33 @@ fn (mut g Gen) gen_str_for_type(typ table.Type) string { fn (mut g Gen) gen_str_for_type_with_styp(typ table.Type, styp string) string { sym := g.table.get_type_symbol(typ) str_fn_name := styp_to_str_fn_name(styp) - already_generated_key := '${styp}:${str_fn_name}' + sym_has_str_method, str_method_expects_ptr, str_nr_args := sym.str_method_info() // generate for type - if !sym.has_method('str') && already_generated_key !in g.str_types { + if sym_has_str_method && str_method_expects_ptr && str_nr_args == 1 { + // TODO: optimize out this. + // It is needed, so that println() can be called with &T and T has `fn (t &T).str() string` + /* + eprintln('>> gsftws: typ: $typ | typ_is_ptr $typ_is_ptr | styp: $styp ' + + '| $str_fn_name | sym.name: $sym.name has_str: $sym_has_str_method ' + + '| expects_ptr: $str_method_expects_ptr') + */ + str_fn_name_no_ptr := '${str_fn_name}_no_ptr' + already_generated_key_no_ptr := '${styp}:${str_fn_name_no_ptr}' + if already_generated_key_no_ptr !in g.str_types { + g.str_types << already_generated_key_no_ptr + g.definitions.writeln('string ${str_fn_name_no_ptr}(${styp} it); // auto no_ptr version') + g.auto_str_funcs.writeln('string ${str_fn_name_no_ptr}(${styp} it){ return ${str_fn_name}(&it); }') + } + /* + typ_is_ptr := typ.is_ptr() + ret_type := if typ_is_ptr { str_fn_name } else { str_fn_name_no_ptr } + eprintln(' ret_type: $ret_type') + return ret_type + */ + return str_fn_name_no_ptr + } + already_generated_key := '${styp}:${str_fn_name}' + if !sym_has_str_method && already_generated_key !in g.str_types { g.str_types << already_generated_key match sym.info { table.Alias { g.gen_str_default(sym, styp, str_fn_name) } @@ -3362,7 +3394,16 @@ fn (mut g Gen) gen_str_for_struct(info table.Struct, styp, str_fn_name string) { fn (mut g Gen) gen_str_for_array(info table.Array, styp, str_fn_name string) { sym := g.table.get_type_symbol(info.elem_type) field_styp := g.typ(info.elem_type) - if !sym.has_method('str') { + is_elem_ptr := info.elem_type.is_ptr() + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + mut elem_str_fn_name := '' + if sym_has_str_method { + elem_str_fn_name = if is_elem_ptr { field_styp.replace('*', '') +'_str' } else { field_styp + '_str' } + } else { + elem_str_fn_name = styp_to_str_fn_name( field_styp ) + } + if !sym_has_str_method { + // eprintln('> sym.name: does not have method `str`') g.gen_str_for_type_with_styp(info.elem_type, field_styp) } g.definitions.writeln('string ${str_fn_name}($styp a); // auto') @@ -3371,12 +3412,25 @@ fn (mut g Gen) gen_str_for_array(info table.Array, styp, str_fn_name string) { g.auto_str_funcs.writeln('\tstrings__Builder_write(&sb, tos3("["));') g.auto_str_funcs.writeln('\tfor (int i = 0; i < a.len; i++) {') g.auto_str_funcs.writeln('\t\t${field_styp} it = (*(${field_styp}*)array_get(a, i));') - if sym.kind == .struct_ && !sym.has_method('str') { - g.auto_str_funcs.writeln('\t\tstring x = ${field_styp}_str(it,0);') + if sym.kind == .struct_ && !sym_has_str_method { + if is_elem_ptr { + g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}(*it,0);') + }else{ + g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}(it,0);') + } } else if sym.kind in [.f32, .f64] { g.auto_str_funcs.writeln('\t\tstring x = _STR("%g", 1, it);') } else { - g.auto_str_funcs.writeln('\t\tstring x = ${field_styp}_str(it);') + // There is a custom .str() method, so use it. + // NB: we need to take account of whether the user has defined + // `fn (x T) str() {` or `fn (x &T) str() {`, and convert accordingly + if str_method_expects_ptr && is_elem_ptr || !str_method_expects_ptr && !is_elem_ptr { + g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}(it);') + } else if str_method_expects_ptr && !is_elem_ptr { + g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}(&it);') + } else if !str_method_expects_ptr && is_elem_ptr { + g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}(*it);') + } } g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, x);') if g.pref.autofree && info.elem_type != table.bool_type { @@ -3398,6 +3452,14 @@ fn (mut g Gen) gen_str_for_array(info table.Array, styp, str_fn_name string) { fn (mut g Gen) gen_str_for_array_fixed(info table.ArrayFixed, styp, str_fn_name string) { sym := g.table.get_type_symbol(info.elem_type) field_styp := g.typ(info.elem_type) + is_elem_ptr := info.elem_type.is_ptr() + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + mut elem_str_fn_name := '' + if sym_has_str_method { + elem_str_fn_name = if is_elem_ptr { field_styp.replace('*', '') +'_str' } else { field_styp + '_str' } + } else { + elem_str_fn_name = styp_to_str_fn_name( field_styp ) + } if !sym.has_method('str') { g.gen_str_for_type_with_styp(info.elem_type, field_styp) } @@ -3406,14 +3468,20 @@ fn (mut g Gen) gen_str_for_array_fixed(info table.ArrayFixed, styp, str_fn_name g.auto_str_funcs.writeln('\tstrings__Builder sb = strings__new_builder($info.size * 10);') g.auto_str_funcs.writeln('\tstrings__Builder_write(&sb, tos3("["));') g.auto_str_funcs.writeln('\tfor (int i = 0; i < $info.size; i++) {') - if sym.kind == .struct_ && !sym.has_method('str') { - g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${field_styp}_str(a[i],0));') + if sym.kind == .struct_ && !sym_has_str_method { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${elem_str_fn_name}(a[i],0));') } else if sym.kind in [.f32, .f64] { g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, _STR("%g", 1, a[i]));') } else if sym.kind == .string { g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, _STR("\'%.*s\\000\'", 2, a[i]));') } else { - g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${field_styp}_str(a[i]));') + if str_method_expects_ptr && is_elem_ptr || !str_method_expects_ptr && !is_elem_ptr { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${elem_str_fn_name}(a[i]));') + } else if str_method_expects_ptr && !is_elem_ptr { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${elem_str_fn_name}(&a[i]));') + } else if !str_method_expects_ptr && is_elem_ptr { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${elem_str_fn_name}(*a[i]));') + } } g.auto_str_funcs.writeln('\t\tif (i < ${info.size-1}) {') g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write(&sb, tos3(", "));') @@ -3432,6 +3500,7 @@ fn (mut g Gen) gen_str_for_map(info table.Map, styp, str_fn_name string) { } val_sym := g.table.get_type_symbol(info.value_type) val_styp := g.typ(info.value_type) + elem_str_fn_name := val_styp.replace('*', '') + '_str' if !val_sym.has_method('str') { g.gen_str_for_type_with_styp(info.value_type, val_styp) } @@ -3451,11 +3520,11 @@ fn (mut g Gen) gen_str_for_map(info table.Map, styp, str_fn_name string) { if val_sym.kind == .string { g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, _STR("\'%.*s\\000\'", 2, it));') } else if val_sym.kind == .struct_ && !val_sym.has_method('str') { - g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${val_styp}_str(it,0));') + g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${elem_str_fn_name}(it,0));') } else if val_sym.kind in [.f32, .f64] { g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, _STR("%g", 1, it));') } else { - g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${val_styp}_str(it));') + g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${elem_str_fn_name}(it));') } g.auto_str_funcs.writeln('\t\tif (i != m.key_values.size-1) {') g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write(&sb, tos3(", "));') diff --git a/vlib/v/table/atypes.v b/vlib/v/table/atypes.v index eb6f750f8e..0a7b59e923 100644 --- a/vlib/v/table/atypes.v +++ b/vlib/v/table/atypes.v @@ -677,6 +677,20 @@ pub fn (t &TypeSymbol) find_method(name string) ?Fn { return none } +pub fn (t &TypeSymbol) str_method_info() (bool, bool, int) { + mut has_str_method := false + mut expects_ptr := false + mut nr_args := 0 + if sym_str_method := t.find_method('str') { + has_str_method = true + nr_args = sym_str_method.args.len + if nr_args > 0 { + expects_ptr = sym_str_method.args[0].typ.is_ptr() + } + } + return has_str_method, expects_ptr, nr_args +} + pub fn (s Struct) find_field(name string) ?Field { for field in s.fields { if field.name == name { diff --git a/vlib/v/tests/string_interpolation_of_array_of_structs_test.v b/vlib/v/tests/string_interpolation_of_array_of_structs_test.v new file mode 100644 index 0000000000..693cabd71f --- /dev/null +++ b/vlib/v/tests/string_interpolation_of_array_of_structs_test.v @@ -0,0 +1,91 @@ +module main + +struct Anything { +mut: + name string = "" + keepo int = 0 +} + +fn (a Anything) str() string { + return a.name +} + +fn test_array_of_ptrs_to_structs_can_be_printed() { + mut testing := []&Anything{} + testing << &Anything{name: "Hehe"} + testing << &Anything{name: "other"} + testing << &Anything{name: "test"} + for test in testing { + println(test) + assert true + } + println('testing: $testing') + println( testing ) + assert true +} + +// At the same time, this should also work: +// (note the str method defined on (a &T), instead on (a T)) + +struct PstrAnything { +mut: + name string = "" + keepo int = 0 +} +fn (a &PstrAnything) str() string { + return a.name +} + +fn test_array_of_ptrs_to_structs_can_be_printed_when_structs_have_str_with_ptr() { + mut testing := []&PstrAnything{} + testing << &PstrAnything{name: "abc"} + testing << &PstrAnything{name: "def"} + testing << &PstrAnything{name: "ghi"} + for test in testing { + println(test) + assert true + } + println('testing: $testing') + println( testing ) + assert true +} + +// + +fn test_stack_array_of_structs_can_be_printed_when_structs_have_ordinary_str() { + mut t := [3]Anything + t[0] = Anything{name: "012"} + t[1] = Anything{name: "345"} + t[2] = Anything{name: "678"} + for test in t { + println(test) + assert true + } + println('t: $t') + println( t ) + println( 't[0] := ${t[0]}') + assert true +} + +fn test_stack_array_of_structs_can_be_printed_when_structs_have_str_with_ptr() { + // this generates a C error + mut pt := [3]PstrAnything + pt[0] = PstrAnything{name: "P012"} + pt[1] = PstrAnything{name: "P345"} + pt[2] = PstrAnything{name: "P678"} + for test in pt { + println(test) + assert true + } + println('pt: $pt') + println( pt ) + print( 'pt[0] := ') + print( pt[0] ) + println('') + assert true + + $if debug_buggy_println ? { + //TODO: fix string interpolation for structs with `fn (t &T) str() string` too: + println( 'pt[0] := ${pt[0]}') + } +}