checker: check array and fields mutability

pull/4627/head
Enzo Baldisserri 2020-04-27 22:53:26 +02:00 committed by GitHub
parent ce1215f507
commit 682838a0cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 340 additions and 86 deletions

View File

@ -24,8 +24,8 @@ const (
'vlib/v/tests/live_test.v', // Linux & Solaris only; since live does not actually work for now with v2, just skip
'vlib/v/tests/asm_test.v', // skip everywhere for now, works on linux with cc != tcc
]
skip_on_linux = []string
skip_on_non_linux = []string
skip_on_linux = []string{}
skip_on_non_linux = []string{}
)
fn main() {

View File

@ -141,7 +141,7 @@ fn test_bf_from_str() {
rand.seed(time.now().unix)
len := 80
mut input := ''
for i in 0..len {
for _ in 0..len {
if rand.next(2) == 1 {
input = input + '1'
}
@ -244,7 +244,7 @@ fn test_bf_resize() {
rand.seed(time.now().unix)
len := 80
mut input := bitfield.new(rand.next(len) + 1)
for i in 0..100 {
for _ in 0..100 {
input.resize(rand.next(len) + 1)
input.setbit(input.getsize() - 1)
}

View File

@ -7,11 +7,12 @@ import strings
pub struct array {
pub:
element_size int
pub mut:
data voidptr// Using a void pointer allows to implement arrays without generics and without generating
// extra code for every type.
len int
cap int
element_size int
}
// Internal function, used by V (`nums := []int`)

View File

@ -33,6 +33,7 @@ pub mut:
}
pub struct Line64 {
pub mut:
f_size_of_struct u32
f_key voidptr
f_line_number u32

View File

@ -5,6 +5,7 @@ struct User {
}
struct A {
mut:
m map[string]int
users map[string]User
}
@ -24,7 +25,7 @@ fn test_map() {
assert 'hi' in m
mut sum := 0
// Test `for in`
for key, val in m {
for _, val in m {
sum += val
}
assert sum == 80 + 101

View File

@ -51,7 +51,7 @@ pub:
// hash_cache int
pub struct ustring {
pub:
pub mut:
s string
runes []int
len int

View File

@ -435,7 +435,7 @@ pub fn (fs FlagParser) usage() string {
if fs.flags.len > 0 {
use += 'Options:\n'
for f in fs.flags {
mut onames:=[]string
mut onames := []string{}
if f.abbr != 0 {
onames << '-${f.abbr.str()}'
}

View File

@ -28,7 +28,7 @@ fn test_rand_r_seed_update() {
for _ in 0..rnd_count {
prev_seed := seed
res := rand.rand_r(&seed)
_ := rand.rand_r(&seed)
assert prev_seed != seed
}

View File

@ -4,8 +4,9 @@
module time
pub struct StopWatch {
mut:
pause_time u64
pub:
pub mut:
start u64
end u64
}

View File

@ -134,9 +134,10 @@ mut:
pub struct ConstDecl {
pub:
fields []ConstField
is_pub bool
pos token.Position
pub mut:
fields []ConstField
}
pub struct StructDecl {
@ -162,10 +163,10 @@ pub:
pub struct StructInitField {
pub:
name string
expr Expr
pos token.Position
mut:
name string
typ table.Type
expected_type table.Type
}
@ -173,10 +174,10 @@ mut:
pub struct StructInit {
pub:
pos token.Position
fields []StructInitField
is_short bool
mut:
typ table.Type
fields []StructInitField
}
// import statement
@ -198,7 +199,6 @@ pub struct FnDecl {
pub:
name string
stmts []Stmt
return_type table.Type
args []table.Arg
is_deprecated bool
is_pub bool
@ -213,6 +213,8 @@ pub:
is_builtin bool // this function is defined in builtin/strconv
ctdefine string // has [if myflag] tag
pos token.Position
pub mut:
return_type table.Type
}
pub struct BranchStmt {
@ -224,10 +226,10 @@ pub struct CallExpr {
pub:
pos token.Position
left Expr // `user` in `user.register()`
is_method bool
mod string
mut:
name string
is_method bool
args []CallArg
expected_arg_types []table.Type
is_c bool
@ -292,10 +294,11 @@ pub struct File {
pub:
path string
mod Module
imports []Import
stmts []Stmt
scope &Scope
global_scope &Scope
mut:
imports []Import
}
pub struct IdentFn {
@ -331,11 +334,11 @@ pub:
tok_kind token.Kind
mod string
pos token.Position
is_mut bool
mut:
name string
kind IdentKind
info IdentInfo
is_mut bool
}
pub fn (i &Ident) var_info() IdentVar {
@ -508,11 +511,11 @@ pub:
pub struct AssignStmt {
pub:
left []Ident
right []Expr
op token.Kind
pos token.Position
mut:
pub mut:
left []Ident
left_types []table.Type
right_types []table.Type
is_static bool // for translated code only
@ -670,8 +673,8 @@ pub:
expr Expr // `buf`
arg Expr // `n` in `string(buf, n)`
typ table.Type // `string`
typname string
mut:
typname string
expr_type table.Type // `byteptr`
has_arg bool
}

View File

@ -121,7 +121,7 @@ pub fn (x Expr) str() string {
return '${it.expr.str()}.${it.field}'
}
StringInterLiteral {
res := []string{}
mut res := []string{}
res << "'"
for i, val in it.vals {
res << val

View File

@ -17,12 +17,12 @@ import v.depgraph
pub struct Builder {
pub:
pref &pref.Preferences
table &table.Table
checker checker.Checker
compiled_dir string // contains os.real_path() of the dir of the final file beeing compiled, or the dir itself when doing `v .`
module_path string
mut:
pref &pref.Preferences
module_search_paths []string
parsed_files []ast.File
global_scope &ast.Scope

View File

@ -364,11 +364,7 @@ pub fn (mut c Checker) infix_expr(infix_expr mut ast.InfixExpr) table.Type {
.left_shift {
if left.kind == .array {
// `array << elm`
match infix_expr.left {
ast.Ident {}
ast.SelectorExpr {}
else { println('typeof: ${typeof(infix_expr.left)}') }
}
c.fail_if_immutable(infix_expr.left)
// the expressions have different types (array_x and x)
if c.table.check(c.table.value_type(left_type), right_type) {
// []T << T
@ -446,6 +442,58 @@ pub fn (mut c Checker) infix_expr(infix_expr mut ast.InfixExpr) table.Type {
}
}
fn (mut c Checker) fail_if_immutable(expr ast.Expr) {
match expr {
ast.Ident {
scope := c.file.scope.innermost(expr.position().pos)
if v := scope.find_var(it.name) {
if !v.is_mut && !v.typ.is_ptr() {
c.error('`$it.name` is immutable, declare it with `mut` to make it mutable',
it.pos)
}
}
}
ast.IndexExpr {
c.fail_if_immutable(it.left)
}
ast.ParExpr {
c.fail_if_immutable(it.expr)
}
ast.PrefixExpr {
c.fail_if_immutable(it.right)
}
ast.SelectorExpr {
// retrieve table.Field
typ_sym := c.table.get_type_symbol(it.expr_type)
match typ_sym.kind {
.struct_ {
struct_info := typ_sym.info as table.Struct
field_info := struct_info.get_field(it.field)
if !field_info.is_mut {
type_str := c.table.type_to_str(it.expr_type)
c.error('field `$it.field` of struct `${type_str}` is immutable', it.pos)
}
c.fail_if_immutable(it.expr)
}
.array, .string {
// This should only happen in `builtin`
// TODO Remove `crypto.rand` when possible (see vlib/crypto/rand/rand.v,
// if `c_array_to_bytes_tmp` doesn't exist, then it's safe to remove it)
if c.file.mod.name !in ['builtin', 'crypto.rand'] {
c.error('`$typ_sym.kind` can not be modified', expr.position())
}
}
else {
c.error('unexpected symbol `${typ_sym.kind}`', expr.position())
}
}
}
else {
c.error('unexpected expression `${typeof(expr)}`', expr.position())
}
}
}
fn (mut c Checker) assign_expr(assign_expr mut ast.AssignExpr) {
c.expected_type = table.void_type
left_type := c.expr(assign_expr.left)
@ -460,32 +508,7 @@ fn (mut c Checker) assign_expr(assign_expr mut ast.AssignExpr) {
return
}
// Make sure the variable is mutable
match assign_expr.left {
ast.Ident {
scope := c.file.scope.innermost(assign_expr.pos.pos)
if v := scope.find_var(it.name) {
if !v.is_mut {
c.error('`$it.name` is immutable, declare it with `mut` to assign to it',
assign_expr.pos)
}
}
}
ast.IndexExpr {
// `m[key] = val`
if it.left is ast.Ident {
ident := it.left as ast.Ident
// TODO copy pasta
scope := c.file.scope.innermost(assign_expr.pos.pos)
if v := scope.find_var(ident.name) {
if !v.is_mut {
c.error('`$ident.name` is immutable, declare it with `mut` to assign to it',
assign_expr.pos)
}
}
}
}
else {}
}
c.fail_if_immutable(assign_expr.left)
// Single side check
match assign_expr.op {
.assign {} // No need to do single side check for =. But here put it first for speed.

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/immutable_array_field_assign.v:9:4: error: field `i` of struct `A` is immutable
7| i: [0]
8| }
9| a.i[0] = 3
^
10| }

View File

@ -0,0 +1,10 @@
struct A {
i []int
}
fn main() {
mut a := A{
i: [0]
}
a.i[0] = 3
}

View File

@ -0,0 +1,10 @@
struct A {
i []int
}
fn main() {
mut a := A{
i: [0]
}
a.i[0] = 3
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/immutable_array_field_shift.v:14:4: error: field `a` of struct `B` is immutable
12| a: A{}
13| }
14| b.a.i << 3
^
15| }

View File

@ -0,0 +1,15 @@
struct A {
mut:
i []int
}
struct B {
a A
}
fn main() {
mut b := B{
a: A{}
}
b.a.i << 3
}

View File

@ -0,0 +1,15 @@
struct A {
mut:
i []int
}
struct B {
a A
}
fn main() {
mut b := B{
a: A{}
}
b.a.i << 3
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/immutable_array_struct_assign.v:8:2: error: `a` is immutable, declare it with `mut` to make it mutable
6| fn main() {
7| a := A{}
8| a.i[0] += 3
^
9| }

View File

@ -0,0 +1,9 @@
struct A {
pub mut:
i []int
}
fn main() {
a := A{}
a.i[0] += 3
}

View File

@ -0,0 +1,9 @@
struct A {
pub mut:
i []int
}
fn main() {
a := A{}
a.i[0] += 3
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/immutable_array_struct_shift.v:8:2: error: `a` is immutable, declare it with `mut` to make it mutable
6| fn main() {
7| a := []A{}
8| a[0].i << 3
^
9| }

View File

@ -0,0 +1,9 @@
struct A {
pub mut:
i []int
}
fn main() {
a := []A{}
a[0].i << 3
}

View File

@ -0,0 +1,9 @@
struct A {
pub mut:
i []int
}
fn main() {
a := []A{}
a[0].i << 3
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/immutable_array_var.v:3:2: error: `a` is immutable, declare it with `mut` to make it mutable
1| fn main() {
2| a := [1, 2]
3| a << 3
^
4| }

View File

@ -0,0 +1,4 @@
fn main() {
a := [1, 2]
a << 3
}

View File

@ -0,0 +1,4 @@
fn main() {
a := [1, 2]
a << 3
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/immutable_field.v:8:4: error: field `i1` of struct `A` is immutable
6| fn main() {
7| a := A{1}
8| a.i1 = 2
~~
9| }

View File

@ -0,0 +1,9 @@
struct A {
pub:
i1 int
}
fn main() {
a := A{1}
a.i1 = 2
}

View File

@ -0,0 +1,9 @@
struct A {
pub:
i1 int
}
fn main() {
a := A{1}
a.i1 = 2
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/immutable_var.v:3:2: error: `a` is immutable, declare it with `mut` to make it mutable
1| fn main() {
2| a := 1
3| a = 2
^
4| }

View File

@ -0,0 +1,4 @@
fn main() {
a := 1
a = 2
}

View File

@ -0,0 +1,4 @@
fn main() {
a := 1
a = 2
}

View File

@ -1,6 +1,6 @@
vlib/v/checker/tests/left_shift_err.v:3:7: error: cannot shift type string into array_int
1| fn main() {
2| l := []int{}
2| mut l := []int{}
3| l << 'test'
~~~~~~
4| }

View File

@ -1,4 +1,4 @@
fn main() {
l := []int{}
mut l := []int{}
l << 'test'
}

View File

@ -1,4 +1,4 @@
fn main() {
l := []int{}
mut l := []int{}
l << 'test'
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/struct_pub_field.v:9:4: error: field `i` of struct `A` is immutable
7| i: 1
8| }
9| a.i = 2
^
10| }

View File

@ -0,0 +1,10 @@
struct A {
i int
}
fn main() {
a := A{
i: 1
}
a.i = 2
}

View File

@ -0,0 +1,10 @@
struct A {
i int
}
fn main() {
a := A{
i: 1
}
a.i = 2
}

View File

@ -56,7 +56,6 @@ struct Gen {
auto_str_funcs strings.Builder // function bodies of all auto generated _str funcs
comptime_defines strings.Builder // custom defines, given by -d/-define flags on the CLI
pcs_declarations strings.Builder // -prof profile counter declarations for each function
pcs []ProfileCounterMeta // -prof profile counter fn_names => fn counter name
table &table.Table
pref &pref.Preferences
mut:
@ -86,6 +85,7 @@ mut:
threaded_fns []string // for generating unique wrapper types and fns for `go xxx()`
array_fn_definitions []string // array equality functions that have been defined
is_json_fn bool // inside json.encode()
pcs []ProfileCounterMeta // -prof profile counter fn_names => fn counter name
}
const (

View File

@ -16,15 +16,15 @@ const (
)
struct JsGen {
out strings.Builder
table &table.Table
definitions strings.Builder
pref &pref.Preferences
mut:
out strings.Builder
namespaces map[string]strings.Builder
namespaces_pub map[string][]string
namespace string
table &table.Table
definitions strings.Builder
pref &pref.Preferences
namespace string
doc &JsDoc
mut:
constants strings.Builder // all global V constants
file ast.File
tmp_count int
@ -110,7 +110,7 @@ pub fn (g mut JsGen) escape_namespace() {
pub fn (g mut JsGen) push_pub_var(s string) {
// Workaround until `m[key]<<val` works.
arr := g.namespaces_pub[g.namespace]
mut arr := g.namespaces_pub[g.namespace]
arr << s
g.namespaces_pub[g.namespace] = arr
}
@ -123,7 +123,7 @@ pub fn (g mut JsGen) find_class_methods(stmts []ast.Stmt) {
// Found struct method, store it to be generated along with the class.
class_name := g.table.get_type_symbol(it.receiver.typ).name
// Workaround until `map[key] << val` works.
arr := g.method_fn_decls[class_name]
mut arr := g.method_fn_decls[class_name]
arr << stmt
g.method_fn_decls[class_name] = arr
}

View File

@ -26,7 +26,6 @@ fn (mut g Gen) profile_fn(fn_name string, is_main bool){
}
}
pub fn (mut g Gen) gen_vprint_profile_stats() {
g.pcs_declarations.writeln('void vprint_profile_stats(){')
fstring := '"%14llu %14.3fms %14.0fns %s \\n"'

View File

@ -284,7 +284,7 @@ fn (mut p Parser) anon_fn() ast.AnonFn {
stmts = p.parse_block_no_scope()
}
p.close_scope()
func := table.Fn{
mut func := table.Fn{
args: args
is_variadic: is_variadic
return_type: return_type

View File

@ -109,21 +109,16 @@ fn (mut p Parser) match_expr() ast.MatchExpr {
} else if p.tok.kind == .name && (p.tok.lit in table.builtin_type_names || (p.tok.lit[0].is_capital() &&
!p.tok.lit.is_upper()) || p.peek_tok.kind == .dot) {
// Sum type match
// if sym.kind == .sum_type {
// p.warn('is sum')
// TODO `exprs << ast.Type{...}
typ := p.parse_type()
x := ast.Type{
exprs << ast.Type{
typ: typ
}
mut expr := ast.Expr{}
expr = x
exprs << expr
p.scope.register('it', ast.Var{
name: 'it'
typ: typ.to_ptr()
pos: cond_pos
is_used: true
is_mut: is_mut
})
// TODO
if p.tok.kind == .comma {

View File

@ -565,7 +565,7 @@ pub fn (mut p Parser) parse_ident(is_c, is_js bool) ast.Ident {
if p.expr_mod.len > 0 {
name = '${p.expr_mod}.$name'
}
mut ident := ast.Ident{
return ast.Ident{
kind: .unresolved
name: name
is_c: is_c
@ -573,7 +573,6 @@ pub fn (mut p Parser) parse_ident(is_c, is_js bool) ast.Ident {
mod: p.mod
pos: pos
}
return ident
}
pub fn (mut p Parser) name_expr() ast.Expr {
@ -694,9 +693,7 @@ pub fn (mut p Parser) name_expr() ast.Expr {
mod: mod
}
} else {
mut ident := ast.Ident{}
ident = p.parse_ident(is_c, is_js)
node = ident
node = p.parse_ident(is_c, is_js)
}
p.expr_mod = ''
return node

View File

@ -193,14 +193,12 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr {
p.inside_is = true
}
right = p.expr(precedence)
mut expr := ast.Expr{}
expr = ast.InfixExpr{
return ast.InfixExpr{
left: left
right: right
op: op
pos: pos
}
return expr
}
fn (mut p Parser) prefix_expr() ast.PrefixExpr {

View File

@ -38,6 +38,9 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
mut mut_pos := -1
mut pub_pos := -1
mut pub_mut_pos := -1
mut is_field_mut := false
mut is_field_pub := false
mut is_field_global := false
if !no_body {
p.check(.lcbr)
for p.tok.kind != .rcbr {
@ -50,17 +53,29 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
if p.tok.kind == .key_mut {
p.check(.key_mut)
pub_mut_pos = fields.len
is_field_pub = true
is_field_mut = true
is_field_global = false
} else {
pub_pos = fields.len
is_field_pub = true
is_field_mut = false
is_field_global = false
}
p.check(.colon)
} else if p.tok.kind == .key_mut {
p.check(.key_mut)
p.check(.colon)
mut_pos = fields.len
is_field_pub = false
is_field_mut = true
is_field_global = false
} else if p.tok.kind == .key_global {
p.check(.key_global)
p.check(.colon)
is_field_pub = true
is_field_mut = true
is_field_global = true
}
field_name := p.check_name()
field_pos := p.tok.position()
@ -108,6 +123,9 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
typ: typ
default_expr: default_expr
has_default_expr: has_default_expr
is_pub: is_field_pub
is_mut: is_field_mut
is_global: is_field_global
}
// println('struct field $ti.name $field_name')
}

View File

@ -311,6 +311,14 @@ pub fn (t &TypeSymbol) map_info() Map {
}
}
[inline]
pub fn (t &TypeSymbol) struct_info() Struct {
match t.info {
Struct { return it }
else { panic('TypeSymbol.struct_info(): no struct info for type: $t.name') }
}
}
/*
pub fn (t TypeSymbol) str() string {
return t.name
@ -499,6 +507,7 @@ pub mut:
}
pub struct Interface {
mut:
gen_types []string
foo string
}
@ -522,6 +531,9 @@ mut:
has_default_expr bool
default_val string
attr string
is_pub bool
is_mut bool
is_global bool
}
pub struct Array {
@ -594,3 +606,19 @@ pub fn (table &Table) type_to_str(t Type) string {
*/
return res
}
pub fn (s Struct) find_field(name string) ?Field {
for field in s.fields {
if field.name == name {
return field
}
}
return none
}
pub fn (s Struct) get_field(name string) Field {
if field := s.find_field(name) {
return field
}
panic('unknown field `$name`')
}

View File

@ -3,7 +3,7 @@ import v.cflag
const (
module_name = 'main'
cdefines = []string
cdefines = []string{}
no_name = ''
no_flag = ''
no_os = ''

View File

@ -18,7 +18,6 @@ pub mut:
pub struct Fn {
pub:
name string
args []Arg
return_type Type
is_variadic bool
@ -28,6 +27,8 @@ pub:
is_pub bool
mod string
ctdefine string // compile time define. myflag, when [if myflag] tag
pub mut:
name string
}
pub struct Arg {
@ -486,7 +487,7 @@ pub fn (t &Table) check(got, expected Type) bool {
exp_type_sym := t.get_type_symbol(expected)
//
if exp_type_sym.kind == .interface_ {
info := exp_type_sym.info as Interface
mut info := exp_type_sym.info as Interface
// println('gen_types before')
// println(info.gen_types)
info.gen_types << got_type_sym.name

View File

@ -40,5 +40,5 @@ pub fn cescaped_path(s string) string {
}
pub fn is_fmt() bool {
return os.executable().contains('vfmt')
return os.executable().contains('vfmt')
}