From 4be45e8d02b09aef425df36904441fa224419a2e Mon Sep 17 00:00:00 2001 From: spaceface Date: Sun, 24 Jan 2021 22:11:17 +0100 Subject: [PATCH] checker: add mutability checks for interface fields; add tests (#8312) --- vlib/v/checker/checker.v | 13 ++++++++++-- .../tests/immutable_interface_field.out | 7 +++++++ .../tests/immutable_interface_field.vv | 16 ++++++++++++++ .../tests/unimplemented_interface_h.out | 6 ++++++ .../tests/unimplemented_interface_h.vv | 10 +++++++++ .../tests/unimplemented_interface_i.out | 6 ++++++ .../tests/unimplemented_interface_i.vv | 12 +++++++++++ .../tests/unimplemented_interface_j.out | 6 ++++++ .../tests/unimplemented_interface_j.vv | 13 ++++++++++++ vlib/v/parser/struct.v | 21 +++++++++++-------- vlib/v/tests/interface_fields_test.v | 1 + 11 files changed, 100 insertions(+), 11 deletions(-) create mode 100644 vlib/v/checker/tests/immutable_interface_field.out create mode 100644 vlib/v/checker/tests/immutable_interface_field.vv create mode 100644 vlib/v/checker/tests/unimplemented_interface_h.out create mode 100644 vlib/v/checker/tests/unimplemented_interface_h.vv create mode 100644 vlib/v/checker/tests/unimplemented_interface_i.out create mode 100644 vlib/v/checker/tests/unimplemented_interface_i.vv create mode 100644 vlib/v/checker/tests/unimplemented_interface_j.out create mode 100644 vlib/v/checker/tests/unimplemented_interface_j.vv diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 903d76fe76..806b8a8a6b 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1147,13 +1147,18 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) { } } .interface_ { - // TODO: mutability checks on interface fields? interface_info := typ_sym.info as table.Interface - interface_info.find_field(expr.field_name) or { + mut field_info := interface_info.find_field(expr.field_name) or { type_str := c.table.type_to_str(expr.expr_type) c.error('unknown field `${type_str}.$expr.field_name`', expr.pos) return '', pos } + if !field_info.is_mut { + type_str := c.table.type_to_str(expr.expr_type) + c.error('field `$expr.field_name` of interface `$type_str` is immutable', + expr.pos) + } + c.fail_if_immutable(expr.expr) } .array, .string { // This should only happen in `builtin` @@ -1942,6 +1947,10 @@ fn (mut c Checker) type_implements(typ table.Type, inter_typ table.Type, pos tok c.error('`$styp` incorrectly implements field `$ifield.name` of interface `$inter_sym.name`, expected `$exp`, got `$got`', pos) return false + } else if ifield.is_mut && !(field.is_mut || field.is_global) { + c.error('`$styp` incorrectly implements interface `$inter_sym.name`, field `$ifield.name` must be mutable', + pos) + return false } continue } diff --git a/vlib/v/checker/tests/immutable_interface_field.out b/vlib/v/checker/tests/immutable_interface_field.out new file mode 100644 index 0000000000..0e421afb34 --- /dev/null +++ b/vlib/v/checker/tests/immutable_interface_field.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/immutable_interface_field.vv:10:4: error: field `i1` of interface `&Bbb` is immutable + 8 | + 9 | fn mutate_interface(mut b Bbb) { + 10 | b.i1 = 2 + | ~~ + 11 | } + 12 | \ No newline at end of file diff --git a/vlib/v/checker/tests/immutable_interface_field.vv b/vlib/v/checker/tests/immutable_interface_field.vv new file mode 100644 index 0000000000..c3aabefe9c --- /dev/null +++ b/vlib/v/checker/tests/immutable_interface_field.vv @@ -0,0 +1,16 @@ +struct Aaa { + i1 int +} + +interface Bbb { + i1 int +} + +fn mutate_interface(mut b Bbb) { + b.i1 = 2 +} + +fn main() { + mut a := Aaa{1} + mutate_interface(mut a) +} diff --git a/vlib/v/checker/tests/unimplemented_interface_h.out b/vlib/v/checker/tests/unimplemented_interface_h.out new file mode 100644 index 0000000000..0731050373 --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_h.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unimplemented_interface_h.vv:9:13: error: `Cat` doesn't implement field `name` of interface `Animal` + 7 | fn main() { + 8 | mut animals := []Animal{} + 9 | animals << Cat{} + | ~~~~~ + 10 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/unimplemented_interface_h.vv b/vlib/v/checker/tests/unimplemented_interface_h.vv new file mode 100644 index 0000000000..cd008815e3 --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_h.vv @@ -0,0 +1,10 @@ +interface Animal { + name string +} + +struct Cat {} + +fn main() { + mut animals := []Animal{} + animals << Cat{} +} diff --git a/vlib/v/checker/tests/unimplemented_interface_i.out b/vlib/v/checker/tests/unimplemented_interface_i.out new file mode 100644 index 0000000000..073e248772 --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_i.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unimplemented_interface_i.vv:11:13: error: `Cat` incorrectly implements field `name` of interface `Animal`, expected `string`, got `int` + 9 | fn main() { + 10 | mut animals := []Animal{} + 11 | animals << Cat{} + | ~~~~~ + 12 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/unimplemented_interface_i.vv b/vlib/v/checker/tests/unimplemented_interface_i.vv new file mode 100644 index 0000000000..4c50f0ec1b --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_i.vv @@ -0,0 +1,12 @@ +interface Animal { + name string +} + +struct Cat { + name int +} + +fn main() { + mut animals := []Animal{} + animals << Cat{} +} diff --git a/vlib/v/checker/tests/unimplemented_interface_j.out b/vlib/v/checker/tests/unimplemented_interface_j.out new file mode 100644 index 0000000000..4c85238c5d --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_j.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/unimplemented_interface_j.vv:12:13: error: `Cat` incorrectly implements interface `Animal`, field `name` must be mutable + 10 | fn main() { + 11 | mut animals := []Animal{} + 12 | animals << Cat{} + | ~~~~~ + 13 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/unimplemented_interface_j.vv b/vlib/v/checker/tests/unimplemented_interface_j.vv new file mode 100644 index 0000000000..f54dab311e --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_j.vv @@ -0,0 +1,13 @@ +interface Animal { +mut: + name string +} + +struct Cat { + name string +} + +fn main() { + mut animals := []Animal{} + animals << Cat{} +} diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 5fe8ed0108..2f7da7e4c8 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -460,17 +460,17 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { mut methods := []ast.FnDecl{cap: 20} mut is_mut := false for p.tok.kind != .rcbr && p.tok.kind != .eof { + if p.tok.kind == .key_mut { + if is_mut { + p.error_with_pos('redefinition of `mut` section', p.tok.position()) + return {} + } + p.next() + p.check(.colon) + is_mut = true + } if p.peek_tok.kind == .lpar { ts = p.table.get_type_symbol(typ) // removing causes memory bug visible by `v -silent test-fmt` - if p.tok.kind == .key_mut { - if is_mut { - p.error_with_pos('redefinition of `mut` section', p.tok.position()) - return {} - } - p.next() - p.check(.colon) - is_mut = true - } method_start_pos := p.tok.position() line_nr := p.tok.line_nr name := p.check_name() @@ -538,11 +538,14 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { type_pos: type_pos typ: field_typ comments: comments + is_public: true } mut info := ts.info as table.Interface info.fields << table.Field{ name: field_name typ: field_typ + is_pub: true + is_mut: is_mut } ts.info = info } diff --git a/vlib/v/tests/interface_fields_test.v b/vlib/v/tests/interface_fields_test.v index 102b8fcac4..4abe215576 100644 --- a/vlib/v/tests/interface_fields_test.v +++ b/vlib/v/tests/interface_fields_test.v @@ -1,4 +1,5 @@ interface Animal { +mut: breed string }