From 0520b755f45529486134d984f21857117851438f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kr=C3=BCger?= <45282134+UweKrueger@users.noreply.github.com> Date: Tue, 16 Feb 2021 12:40:13 +0100 Subject: [PATCH] checker/cgen: support `print*()`, `.str()` and '$x' for `shared` (#8771) --- cmd/tools/vtest-self.v | 1 + vlib/v/checker/check_types.v | 16 ++++++ vlib/v/checker/checker.v | 48 ++++------------ vlib/v/checker/tests/shared_bad_args.out | 30 +++++++++- vlib/v/checker/tests/shared_bad_args.vv | 13 +++++ vlib/v/gen/c/fn.v | 11 +++- vlib/v/gen/c/str.v | 9 ++- .../tests/string_interpolation_shared_test.v | 55 +++++++++++++++++++ 8 files changed, 141 insertions(+), 42 deletions(-) create mode 100644 vlib/v/tests/string_interpolation_shared_test.v diff --git a/cmd/tools/vtest-self.v b/cmd/tools/vtest-self.v index e3d78ef75c..f5bdb11cf1 100644 --- a/cmd/tools/vtest-self.v +++ b/cmd/tools/vtest-self.v @@ -148,6 +148,7 @@ const ( 'vlib/v/tests/shift_test.v', 'vlib/v/tests/str_gen_test.v', 'vlib/v/tests/string_interpolation_multi_return_test.v', + 'vlib/v/tests/string_interpolation_shared_test.v', 'vlib/v/tests/struct_allow_both_field_defaults_and_skip_flag_test.v', 'vlib/v/tests/string_interpolation_test.v', 'vlib/v/tests/struct_test.v', diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 5b8551c98e..af8774f353 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -307,9 +307,25 @@ pub fn (c &Checker) get_default_fmt(ftyp table.Type, typ table.Type) byte { } } +pub fn (mut c Checker) fail_if_not_rlocked(expr ast.Expr, what string) { + if expr is ast.Ident { + if expr.name !in c.rlocked_names && expr.name !in c.locked_names { + action := if what == 'argument' { 'passed' } else { 'used' } + c.error('$expr.name is `shared` and must be `rlock`ed or `lock`ed to be $action as non-mut $what', + expr.pos) + } + } else { + c.error('you have to create a handle and `rlock` it to use a `shared` element as non-mut $what', + expr.position()) + } +} + pub fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) table.Type { for i, expr in node.exprs { ftyp := c.expr(expr) + if ftyp.has_flag(.shared_f) { + c.fail_if_not_rlocked(expr, 'interpolation object') + } node.expr_types << ftyp typ := c.table.unalias_num_type(ftyp) mut fmt := node.fmts[i] diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 577761e5e7..66dece85c6 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1055,30 +1055,6 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type { return if infix_expr.op.is_relational() { table.bool_type } else { return_type } } -// returns name and position of variable that needs write lock -fn (mut c Checker) needs_rlock(expr ast.Expr) (string, token.Position) { - mut to_lock := '' // name of variable that needs lock - mut pos := token.Position{} // and its position - match mut expr { - ast.Ident { - if expr.obj is ast.Var { - mut v := expr.obj as ast.Var - if v.typ.share() == .shared_t { - if expr.name !in c.rlocked_names && expr.name !in c.locked_names { - to_lock = expr.name - pos = expr.pos - } - } - } - } - else { - to_lock = '' - pos = token.Position{} - } - } - return to_lock, pos -} - // returns name and position of variable that needs write lock // also sets `is_changed` to true (TODO update the name to reflect this?) fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) { @@ -1540,11 +1516,7 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type { } } else { if left_type.has_flag(.shared_f) { - to_lock, pos := c.needs_rlock(call_expr.left) - if to_lock != '' { - c.error('$to_lock is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut receiver', - pos) - } + c.fail_if_not_rlocked(call_expr.left, 'receiver') } } if (!left_type_sym.is_builtin() && method.mod != 'builtin') && method.language == .v @@ -1639,10 +1611,8 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type { c.error('`$call_expr.name` parameter `$param.name` is `$tok`, you need to provide `$tok` e.g. `$tok arg${ i + 1}`', arg.expr.position()) } else { - to_lock, pos := c.needs_rlock(arg.expr) - if to_lock != '' { - c.error('$to_lock is `shared` and must be `rlock`ed or `locked` to be passed as non-mut argument', - pos) + if got_arg_typ.has_flag(.shared_f) { + c.fail_if_not_rlocked(arg.expr, 'argument') } } } @@ -1699,6 +1669,9 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type { if call_expr.args.len > 0 { c.error('.str() method calls should have no arguments', call_expr.pos) } + if left_type.has_flag(.shared_f) { + c.fail_if_not_rlocked(call_expr.left, 'receiver') + } return table.string_type } // call struct field fn type @@ -1937,6 +1910,9 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { if fn_name in ['println', 'print', 'eprintln', 'eprint'] && call_expr.args.len > 0 { c.expected_type = table.string_type call_expr.args[0].typ = c.expr(call_expr.args[0].expr) + if call_expr.args[0].typ.has_flag(.shared_f) { + c.fail_if_not_rlocked(call_expr.args[0].expr, 'argument to print') + } /* // TODO: optimize `struct T{} fn (t &T) str() string {return 'abc'} mut a := []&T{} a << &T{} println(a[0])` // It currently generates: @@ -1999,10 +1975,8 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { c.error('`$call_expr.name` parameter `$arg.name` is `$tok`, you need to provide `$tok` e.g. `$tok arg${ i + 1}`', call_arg.expr.position()) } else { - to_lock, pos := c.needs_rlock(call_arg.expr) - if to_lock != '' { - c.error('$to_lock is `shared` and must be `rlock`ed or `lock`ed to be passed as non-mut argument', - pos) + if typ.has_flag(.shared_f) { + c.fail_if_not_rlocked(call_arg.expr, 'argument') } } } diff --git a/vlib/v/checker/tests/shared_bad_args.out b/vlib/v/checker/tests/shared_bad_args.out index 2a0708b5ae..30064c1cb8 100644 --- a/vlib/v/checker/tests/shared_bad_args.out +++ b/vlib/v/checker/tests/shared_bad_args.out @@ -5,7 +5,7 @@ vlib/v/checker/tests/shared_bad_args.vv:43:8: error: r is `shared` and must be ` | ^ 44 | println(u) 45 | } -vlib/v/checker/tests/shared_bad_args.vv:47:16: error: s is `shared` and must be `rlock`ed or `locked` to be passed as non-mut argument +vlib/v/checker/tests/shared_bad_args.vv:47:16: error: s is `shared` and must be `rlock`ed or `lock`ed to be passed as non-mut argument 45 | } 46 | lock r { 47 | v := r.s_val(s) @@ -53,3 +53,31 @@ vlib/v/checker/tests/shared_bad_args.vv:67:12: error: a is `shared` and must be 67 | a_mut(mut a) | ^ 68 | } + 69 | +vlib/v/checker/tests/shared_bad_args.vv:76:10: error: y is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut argument to print + 74 | fn main() { + 75 | shared y := St{ a: 5 } + 76 | println(y) + | ^ + 77 | println('$y') + 78 | a := Ab{ s: St{ a: 3 } } +vlib/v/checker/tests/shared_bad_args.vv:77:12: error: y is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut interpolation object + 75 | shared y := St{ a: 5 } + 76 | println(y) + 77 | println('$y') + | ^ + 78 | a := Ab{ s: St{ a: 3 } } + 79 | println(a.s) +vlib/v/checker/tests/shared_bad_args.vv:79:12: error: you have to create a handle and `rlock` it to use a `shared` element as non-mut argument to print + 77 | println('$y') + 78 | a := Ab{ s: St{ a: 3 } } + 79 | println(a.s) + | ^ + 80 | println('$a.s') + 81 | } +vlib/v/checker/tests/shared_bad_args.vv:80:14: error: you have to create a handle and `rlock` it to use a `shared` element as non-mut interpolation object + 78 | a := Ab{ s: St{ a: 3 } } + 79 | println(a.s) + 80 | println('$a.s') + | ^ + 81 | } diff --git a/vlib/v/checker/tests/shared_bad_args.vv b/vlib/v/checker/tests/shared_bad_args.vv index d3d5dfc37f..08e5d8a69b 100644 --- a/vlib/v/checker/tests/shared_bad_args.vv +++ b/vlib/v/checker/tests/shared_bad_args.vv @@ -66,3 +66,16 @@ fn test_shared_as_mut() { m_mut(mut m) a_mut(mut a) } + +struct Ab { + s shared St +} + +fn main() { + shared y := St{ a: 5 } + println(y) + println('$y') + a := Ab{ s: St{ a: 3 } } + println(a.s) + println('$a.s') +} diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 2bd2435172..47b9fca8ae 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -546,7 +546,11 @@ fn (mut g Gen) method_call(node ast.CallExpr) { return } if node.name == 'str' { - g.gen_str_for_type(node.receiver_type) + mut rec_type := node.receiver_type + if rec_type.has_flag(.shared_f) { + rec_type = rec_type.clear_flag(.shared_f).set_nr_muls(0) + } + g.gen_str_for_type(rec_type) } mut has_cast := false if left_sym.kind == .map && node.name in ['clone', 'move'] { @@ -622,7 +626,8 @@ fn (mut g Gen) method_call(node ast.CallExpr) { } else { g.write('${name}(') } - if node.receiver_type.is_ptr() && (!node.left_type.is_ptr() || node.from_embed_type != 0) { + if node.receiver_type.is_ptr() && (!node.left_type.is_ptr() + || node.from_embed_type != 0 || (node.left_type.has_flag(.shared_f) && node.name != 'str')) { // The receiver is a reference, but the caller provided a value // Add `&` automatically. // TODO same logic in call_args() @@ -651,7 +656,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) { } g.write(embed_name) } - if node.left_type.has_flag(.shared_f) && !node.receiver_type.is_ptr() { + if node.left_type.has_flag(.shared_f) { g.write('->val') } } diff --git a/vlib/v/gen/c/str.v b/vlib/v/gen/c/str.v index eb900a4adf..22102e974f 100644 --- a/vlib/v/gen/c/str.v +++ b/vlib/v/gen/c/str.v @@ -302,7 +302,11 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { } fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype table.Type) { + is_shared := etype.has_flag(.shared_f) mut typ := etype + if is_shared { + typ = typ.clear_flag(.shared_f).set_nr_muls(0) + } mut sym := g.table.get_type_symbol(typ) // when type is alias, print the aliased value if mut sym.info is table.Alias { @@ -351,7 +355,7 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype table.Type) { g.write('${str_fn_name}(') if str_method_expects_ptr && !is_ptr { g.write('&') - } else if (!str_method_expects_ptr && is_ptr) || is_var_mut { + } else if (!str_method_expects_ptr && is_ptr && !is_shared) || is_var_mut { g.write('*') } if expr is ast.ArrayInit { @@ -361,6 +365,9 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype table.Type) { } } g.expr(expr) + if is_shared { + g.write('->val') + } g.write(')') if is_ptr && !is_var_mut { g.write(')') diff --git a/vlib/v/tests/string_interpolation_shared_test.v b/vlib/v/tests/string_interpolation_shared_test.v new file mode 100644 index 0000000000..fb0ba3b187 --- /dev/null +++ b/vlib/v/tests/string_interpolation_shared_test.v @@ -0,0 +1,55 @@ +struct Abc { +mut: + x f64 +} + +struct Qwe { + s shared Abc +} + +fn test_shared_struct_interpolation() { + shared s := Abc{ x: 6.25 } + astr := rlock s { + '$s' + } + assert astr.starts_with('Abc{') + assert astr.contains('x: 6.25') + assert astr.ends_with('}') +} + +fn test_shared_array_interpolation() { + shared a := [0.25, -6.125, 12.5] + astr := rlock a { + '$a' + } + assert astr == '[0.25, -6.125, 12.5]' +} + +fn test_shared_map_interpolation() { + shared m := {'xy': 12.125, 'qwe': -6.0625, 'foo': 0.5} + mstr := rlock m { + '$m' + } + assert mstr == "{'xy': 12.125, 'qwe': -6.0625, 'foo': 0.5}" +} + +fn test_print_shared() { + shared s := Abc{ x: 6.25 } + shared a := [0.25, -6.125, 12.5] + shared m := {'xy': 12.125, 'qwe': -6.0625, 'foo': 0.5} + x, y := rlock a, s { + println(s) + println(a) + s.str(), a.str() + } + z := rlock m { + println(m) + m.str() + } + assert x.starts_with('Abc{') + assert x.contains('x: 6.25') + assert x.ends_with('}') + assert y == '[0.25, -6.125, 12.5]' + assert z == "{'xy': 12.125, 'qwe': -6.0625, 'foo': 0.5}" +} +