checker: add check for strict .str() method signature

pull/5656/head
Delyan Angelov 2020-07-04 14:14:24 +03:00
parent 4403c76406
commit 35431d457b
10 changed files with 69 additions and 25 deletions

View File

@ -24,7 +24,7 @@ pub fn (node &FnDecl) modname() string {
// These methods are used only by vfmt, vdoc, and for debugging. // These methods are used only by vfmt, vdoc, and for debugging.
pub fn (node &FnDecl) str(t &table.Table) string { pub fn (node &FnDecl) stringify(t &table.Table) string {
mut f := strings.new_builder(30) mut f := strings.new_builder(30)
if node.is_pub { if node.is_pub {
f.write('pub ') f.write('pub ')

View File

@ -2950,6 +2950,16 @@ fn (mut c Checker) fn_decl(node ast.FnDecl) {
} }
} }
} }
if node.language == .v && node.is_method && node.name == 'str' {
if node.return_type != table.string_type {
c.error('.str() methods should return `string`', node.pos)
}
if node.args.len != 1 {
c.error('.str() methods should have 0 arguments', node.pos)
}
}
c.expected_type = table.void_type c.expected_type = table.void_type
c.cur_fn = &node c.cur_fn = &node
c.stmts(node.stmts) c.stmts(node.stmts)

View File

@ -0,0 +1,7 @@
vlib/v/checker/tests/str_method_0_arguments.v:5:1: error: .str() methods should have 0 arguments
3 | }
4 |
5 | fn (z Zzz) str(x int) string {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6 | return 'z: $z.x'
7 | }

View File

@ -0,0 +1,11 @@
struct Zzz {
x int
}
fn (z Zzz) str(x int) string {
return 'z: $z.x'
}
fn main() {
println(Zzz{123})
}

View File

@ -0,0 +1,7 @@
vlib/v/checker/tests/str_method_return_string.v:5:1: error: .str() methods should return `string`
3 | }
4 |
5 | fn (z Zzz) str(x int) int {
| ~~~~~~~~~~~~~~~~~~~~~~~~~
6 | return z.x
7 | }

View File

@ -0,0 +1,11 @@
struct Zzz {
x int
}
fn (z Zzz) str(x int) int {
return z.x
}
fn main() {
println(Zzz{123})
}

View File

@ -119,7 +119,7 @@ pub fn (mut d Doc) get_signature(stmt ast.Stmt, file &ast.File) string {
return 'module $stmt.name' return 'module $stmt.name'
} }
ast.FnDecl { ast.FnDecl {
return stmt.str(d.table).replace(d.fmt.cur_mod + '.', '') return stmt.stringify(d.table).replace(d.fmt.cur_mod + '.', '')
} }
else { else {
d.fmt.out = strings.new_builder(1000) d.fmt.out = strings.new_builder(1000)

View File

@ -421,7 +421,7 @@ pub fn (mut f Fmt) stmt(node ast.Stmt) {
f.writeln('interface $it.name {') f.writeln('interface $it.name {')
for method in it.methods { for method in it.methods {
f.write('\t') f.write('\t')
f.writeln(method.str(f.table).after('fn ')) f.writeln(method.stringify(f.table).after('fn '))
} }
f.writeln('}\n') f.writeln('}\n')
} }
@ -1134,7 +1134,7 @@ pub fn (mut f Fmt) comments(some_comments []ast.Comment, remove_last_new_line bo
pub fn (mut f Fmt) fn_decl(node ast.FnDecl) { pub fn (mut f Fmt) fn_decl(node ast.FnDecl) {
// println('$it.name find_comment($it.pos.line_nr)') // println('$it.name find_comment($it.pos.line_nr)')
// f.find_comment(it.pos.line_nr) // f.find_comment(it.pos.line_nr)
s := node.str(f.table) s := node.stringify(f.table)
f.write(s.replace(f.cur_mod + '.', '')) // `Expr` instead of `ast.Expr` in mod ast f.write(s.replace(f.cur_mod + '.', '')) // `Expr` instead of `ast.Expr` in mod ast
if node.language == .v { if node.language == .v {
f.writeln(' {') f.writeln(' {')

View File

@ -3232,9 +3232,9 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype table.Type) ?bool {
g.expr(expr) g.expr(expr)
if sym.kind == .struct_ && !sym_has_str_method { if sym.kind == .struct_ && !sym_has_str_method {
if is_p { if is_p {
g.write('),0))') g.write(')))')
} else { } else {
g.write(',0)') g.write(')')
} }
} else { } else {
if is_p { if is_p {
@ -4062,8 +4062,12 @@ fn (mut g Gen) gen_str_for_struct(info table.Struct, styp, str_fn_name string) {
fnames2strfunc[field_styp] = field_fn_name fnames2strfunc[field_styp] = field_fn_name
} }
} }
g.type_definitions.writeln('string ${str_fn_name}($styp x, int indent_count); // auto') // _str() functions should have a single argument, the indenting ones take 2:
g.auto_str_funcs.writeln('string ${str_fn_name}($styp x, int indent_count) {') g.type_definitions.writeln('string ${str_fn_name}($styp x); // auto')
g.auto_str_funcs.writeln('string ${str_fn_name}($styp x) { return indent_${str_fn_name}(x,0);}')
//
g.type_definitions.writeln('string indent_${str_fn_name}($styp x, int indent_count); // auto')
g.auto_str_funcs.writeln('string indent_${str_fn_name}($styp x, int indent_count) {')
mut clean_struct_v_type_name := styp.replace('__', '.') mut clean_struct_v_type_name := styp.replace('__', '.')
if styp.ends_with('*') { if styp.ends_with('*') {
deref_typ := styp.replace('*', '') deref_typ := styp.replace('*', '')
@ -4090,7 +4094,6 @@ fn (mut g Gen) gen_str_for_struct(info table.Struct, styp, str_fn_name string) {
for i, field in info.fields { for i, field in info.fields {
sym := g.table.get_type_symbol(field.typ) sym := g.table.get_type_symbol(field.typ)
has_custom_str := sym.has_method('str') has_custom_str := sym.has_method('str')
second_str_param := if has_custom_str { '' } else { ', indent_count + 1' }
mut field_styp := g.typ(field.typ) mut field_styp := g.typ(field.typ)
if field_styp.ends_with('*') { if field_styp.ends_with('*') {
field_styp = field_styp.replace('*', '') field_styp = field_styp.replace('*', '')
@ -4101,7 +4104,11 @@ fn (mut g Gen) gen_str_for_struct(info table.Struct, styp, str_fn_name string) {
g.auto_str_funcs.write('${field_styp_fn_name}( it->${c_name(field.name)} ) ') g.auto_str_funcs.write('${field_styp_fn_name}( it->${c_name(field.name)} ) ')
} else if sym.kind == .struct_ { } else if sym.kind == .struct_ {
g.auto_str_funcs.write('indents, ') g.auto_str_funcs.write('indents, ')
g.auto_str_funcs.write('${field_styp_fn_name}( it->${c_name(field.name)}$second_str_param ) ') if has_custom_str {
g.auto_str_funcs.write('${field_styp_fn_name}( it->${c_name(field.name)} ) ')
}else{
g.auto_str_funcs.write('indent_${field_styp_fn_name}( it->${c_name(field.name)}, indent_count + 1 ) ')
}
} else if sym.kind in [.array, .array_fixed, .map] { } else if sym.kind in [.array, .array_fixed, .map] {
g.auto_str_funcs.write('indents, ') g.auto_str_funcs.write('indents, ')
g.auto_str_funcs.write('${field_styp_fn_name}( it->${c_name(field.name)}) ') g.auto_str_funcs.write('${field_styp_fn_name}( it->${c_name(field.name)}) ')
@ -4145,9 +4152,9 @@ fn (mut g Gen) gen_str_for_array(info table.Array, styp, str_fn_name string) {
g.auto_str_funcs.writeln('\t\t$field_styp it = (*($field_styp*)array_get(a, i));') g.auto_str_funcs.writeln('\t\t$field_styp it = (*($field_styp*)array_get(a, i));')
if sym.kind == .struct_ && !sym_has_str_method { if sym.kind == .struct_ && !sym_has_str_method {
if is_elem_ptr { if is_elem_ptr {
g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}(*it,0);') g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}(*it);')
} else { } else {
g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}(it,0);') g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}(it);')
} }
} else if sym.kind in [.f32, .f64] { } else if sym.kind in [.f32, .f64] {
g.auto_str_funcs.writeln('\t\tstring x = _STR("%g", 1, it);') g.auto_str_funcs.writeln('\t\tstring x = _STR("%g", 1, it);')
@ -4201,7 +4208,7 @@ fn (mut g Gen) gen_str_for_array_fixed(info table.ArrayFixed, styp, str_fn_name
g.auto_str_funcs.writeln('\tstrings__Builder_write(&sb, tos_lit("["));') g.auto_str_funcs.writeln('\tstrings__Builder_write(&sb, tos_lit("["));')
g.auto_str_funcs.writeln('\tfor (int i = 0; i < $info.size; ++i) {') g.auto_str_funcs.writeln('\tfor (int i = 0; i < $info.size; ++i) {')
if sym.kind == .struct_ && !sym_has_str_method { 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));') g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${elem_str_fn_name}(a[i]));')
} else if sym.kind in [.f32, .f64] { } else if sym.kind in [.f32, .f64] {
g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, _STR("%g", 1, a[i]));') g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, _STR("%g", 1, a[i]));')
} else if sym.kind == .string { } else if sym.kind == .string {
@ -4252,7 +4259,7 @@ fn (mut g Gen) gen_str_for_map(info table.Map, styp, str_fn_name string) {
if val_sym.kind == .string { if val_sym.kind == .string {
g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, _STR("\'%.*s\\000\'", 2, it));') 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') { } else if val_sym.kind == .struct_ && !val_sym.has_method('str') {
g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${elem_str_fn_name}(it,0));') g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${elem_str_fn_name}(it));')
} else if val_sym.kind in [.f32, .f64] { } else if val_sym.kind in [.f32, .f64] {
g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, _STR("%g", 1, it));') g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, _STR("%g", 1, it));')
} else { } else {
@ -4273,12 +4280,7 @@ fn (mut g Gen) gen_str_for_varg(styp, str_fn_name string, has_str_method bool) {
g.auto_str_funcs.writeln('\tstrings__Builder sb = strings__new_builder(it.len);') g.auto_str_funcs.writeln('\tstrings__Builder sb = strings__new_builder(it.len);')
g.auto_str_funcs.writeln('\tstrings__Builder_write(&sb, tos_lit("["));') g.auto_str_funcs.writeln('\tstrings__Builder_write(&sb, tos_lit("["));')
g.auto_str_funcs.writeln('\tfor(int i=0; i<it.len; ++i) {') g.auto_str_funcs.writeln('\tfor(int i=0; i<it.len; ++i) {')
if has_str_method { g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${str_fn_name}(it.args[i]));')
g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${str_fn_name}(it.args[i]));')
} else {
// autogenerated str methods take the indent level as a second argument:
g.auto_str_funcs.writeln('\t\tstrings__Builder_write(&sb, ${str_fn_name}(it.args[i], 0));')
}
g.auto_str_funcs.writeln('\t\tif (i < it.len-1) {') g.auto_str_funcs.writeln('\t\tif (i < it.len-1) {')
g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write(&sb, tos_lit(", "));') g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write(&sb, tos_lit(", "));')
g.auto_str_funcs.writeln('\t\t}') g.auto_str_funcs.writeln('\t\t}')
@ -4313,7 +4315,7 @@ fn (mut g Gen) gen_str_for_multi_return(info table.MultiReturn, styp, str_fn_nam
arg_str_fn_name = styp_to_str_fn_name(field_styp) arg_str_fn_name = styp_to_str_fn_name(field_styp)
} }
if sym.kind == .struct_ && !sym_has_str_method { if sym.kind == .struct_ && !sym_has_str_method {
g.auto_str_funcs.writeln('\tstrings__Builder_write(&sb, ${str_fn_name}(a.arg$i,0));') g.auto_str_funcs.writeln('\tstrings__Builder_write(&sb, ${str_fn_name}(a.arg$i));')
} else if sym.kind in [.f32, .f64] { } else if sym.kind in [.f32, .f64] {
g.auto_str_funcs.writeln('\tstrings__Builder_write(&sb, _STR("%g", 1, a.arg$i));') g.auto_str_funcs.writeln('\tstrings__Builder_write(&sb, _STR("%g", 1, a.arg$i));')
} else if sym.kind == .string { } else if sym.kind == .string {

View File

@ -515,10 +515,6 @@ fn (mut g Gen) fn_call(node ast.CallExpr) {
g.write('*') g.write('*')
} }
g.expr(expr) g.expr(expr)
if !typ.has_flag(.variadic) && sym.kind == .struct_ &&
styp != 'ptr' && !sym.has_method('str') {
g.write(', 0') // trailing 0 is initial struct indent count
}
} }
g.write('))') g.write('))')
} }