cgen: fix auto .str() generation for []&T, and `fn (t &T) str() string{}`

pull/4778/head^2
Delyan Angelov 2020-05-08 12:48:01 +03:00
parent 8866773f97
commit fec7f0f0b9
4 changed files with 205 additions and 18 deletions

View File

@ -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

View File

@ -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(", "));')

View File

@ -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 {

View File

@ -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]}')
}
}