checker,cgen: make `shared` behave like `mut` inside `lock` - and like non-mut inside `rlock` (#8526)

pull/8530/head
Uwe Krüger 2021-02-03 00:20:19 +01:00 committed by GitHub
parent 91af2418de
commit df0520b43a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 300 additions and 12 deletions

View File

@ -1033,6 +1033,30 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.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 // returns name and position of variable that needs write lock
// also sets `is_changed` to true (TODO update the name to reflect this?) // 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) { fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) {
@ -1465,9 +1489,26 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
// println('warn $method_name lef.mod=$left_type_sym.mod c.mod=$c.mod') // println('warn $method_name lef.mod=$left_type_sym.mod c.mod=$c.mod')
c.error('method `${left_type_sym.name}.$method_name` is private', call_expr.pos) c.error('method `${left_type_sym.name}.$method_name` is private', call_expr.pos)
} }
rec_share := method.params[0].typ.share()
if rec_share == .shared_t && (c.locked_names.len > 0 || c.rlocked_names.len > 0) {
c.error('method with `shared` receiver cannot be called inside `lock`/`rlock` block',
call_expr.pos)
}
if method.params[0].is_mut { if method.params[0].is_mut {
c.fail_if_immutable(call_expr.left) to_lock, pos := c.fail_if_immutable(call_expr.left)
// call_expr.is_mut = true // call_expr.is_mut = true
if to_lock != '' && rec_share != .shared_t {
c.error('$to_lock is `shared` and must be `lock`ed to be passed as `mut`',
pos)
}
} 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)
}
}
} }
if (!left_type_sym.is_builtin() && method.mod != 'builtin') && method.language == .v if (!left_type_sym.is_builtin() && method.mod != 'builtin') && method.language == .v
&& method.no_body { && method.no_body {
@ -1535,20 +1576,37 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
} else { } else {
method.params[i + 1] method.params[i + 1]
} }
param_share := param.typ.share()
if param_share == .shared_t && (c.locked_names.len > 0 || c.rlocked_names.len > 0) {
c.error('method with `shared` arguments cannot be called inside `lock`/`rlock` block',
call_expr.pos)
}
if arg.is_mut { if arg.is_mut {
c.fail_if_immutable(arg.expr) to_lock, pos := c.fail_if_immutable(arg.expr)
if !param.is_mut { if !param.is_mut {
tok := arg.share.str() tok := arg.share.str()
c.error('`$call_expr.name` parameter `$param.name` is not `$tok`, `$tok` is not needed`', c.error('`$call_expr.name` parameter `$param.name` is not `$tok`, `$tok` is not needed`',
arg.expr.position()) arg.expr.position())
} else if param.typ.share() != arg.share { } else {
c.error('wrong shared type', arg.expr.position()) if param.typ.share() != arg.share {
c.error('wrong shared type', arg.expr.position())
}
if to_lock != '' && param_share != .shared_t {
c.error('$to_lock is `shared` and must be `lock`ed to be passed as `mut`',
pos)
}
} }
} else { } else {
if param.is_mut && (!arg.is_mut || param.typ.share() != arg.share) { if param.is_mut {
tok := arg.share.str() tok := arg.share.str()
c.warn('`$call_expr.name` parameter `$param.name` is `$tok`, you need to provide `$tok` e.g. `$tok arg${ c.error('`$call_expr.name` parameter `$param.name` is `$tok`, you need to provide `$tok` e.g. `$tok arg${
i + 1}`', arg.expr.position()) 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)
}
} }
} }
} }
@ -1874,20 +1932,37 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
c.error('when forwarding a varg variable, it must be the final argument', c.error('when forwarding a varg variable, it must be the final argument',
call_expr.pos) call_expr.pos)
} }
arg_share := arg.typ.share()
if arg_share == .shared_t && (c.locked_names.len > 0 || c.rlocked_names.len > 0) {
c.error('function with `shared` arguments cannot be called inside `lock`/`rlock` block',
call_expr.pos)
}
if call_arg.is_mut { if call_arg.is_mut {
c.fail_if_immutable(call_arg.expr) to_lock, pos := c.fail_if_immutable(call_arg.expr)
if !arg.is_mut { if !arg.is_mut {
tok := call_arg.share.str() tok := call_arg.share.str()
c.error('`$call_expr.name` parameter `$arg.name` is not `$tok`, `$tok` is not needed`', c.error('`$call_expr.name` parameter `$arg.name` is not `$tok`, `$tok` is not needed`',
call_arg.expr.position()) call_arg.expr.position())
} else if arg.typ.share() != call_arg.share { } else {
c.error('wrong shared type', call_arg.expr.position()) if arg.typ.share() != call_arg.share {
c.error('wrong shared type', call_arg.expr.position())
}
if to_lock != '' && !arg.typ.has_flag(.shared_f) {
c.error('$to_lock is `shared` and must be `lock`ed to be passed as `mut`',
pos)
}
} }
} else { } else {
if arg.is_mut && (!call_arg.is_mut || arg.typ.share() != call_arg.share) { if arg.is_mut {
tok := call_arg.share.str() tok := call_arg.share.str()
c.warn('`$call_expr.name` parameter `$arg.name` is `$tok`, you need to provide `$tok` e.g. `$tok arg${ 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()) 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)
}
} }
} }
// Handle expected interface // Handle expected interface

View File

@ -0,0 +1,55 @@
vlib/v/checker/tests/shared_bad_args.vv:43:8: error: r is `shared` and must be `rlock`ed or `lock`ed to be used as non-mut receiver
41 | shared r := Qr{ a: 7 }
42 | lock s {
43 | u := r.s_val(s)
| ^
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
45 | }
46 | lock r {
47 | v := r.s_val(s)
| ^
48 | println(v)
49 | }
vlib/v/checker/tests/shared_bad_args.vv:50:13: error: m is `shared` and must be `rlock`ed or `lock`ed to be passed as non-mut argument
48 | println(v)
49 | }
50 | w := m_val(m)
| ^
51 | x := a_val(a)
52 | println('$w $x')
vlib/v/checker/tests/shared_bad_args.vv:51:13: error: a is `shared` and must be `rlock`ed or `lock`ed to be passed as non-mut argument
49 | }
50 | w := m_val(m)
51 | x := a_val(a)
| ^
52 | println('$w $x')
53 | }
vlib/v/checker/tests/shared_bad_args.vv:61:3: error: r is `shared` and must be `lock`ed to be passed as `mut`
59 | shared r := Qr{ a: 7 }
60 | lock s {
61 | r.s_mut(mut s)
| ^
62 | }
63 | lock r {
vlib/v/checker/tests/shared_bad_args.vv:64:15: error: s is `shared` and must be `lock`ed to be passed as `mut`
62 | }
63 | lock r {
64 | r.s_mut(mut s)
| ^
65 | }
66 | m_mut(mut m)
vlib/v/checker/tests/shared_bad_args.vv:66:12: error: m is `shared` and must be `lock`ed to be passed as `mut`
64 | r.s_mut(mut s)
65 | }
66 | m_mut(mut m)
| ^
67 | a_mut(mut a)
68 | }
vlib/v/checker/tests/shared_bad_args.vv:67:12: error: a is `shared` and must be `lock`ed to be passed as `mut`
65 | }
66 | m_mut(mut m)
67 | a_mut(mut a)
| ^
68 | }

View File

@ -0,0 +1,68 @@
struct St {
mut:
a int
}
struct Qr {
mut:
a int
}
fn (mut r Qr) s_mut(mut s St) {
r.a = 5
s.a = 7
}
fn (r Qr) s_val(s St) int {
return r.a * s.a
}
fn m_mut(mut a map[string]f64) {
a['yxcv'] = -2.25
}
fn m_val(a map[string]f64) f64 {
x := a['yxcv']
return x
}
fn a_mut(mut a []int) {
a[2] = 42
}
fn a_val(a []int) int {
return a[1]
}
fn test_shared_as_value() {
shared s := St{ a: 5 }
shared a := [3, 4, 6, 13, -23]
shared m := {'qw': 12.75, 'yxcv': -3.125, 'poiu': 88.0625}
shared r := Qr{ a: 7 }
lock s {
u := r.s_val(s)
println(u)
}
lock r {
v := r.s_val(s)
println(v)
}
w := m_val(m)
x := a_val(a)
println('$w $x')
}
fn test_shared_as_mut() {
shared s := St{ a: 5 }
shared a := [3, 4, 6, 13, -23]
shared m := {'qw': 12.75, 'yxcv': -3.125, 'poiu': 88.0625}
shared r := Qr{ a: 7 }
lock s {
r.s_mut(mut s)
}
lock r {
r.s_mut(mut s)
}
m_mut(mut m)
a_mut(mut a)
}

View File

@ -522,7 +522,9 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
} }
} else if !node.receiver_type.is_ptr() && node.left_type.is_ptr() && node.name != 'str' } else if !node.receiver_type.is_ptr() && node.left_type.is_ptr() && node.name != 'str'
&& node.from_embed_type == 0 { && node.from_embed_type == 0 {
g.write('/*rec*/*') if !node.left_type.has_flag(.shared_f) {
g.write('/*rec*/*')
}
} }
if g.is_autofree && node.free_receiver && !g.inside_lambda && !g.is_builtin_mod { if g.is_autofree && node.free_receiver && !g.inside_lambda && !g.is_builtin_mod {
// The receiver expression needs to be freed, use the temp var. // The receiver expression needs to be freed, use the temp var.
@ -540,6 +542,9 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
} }
g.write(embed_name) g.write(embed_name)
} }
if node.left_type.has_flag(.shared_f) && !node.receiver_type.is_ptr() {
g.write('->val')
}
} }
if has_cast { if has_cast {
g.write(')') g.write(')')
@ -958,6 +963,13 @@ fn (mut g Gen) ref_or_deref_arg(arg ast.CallArg, expected_type table.Type) {
g.write('(voidptr)&/*qq*/') g.write('(voidptr)&/*qq*/')
} }
} }
} else if arg.typ.has_flag(.shared_f) && !expected_type.has_flag(.shared_f) {
if expected_type.is_ptr() {
g.write('&')
}
g.expr(arg.expr)
g.write('->val')
return
} }
g.expr_with_cast(arg.expr, arg.typ, expected_type) g.expr_with_cast(arg.expr, arg.typ, expected_type)
} }

View File

@ -0,0 +1,78 @@
struct St {
mut:
a int
}
struct Qr {
mut:
a int
}
fn (mut r Qr) s_mut(mut s St) {
r.a = 5
s.a = 7
}
fn (r Qr) s_val(s St) int {
return r.a * s.a
}
fn m_mut(mut a map[string]f64) {
a['yxcv'] = -2.25
}
fn m_val(a map[string]f64) f64 {
x := a['yxcv']
return x
}
fn a_mut(mut a []int) {
a[2] = 42
}
fn a_val(a []int) int {
return a[1]
}
fn test_shared_as_value() {
shared s := St{ a: 5 }
shared a := [3, 4, 6, 13, -23]
shared m := {'qw': 12.75, 'yxcv': -3.125, 'poiu': 88.0625}
shared r := Qr{ a: 7 }
rlock s, r {
u := r.s_val(s)
assert u == 35
}
lock s, r {
v := r.s_val(s)
assert v == 35
}
rlock m {
w := m_val(m)
assert w == -3.125
}
lock a {
x := a_val(a)
assert x == 4
}
}
fn test_shared_as_mut() {
shared s := St{ a: 5 }
shared a := [3, 4, 6, 13, -23]
shared m := {'qw': 12.75, 'yxcv': -3.125, 'poiu': 88.0625}
shared r := Qr{ a: 7 }
lock s, r {
r.s_mut(mut s)
x := r.a * s.a
assert x == 35
}
lock a, m {
m_mut(mut m)
a_mut(mut a)
y := m['yxcv']
z := a[2]
assert y == -2.25
assert z == 42
}
}