checker/channels: check `mut`/`&` state of transmitted objects (#6315)
							parent
							
								
									601d098b48
								
							
						
					
					
						commit
						2cb711ee15
					
				| 
						 | 
					@ -682,10 +682,19 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		.arrow { // `chan <- elem`
 | 
							.arrow { // `chan <- elem`
 | 
				
			||||||
			if left.kind == .chan {
 | 
								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) {
 | 
									if !c.check_types(right_type, elem_type) {
 | 
				
			||||||
					c.error('cannot push `$right.name` on `$left.name`', right_pos)
 | 
										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 {
 | 
								} else {
 | 
				
			||||||
				c.error('cannot push on non-channel `$left.name`', left_pos)
 | 
									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]
 | 
						right_first := assign_stmt.right[0]
 | 
				
			||||||
	mut right_len := assign_stmt.right.len
 | 
						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 {
 | 
						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 = [
 | 
							assign_stmt.right_types = [
 | 
				
			||||||
			c.check_expr_opt_call(right_first, right_type0),
 | 
								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
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// Check `x := &y`
 | 
						// Check `x := &y` and `mut x := <-ch`
 | 
				
			||||||
	if right_first is ast.PrefixExpr {
 | 
						if right_first is ast.PrefixExpr {
 | 
				
			||||||
		node := right_first
 | 
							node := right_first
 | 
				
			||||||
		left_first := assign_stmt.left[0]
 | 
							left_first := assign_stmt.left[0]
 | 
				
			||||||
		if node.op == .amp && node.right is ast.Ident {
 | 
							if left_first is ast.Ident {
 | 
				
			||||||
			ident := node.right as ast.Ident
 | 
								assigned_var := left_first
 | 
				
			||||||
			scope := c.file.scope.innermost(node.pos.pos)
 | 
								if node.right is ast.Ident {
 | 
				
			||||||
			if v := scope.find_var(ident.name) {
 | 
									ident := node.right as ast.Ident
 | 
				
			||||||
				if left_first is ast.Ident {
 | 
									scope := c.file.scope.innermost(node.pos.pos)
 | 
				
			||||||
					assigned_var := left_first
 | 
									if v := scope.find_var(ident.name) {
 | 
				
			||||||
					if !v.is_mut && assigned_var.is_mut && !c.inside_unsafe {
 | 
										right_type0 = v.typ
 | 
				
			||||||
						c.error('`$ident.name` is immutable, cannot have a mutable reference to it',
 | 
										if node.op == .amp {
 | 
				
			||||||
							node.pos)
 | 
											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)
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					@ -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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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()
 | 
				
			||||||
| 
						 | 
					@ -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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -58,8 +58,9 @@ pub fn (mut p Parser) parse_chan_type() table.Type {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	p.register_auto_import('sync')
 | 
						p.register_auto_import('sync')
 | 
				
			||||||
	p.next()
 | 
						p.next()
 | 
				
			||||||
 | 
						is_mut := p.tok.kind == .key_mut
 | 
				
			||||||
	elem_type := p.parse_type()
 | 
						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)
 | 
						return table.new_type(idx)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -762,6 +762,7 @@ pub mut:
 | 
				
			||||||
pub struct Chan {
 | 
					pub struct Chan {
 | 
				
			||||||
pub mut:
 | 
					pub mut:
 | 
				
			||||||
	elem_type Type
 | 
						elem_type Type
 | 
				
			||||||
 | 
						is_mut    bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct Map {
 | 
					pub struct Map {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -357,16 +357,26 @@ pub fn (t &Table) array_fixed_source_name(elem_type Type, size int) string {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[inline]
 | 
					[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)
 | 
						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
 | 
						return 'chan_$elem_type_sym.name' + suffix
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[inline]
 | 
					[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)
 | 
						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'
 | 
						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'
 | 
						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 {
 | 
					pub fn (mut t Table) find_or_register_chan(elem_type Type, is_mut bool) int {
 | 
				
			||||||
	name := t.chan_name(elem_type)
 | 
						name := t.chan_name(elem_type, is_mut)
 | 
				
			||||||
	source_name := t.chan_source_name(elem_type)
 | 
						source_name := t.chan_source_name(elem_type, is_mut)
 | 
				
			||||||
	// existing
 | 
						// existing
 | 
				
			||||||
	existing_idx := t.type_idxs[name]
 | 
						existing_idx := t.type_idxs[name]
 | 
				
			||||||
	if existing_idx > 0 {
 | 
						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
 | 
							source_name: source_name
 | 
				
			||||||
		info: Chan{
 | 
							info: Chan{
 | 
				
			||||||
			elem_type: elem_type
 | 
								elem_type: elem_type
 | 
				
			||||||
 | 
								is_mut:    is_mut
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return t.register_type_symbol(chan_typ)
 | 
						return t.register_type_symbol(chan_typ)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,10 +18,11 @@ fn test_semaphore() {
 | 
				
			||||||
	// wait for the 2 coroutines to finish using the semaphore
 | 
						// wait for the 2 coroutines to finish using the semaphore
 | 
				
			||||||
	stopwatch := time.new_stopwatch({})
 | 
						stopwatch := time.new_stopwatch({})
 | 
				
			||||||
	mut elapsed := stopwatch.elapsed()
 | 
						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
 | 
							// we should come here due to timeout
 | 
				
			||||||
		elapsed = stopwatch.elapsed()
 | 
							elapsed = stopwatch.elapsed()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	println('elapsed: ${f64(elapsed)/time.second}s')
 | 
						elapsed_ms := f64(elapsed)/time.millisecond
 | 
				
			||||||
	assert elapsed >= 48* time.millisecond
 | 
						println('elapsed: ${elapsed_ms:.1f}ms')
 | 
				
			||||||
 | 
						assert elapsed_ms >= 195.0
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue