diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 803ce56200..a4435718b4 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -682,10 +682,19 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type { } .arrow { // `chan <- elem` if left.kind == .chan { - elem_type := left.chan_info().elem_type + chan_info := left.chan_info() + elem_type := chan_info.elem_type if !c.check_types(right_type, elem_type) { c.error('cannot push `$right.name` on `$left.name`', right_pos) } + if chan_info.is_mut { + // TODO: The error message of the following could be more specific... + c.fail_if_immutable(infix_expr.right) + } + if elem_type.is_ptr() && !right_type.is_ptr() { + c.error('cannon push non-reference `$right.source_name` on `$left.source_name`', + right_pos) + } } else { c.error('cannot push on non-channel `$left.name`', left_pos) } @@ -1687,8 +1696,9 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { } right_first := assign_stmt.right[0] mut right_len := assign_stmt.right.len + mut right_type0 := table.void_type if right_first is ast.CallExpr || right_first is ast.IfExpr || right_first is ast.MatchExpr { - right_type0 := c.expr(right_first) + right_type0 = c.expr(right_first) assign_stmt.right_types = [ c.check_expr_opt_call(right_first, right_type0), ] @@ -1710,19 +1720,34 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { } return } - // Check `x := &y` + // Check `x := &y` and `mut x := <-ch` if right_first is ast.PrefixExpr { node := right_first left_first := assign_stmt.left[0] - if node.op == .amp && node.right is ast.Ident { - ident := node.right as ast.Ident - scope := c.file.scope.innermost(node.pos.pos) - if v := scope.find_var(ident.name) { - if left_first is ast.Ident { - assigned_var := left_first - if !v.is_mut && assigned_var.is_mut && !c.inside_unsafe { - c.error('`$ident.name` is immutable, cannot have a mutable reference to it', - node.pos) + if left_first is ast.Ident { + assigned_var := left_first + if node.right is ast.Ident { + ident := node.right as ast.Ident + scope := c.file.scope.innermost(node.pos.pos) + if v := scope.find_var(ident.name) { + right_type0 = v.typ + if node.op == .amp { + if !v.is_mut && assigned_var.is_mut && !c.inside_unsafe { + c.error('`$ident.name` is immutable, cannot have a mutable reference to it', + node.pos) + } + } + } + } + if node.op == .arrow { + if assigned_var.is_mut { + right_sym := c.table.get_type_symbol(right_type0) + if right_sym.kind == .chan { + chan_info := right_sym.chan_info() + if chan_info.elem_type.is_ptr() && !chan_info.is_mut { + c.error('cannot have a mutable reference to object from `$right_sym.source_name`', + node.pos) + } } } } diff --git a/vlib/v/checker/tests/chan_mut.out b/vlib/v/checker/tests/chan_mut.out new file mode 100644 index 0000000000..86835c45b4 --- /dev/null +++ b/vlib/v/checker/tests/chan_mut.out @@ -0,0 +1,28 @@ +vlib/v/checker/tests/chan_mut.vv:8:8: error: `v` is immutable, declare it with `mut` to make it mutable + 6 | fn f(ch chan mut St) { + 7 | v := St{} + 8 | ch <- v + | ^ + 9 | mut w := St{} + 10 | ch <- w +vlib/v/checker/tests/chan_mut.vv:10:8: error: cannon push non-reference `St` on `chan mut St` + 8 | ch <- v + 9 | mut w := St{} + 10 | ch <- w + | ^ + 11 | x := &St{} + 12 | ch <- x +vlib/v/checker/tests/chan_mut.vv:12:8: error: `x` is immutable, declare it with `mut` to make it mutable + 10 | ch <- w + 11 | x := &St{} + 12 | ch <- x + | ^ + 13 | mut y := St{} + 14 | ch <- y +vlib/v/checker/tests/chan_mut.vv:14:8: error: cannon push non-reference `St` on `chan mut St` + 12 | ch <- x + 13 | mut y := St{} + 14 | ch <- y + | ^ + 15 | mut z := &St{n: 7} + 16 | // this works diff --git a/vlib/v/checker/tests/chan_mut.vv b/vlib/v/checker/tests/chan_mut.vv new file mode 100644 index 0000000000..1250c51644 --- /dev/null +++ b/vlib/v/checker/tests/chan_mut.vv @@ -0,0 +1,27 @@ +struct St{ +mut: + n int +} + +fn f(ch chan mut St) { + v := St{} + ch <- v + mut w := St{} + ch <- w + x := &St{} + ch <- x + mut y := St{} + ch <- y + mut z := &St{n: 7} + // this works + ch <- z +} + +fn main() { + c := chan mut St{} + go f(c) + mut y := <-c + z := <-c + println(y) + println(z) +} diff --git a/vlib/v/checker/tests/chan_ref.out b/vlib/v/checker/tests/chan_ref.out new file mode 100644 index 0000000000..7675b91824 --- /dev/null +++ b/vlib/v/checker/tests/chan_ref.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/chan_ref.vv:10:8: error: cannon push non-reference `St` on `chan &St` + 8 | fn f(ch chan &St, sem sync.Semaphore) { + 9 | w := St{} + 10 | ch <- w + | ^ + 11 | mut x := St{} + 12 | ch <- x +vlib/v/checker/tests/chan_ref.vv:12:8: error: cannon push non-reference `St` on `chan &St` + 10 | ch <- w + 11 | mut x := St{} + 12 | ch <- x + | ^ + 13 | // the following works + 14 | y := &St{} +vlib/v/checker/tests/chan_ref.vv:28:11: error: cannot have a mutable reference to object from `chan &St` + 26 | y := <-c + 27 | // this should fail + 28 | mut z := <-c + | ~~ + 29 | z.n = 9 + 30 | sem.post() diff --git a/vlib/v/checker/tests/chan_ref.vv b/vlib/v/checker/tests/chan_ref.vv new file mode 100644 index 0000000000..f187781747 --- /dev/null +++ b/vlib/v/checker/tests/chan_ref.vv @@ -0,0 +1,33 @@ +import sync + +struct St{ +mut: + n int +} + +fn f(ch chan &St, sem sync.Semaphore) { + w := St{} + ch <- w + mut x := St{} + ch <- x + // the following works + y := &St{} + ch <- y + mut z := &St{} + ch <- z + sem.wait() + println(z) +} + +fn main() { + c := chan &St{} + sem := sync.new_semaphore() + go f(c, sem) + y := <-c + // this should fail + mut z := <-c + z.n = 9 + sem.post() + println(y) + println(z) +} diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index fe37005f90..a25c39bfd8 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -58,8 +58,9 @@ pub fn (mut p Parser) parse_chan_type() table.Type { } p.register_auto_import('sync') p.next() + is_mut := p.tok.kind == .key_mut elem_type := p.parse_type() - idx := p.table.find_or_register_chan(elem_type) + idx := p.table.find_or_register_chan(elem_type, is_mut) return table.new_type(idx) } diff --git a/vlib/v/table/atypes.v b/vlib/v/table/atypes.v index 232f1d816c..7044b5485c 100644 --- a/vlib/v/table/atypes.v +++ b/vlib/v/table/atypes.v @@ -762,6 +762,7 @@ pub mut: pub struct Chan { pub mut: elem_type Type + is_mut bool } pub struct Map { diff --git a/vlib/v/table/table.v b/vlib/v/table/table.v index db12d63819..b2f5ac84e0 100644 --- a/vlib/v/table/table.v +++ b/vlib/v/table/table.v @@ -357,16 +357,26 @@ pub fn (t &Table) array_fixed_source_name(elem_type Type, size int) string { } [inline] -pub fn (t &Table) chan_name(elem_type Type) string { +pub fn (t &Table) chan_name(elem_type Type, is_mut bool) string { elem_type_sym := t.get_type_symbol(elem_type) - suffix := if elem_type.is_ptr() { '_ptr' } else { '' } + mut suffix := '' + if is_mut { + suffix = '_mut' + } else if elem_type.is_ptr() { + suffix = '_ptr' + } return 'chan_$elem_type_sym.name' + suffix } [inline] -pub fn (t &Table) chan_source_name(elem_type Type) string { +pub fn (t &Table) chan_source_name(elem_type Type, is_mut bool) string { elem_type_sym := t.get_type_symbol(elem_type) - ptr := if elem_type.is_ptr() { '&' } else { '' } + mut ptr := '' + if is_mut { + ptr = 'mut ' + } else if elem_type.is_ptr() { + ptr = '&' + } return 'chan $ptr$elem_type_sym.source_name' } @@ -389,9 +399,9 @@ pub fn (t &Table) map_source_name(key_type, value_type Type) string { return 'map[${key_type_sym.source_name}]$ptr$value_type_sym.source_name' } -pub fn (mut t Table) find_or_register_chan(elem_type Type) int { - name := t.chan_name(elem_type) - source_name := t.chan_source_name(elem_type) +pub fn (mut t Table) find_or_register_chan(elem_type Type, is_mut bool) int { + name := t.chan_name(elem_type, is_mut) + source_name := t.chan_source_name(elem_type, is_mut) // existing existing_idx := t.type_idxs[name] if existing_idx > 0 { @@ -405,6 +415,7 @@ pub fn (mut t Table) find_or_register_chan(elem_type Type) int { source_name: source_name info: Chan{ elem_type: elem_type + is_mut: is_mut } } return t.register_type_symbol(chan_typ) diff --git a/vlib/v/tests/semaphore_timed_test.v b/vlib/v/tests/semaphore_timed_test.v index ca876a551d..62fadf9a48 100644 --- a/vlib/v/tests/semaphore_timed_test.v +++ b/vlib/v/tests/semaphore_timed_test.v @@ -18,10 +18,11 @@ fn test_semaphore() { // wait for the 2 coroutines to finish using the semaphore stopwatch := time.new_stopwatch({}) mut elapsed := stopwatch.elapsed() - if !sem.timed_wait(50 * time.millisecond) { + if !sem.timed_wait(200 * time.millisecond) { // we should come here due to timeout elapsed = stopwatch.elapsed() } - println('elapsed: ${f64(elapsed)/time.second}s') - assert elapsed >= 48* time.millisecond + elapsed_ms := f64(elapsed)/time.millisecond + println('elapsed: ${elapsed_ms:.1f}ms') + assert elapsed_ms >= 195.0 }