From 9958881cbedd026c8b7847d98ad0285ac7c386c2 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Sat, 19 Sep 2020 17:18:36 +0100 Subject: [PATCH] parser: support parsing unsafe (as first token) as UnsafeExpr (#6032) --- doc/docs.md | 2 +- vlib/v/ast/ast.v | 11 +++++- vlib/v/ast/str.v | 2 +- vlib/v/checker/checker.v | 25 ++----------- vlib/v/fmt/fmt.v | 3 +- vlib/v/gen/cgen.v | 3 +- vlib/v/gen/js/js.v | 3 +- vlib/v/parser/parser.v | 74 +++++++++++++++++++++++++++----------- vlib/v/parser/pratt.v | 16 ++++++--- vlib/v/tests/unsafe_test.v | 34 +++++++++++------- 10 files changed, 104 insertions(+), 69 deletions(-) diff --git a/doc/docs.md b/doc/docs.md index 807a122c11..9949ff3309 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -2075,7 +2075,7 @@ To mark potentially memory-unsafe operations, enclose them in an `unsafe` block: mut p := unsafe { &byte(malloc(2)) } p[0] = `h` // Error: pointer indexing is only allowed in `unsafe` blocks unsafe { - p[0] = `h` + p[0] = `h` // OK p[1] = `i` } p++ // Error: pointer arithmetic is only allowed in `unsafe` blocks diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index e3bffc9ca7..fc6f892328 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -491,7 +491,7 @@ pub mut: pub struct UnsafeExpr { pub: - stmts []Stmt + expr Expr pos token.Position } @@ -1082,6 +1082,15 @@ pub fn (expr Expr) is_lvalue() bool { return false } +pub fn (expr Expr) is_expr() bool { + match expr { + IfExpr {return expr.is_expr} + MatchExpr {return expr.is_expr} + else {} + } + return true +} + pub fn (stmt Stmt) position() token.Position { match stmt { AssertStmt { return stmt.pos } diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index e7328d2487..ca9203d31b 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -264,7 +264,7 @@ pub fn (x Expr) str() string { return '_likely_(${it.expr.str()})' } UnsafeExpr { - return 'unsafe { $it.stmts.len stmts }' + return 'unsafe { $it.expr }' } else {} } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index fc717ed409..40860e0bbf 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -3101,30 +3101,11 @@ pub fn (mut c Checker) lock_expr(mut node ast.LockExpr) table.Type { } pub fn (mut c Checker) unsafe_expr(mut node ast.UnsafeExpr) table.Type { - slen := node.stmts.len - if slen > 1 { - c.error('FIXME: unsafe expression block should support multiple statements', node.pos) - return table.none_type - } - if slen == 0 { - c.error('unsafe expression does not yield an expression', node.pos) - return table.none_type - } assert !c.inside_unsafe c.inside_unsafe = true - defer { - c.inside_unsafe = false - } - if slen > 1 { - c.stmts(node.stmts[0..slen - 1]) - } - last := node.stmts[0] - if last is ast.ExprStmt { - t := c.expr(last.expr) - return t - } - c.error('unsafe expression does not yield an expression', node.pos) - return table.none_type + t := c.expr(node.expr) + c.inside_unsafe = false + return t } pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 9e787e2170..a31847670d 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -1075,8 +1075,7 @@ pub fn (mut f Fmt) expr(node ast.Expr) { } ast.UnsafeExpr { f.write('unsafe {') - es := node.stmts[0] as ast.ExprStmt - f.expr(es.expr) + f.expr(node.expr) f.write('}') } } diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 9cbde9a0a9..e9160a965e 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -2224,8 +2224,7 @@ fn (mut g Gen) expr(node ast.Expr) { g.write(')') } ast.UnsafeExpr { - es := node.stmts[0] as ast.ExprStmt - g.expr(es.expr) + g.expr(node.expr) } } } diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index b4fbc9cc09..def3e56838 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -655,8 +655,7 @@ fn (mut g JsGen) expr(node ast.Expr) { // TODO } ast.UnsafeExpr { - es := node.stmts[0] as ast.ExprStmt - g.expr(es.expr) + g.expr(node.expr) } } } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 6717351ab6..7775adc0c5 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -623,27 +623,7 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { } } .key_unsafe { - if p.peek_tok.kind == .lcbr { - // unsafe { - p.next() - assert !p.inside_unsafe - p.inside_unsafe = true - stmts := p.parse_block() - p.inside_unsafe = false - return ast.Block{ - stmts: stmts - is_unsafe: true - } - } else { - p.error_with_pos('please use `unsafe {`', p.tok.position()) - } - // unsafe( ; NB: this will be never reached - pos := p.tok.position() - ex := p.expr(0) - return ast.ExprStmt{ - expr: ex - pos: pos - } + return p.unsafe_stmt() } .hash { return p.hash() @@ -1988,3 +1968,55 @@ pub fn (mut p Parser) mark_var_as_used(varname string) bool { } return false } + +fn (mut p Parser) unsafe_stmt() ast.Stmt { + pos := p.tok.position() + p.next() + if p.tok.kind != .lcbr { + p.error_with_pos('please use `unsafe {`', p.tok.position()) + } + p.next() + assert !p.inside_unsafe + if p.tok.kind == .rcbr { + // `unsafe {}` + p.next() + return ast.Block{ + is_unsafe: true + } + } + p.inside_unsafe = true + p.open_scope() // needed in case of `unsafe {stmt}` + defer { + p.inside_unsafe = false + p.close_scope() + } + stmt := p.stmt(false) + if p.tok.kind == .rcbr { + if stmt is ast.ExprStmt { + // `unsafe {expr}` + if stmt.expr.is_expr() { + p.next() + ue := ast.UnsafeExpr{ + expr: stmt.expr + pos: pos + } + // parse e.g. `unsafe {expr}.foo()` + expr := p.expr_with_left(ue, 0, p.is_stmt_ident) + return ast.ExprStmt{ + expr: expr + pos: pos + } + } + } + } + // unsafe {stmts} + mut stmts := [stmt] + for p.tok.kind != .rcbr { + stmts << p.stmt(false) + } + p.next() + return ast.Block{ + stmts: stmts + is_unsafe: true + } +} diff --git a/vlib/v/parser/pratt.v b/vlib/v/parser/pratt.v index 8b9af1b129..cde83884b7 100644 --- a/vlib/v/parser/pratt.v +++ b/vlib/v/parser/pratt.v @@ -13,7 +13,6 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { eprintln('parsing file: ${p.file_name:-30} | tok.kind: ${p.tok.kind:-10} | tok.lit: ${p.tok.lit:-10} | tok_pos: ${tok_pos.str():-45} | expr($precedence)') } // println('\n\nparser.expr()') - mut typ := table.void_type mut node := ast.Expr{} is_stmt_ident := p.is_stmt_ident p.is_stmt_ident = false @@ -96,16 +95,18 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { node = p.if_expr(false) } .key_unsafe { + // unsafe { p.next() pos := p.tok.position() assert !p.inside_unsafe p.inside_unsafe = true - stmts := p.parse_block() - p.inside_unsafe = false + p.check(.lcbr) node = ast.UnsafeExpr{ - stmts: stmts + expr: p.expr(0) pos: pos } + p.check(.rcbr) + p.inside_unsafe = false } .key_lock, .key_rlock { node = p.lock_expr() @@ -228,6 +229,11 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { p.error('expr(): bad token `$p.tok.kind.str()`') } } + return p.expr_with_left(node, precedence, is_stmt_ident) +} + +pub fn (mut p Parser) expr_with_left(left ast.Expr, precedence int, is_stmt_ident bool) ast.Expr { + mut node := left // Infix for precedence < p.tok.precedence() { if p.tok.kind == .dot { @@ -244,7 +250,7 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { // sum type as cast `x := SumType as Variant` pos := p.tok.position() p.next() - typ = p.parse_type() + typ := p.parse_type() node = ast.AsCast{ expr: node typ: typ diff --git a/vlib/v/tests/unsafe_test.v b/vlib/v/tests/unsafe_test.v index 6080a2718b..c3500d2525 100644 --- a/vlib/v/tests/unsafe_test.v +++ b/vlib/v/tests/unsafe_test.v @@ -3,17 +3,9 @@ fn test_ptr_assign() { mut p := &v[0] unsafe { (*p)++ - } - unsafe { - p++ - } // p now points to v[1] - unsafe { + p++ // p now points to v[1] (*p) += 2 - } - unsafe { - p += 2 - } // p now points to v[3] - unsafe { + p += 2 // p now points to v[3] *p = 31 } assert v[0] == 6 @@ -26,8 +18,7 @@ fn test_ptr_infix() { v := 4 mut q := unsafe {&v - 1} q = unsafe {q + 3} - _ := q - _ := v + assert q == unsafe {&v + 2} } struct S1 { @@ -44,3 +35,22 @@ fn test_funcs() { } _ = C.strerror(0) // [trusted] function prototype in builtin/cfns.c.v } + +fn test_if_expr_unsafe() { + i := 4 + p := if true { + unsafe {&i} + } + else {unsafe {&i}} + assert *p == 4 +} + +fn test_unsafe_if_stmt() int { + i := 4 + unsafe { + if true { + return (&i)[0] + } + } + return i +}