checker: add an interface check for mutability, fixes #1081, fixes #7038 (#11963)

pull/12153/head
Alexander Ivanov 2021-10-11 15:41:31 +03:00 committed by GitHub
parent d0c961ebc0
commit 0386f2bbea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 219 additions and 92 deletions

View File

@ -336,7 +336,7 @@ jobs:
- name: Build V UI examples
run: |
git clone --depth 1 https://github.com/vlang/ui
git clone --depth 1 https://github.com/vlang/ui
cd ui
mkdir -p ~/.vmodules
ln -s $(pwd) ~/.vmodules/ui

View File

@ -20,7 +20,8 @@ jobs:
- name: Build V
run: make
- name: Clone current Vinix
run: git clone https://github.com/vlang/vinix.git --depth=1
run: |
git clone https://github.com/vlang/vinix.git --depth 1
- name: Clone current mlibc
run: git clone https://github.com/managarm/mlibc.git --depth=1
- name: Patch mlibc for Vinix

View File

@ -39,9 +39,9 @@ fn example_with_cancel() {
// The callers of gen need to cancel the context once
// they are done consuming generated integers not to leak
// the internal routine started by gen.
gen := fn (ctx context.Context) chan int {
gen := fn (mut ctx context.Context) chan int {
dst := chan int{}
go fn (ctx context.Context, dst chan int) {
go fn (mut ctx context.Context, dst chan int) {
mut v := 0
ch := ctx.done()
for {
@ -55,16 +55,20 @@ fn example_with_cancel() {
}
}
}
}(ctx, dst)
}(mut ctx, dst)
return dst
}
ctx, cancel := context.with_cancel(context.background())
mut background := context.background()
mut b := &background
mut ctx, cancel := context.with_cancel(mut b)
defer {
cancel()
}
ch := gen(ctx)
mut mut_ctx := ctx
mut ctx2 := &mut_ctx
ch := gen(mut ctx2)
for i in 0 .. 5 {
v := <-ch
assert i == v
@ -87,7 +91,9 @@ const (
// function that it should abandon its work as soon as it gets to it.
fn example_with_deadline() {
dur := time.now().add(short_duration)
ctx, cancel := context.with_deadline(context.background(), dur)
mut background := context.background()
mut b := &background
mut ctx, cancel := context.with_deadline(mut b, dur)
defer {
// Even though ctx will be expired, it is good practice to call its
@ -122,7 +128,9 @@ const (
fn example_with_timeout() {
// Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses.
ctx, cancel := context.with_timeout(context.background(), short_duration)
mut background := context.background()
mut b := &background
mut ctx, cancel := context.with_timeout(mut b, short_duration)
defer {
cancel()
}

View File

@ -43,23 +43,6 @@ pub interface Context {
// should be canceled. deadline returns none when no deadline is
// set. Successive calls to deadline return the same results.
deadline() ?time.Time
// done returns a channel that's closed when work done on behalf of this
// context should be canceled. done may return nil if this context can
// never be canceled. Successive calls to done return the same value.
// The close of the done channel may happen asynchronously,
// after the cancel function returns.
//
// with_cancel arranges for done to be closed when cancel is called;
// with_deadline arranges for done to be closed when the deadline
// expires; with_timeout arranges for done to be closed when the timeout
// elapses.
done() chan int
// If done is not yet closed, err returns nil.
// If done is closed, err returns a non-nil error explaining why:
// canceled if the context was canceled
// or deadline_exceeded if the context's deadline passed.
// After err returns a non-nil error, successive calls to err return the same error.
err() IError
// Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
@ -76,6 +59,24 @@ pub interface Context {
// collisions.
value(key Key) ?Any
str() string
// done returns a channel that's closed when work done on behalf of this
// context should be canceled. done may return nil if this context can
// never be canceled. Successive calls to done return the same value.
// The close of the done channel may happen asynchronously,
// after the cancel function returns.
//
// with_cancel arranges for done to be closed when cancel is called;
// with_deadline arranges for done to be closed when the deadline
// expires; with_timeout arranges for done to be closed when the timeout
// elapses.
mut:
done() chan int
// If done is not yet closed, err returns nil.
// If done is closed, err returns a non-nil error explaining why:
// canceled if the context was canceled
// or deadline_exceeded if the context's deadline passed.
// After err returns a non-nil error, successive calls to err return the same error.
err() IError
}
// background returns an empty Context. It is never canceled, has no

View File

@ -12,12 +12,13 @@ pub type CancelFn = fn ()
pub interface Canceler {
id string
mut:
cancel(remove_from_parent bool, err IError)
done() chan int
}
[deprecated]
pub fn cancel(ctx Context) {
pub fn cancel(mut ctx Context) {
match mut ctx {
CancelContext {
ctx.cancel(true, canceled)
@ -47,9 +48,9 @@ mut:
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
pub fn with_cancel(parent Context) (Context, CancelFn) {
pub fn with_cancel(mut parent Context) (Context, CancelFn) {
mut c := new_cancel_context(parent)
propagate_cancel(parent, c)
propagate_cancel(mut parent, mut c)
return Context(c), fn [mut c] () {
c.cancel(true, canceled)
}
@ -116,18 +117,20 @@ fn (mut ctx CancelContext) cancel(remove_from_parent bool, err IError) {
for _, child in ctx.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
mut c := child
c.cancel(false, err)
}
ctx.children = map[string]Canceler{}
ctx.mutex.unlock()
if remove_from_parent {
remove_child(ctx.context, ctx)
mut cc := &ctx.context
remove_child(mut cc, ctx)
}
}
fn propagate_cancel(parent Context, child Canceler) {
fn propagate_cancel(mut parent Context, mut child Canceler) {
done := parent.done()
select {
_ := <-done {
@ -136,15 +139,15 @@ fn propagate_cancel(parent Context, child Canceler) {
return
}
}
mut p := parent_cancel_context(parent) or {
go fn (parent Context, child Canceler) {
mut p := parent_cancel_context(mut parent) or {
go fn (mut parent Context, mut child Canceler) {
pdone := parent.done()
select {
_ := <-pdone {
child.cancel(false, parent.err())
}
}
}(parent, child)
}(mut parent, mut child)
return
}
@ -162,7 +165,7 @@ fn propagate_cancel(parent Context, child Canceler) {
// parent.done() matches that CancelContext. (If not, the CancelContext
// has been wrapped in a custom implementation providing a
// different done channel, in which case we should not bypass it.)
fn parent_cancel_context(parent Context) ?&CancelContext {
fn parent_cancel_context(mut parent Context) ?&CancelContext {
done := parent.done()
if done.closed {
return none
@ -181,7 +184,7 @@ fn parent_cancel_context(parent Context) ?&CancelContext {
}
// remove_child removes a context from its parent.
fn remove_child(parent Context, child Canceler) {
mut p := parent_cancel_context(parent) or { return }
fn remove_child(mut parent Context, child Canceler) {
mut p := parent_cancel_context(mut parent) or { return }
p.children.delete(child.id)
}

View File

@ -10,9 +10,9 @@ fn test_with_cancel() {
// The callers of gen need to cancel the context once
// they are done consuming generated integers not to leak
// the internal routine started by gen.
gen := fn (ctx context.Context) chan int {
gen := fn (mut ctx context.Context) chan int {
dst := chan int{}
go fn (ctx context.Context, dst chan int) {
go fn (mut ctx context.Context, dst chan int) {
mut v := 0
ch := ctx.done()
for {
@ -26,16 +26,20 @@ fn test_with_cancel() {
}
}
}
}(ctx, dst)
}(mut ctx, dst)
return dst
}
ctx, cancel := context.with_cancel(context.background())
mut background := context.background()
mut b := &background
mut ctx, cancel := context.with_cancel(mut b)
defer {
cancel()
}
ch := gen(ctx)
mut mut_ctx := ctx
mut ctx2 := &mut_ctx
ch := gen(mut ctx2)
for i in 0 .. 5 {
v := <-ch
assert i == v

View File

@ -26,12 +26,12 @@ mut:
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
pub fn with_deadline(parent Context, d time.Time) (Context, CancelFn) {
pub fn with_deadline(mut parent Context, d time.Time) (Context, CancelFn) {
id := rand.uuid_v4()
if cur := parent.deadline() {
if cur < d {
// The current deadline is already sooner than the new one.
return with_cancel(parent)
return with_cancel(mut parent)
}
}
cancel_ctx := new_cancel_context(parent)
@ -40,7 +40,7 @@ pub fn with_deadline(parent Context, d time.Time) (Context, CancelFn) {
deadline: d
id: id
}
propagate_cancel(parent, ctx)
propagate_cancel(mut parent, mut ctx)
dur := d - time.now()
if dur.nanoseconds() <= 0 {
ctx.cancel(true, deadline_exceeded) // deadline has already passed
@ -64,8 +64,8 @@ pub fn with_deadline(parent Context, d time.Time) (Context, CancelFn) {
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete
pub fn with_timeout(parent Context, timeout time.Duration) (Context, CancelFn) {
return with_deadline(parent, time.now().add(timeout))
pub fn with_timeout(mut parent Context, timeout time.Duration) (Context, CancelFn) {
return with_deadline(mut parent, time.now().add(timeout))
}
pub fn (ctx &TimerContext) deadline() ?time.Time {
@ -88,7 +88,8 @@ pub fn (mut ctx TimerContext) cancel(remove_from_parent bool, err IError) {
ctx.cancel_ctx.cancel(false, err)
if remove_from_parent {
// Remove this TimerContext from its parent CancelContext's children.
remove_child(ctx.cancel_ctx.context, ctx)
mut cc := &ctx.cancel_ctx.context
remove_child(mut cc, ctx)
}
}

View File

@ -10,7 +10,9 @@ const (
// function that it should abandon its work as soon as it gets to it.
fn test_with_deadline() {
dur := time.now().add(short_duration)
ctx, cancel := context.with_deadline(context.background(), dur)
mut background := context.background()
mut b := &background
mut ctx, cancel := context.with_deadline(mut b, dur)
defer {
// Even though ctx will be expired, it is good practice to call its
@ -33,7 +35,9 @@ fn test_with_deadline() {
fn test_with_timeout() {
// Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses.
ctx, cancel := context.with_timeout(context.background(), short_duration)
mut background := context.background()
mut b := &background
mut ctx, cancel := context.with_timeout(mut b, short_duration)
defer {
cancel()
}

View File

@ -37,11 +37,11 @@ pub fn (ctx &ValueContext) deadline() ?time.Time {
return ctx.context.deadline()
}
pub fn (ctx &ValueContext) done() chan int {
pub fn (mut ctx ValueContext) done() chan int {
return ctx.context.done()
}
pub fn (ctx &ValueContext) err() IError {
pub fn (mut ctx ValueContext) err() IError {
return ctx.context.err()
}

View File

@ -4,7 +4,7 @@ const (
buf_max_len = 1024
)
pub fn cp(src Reader, mut dst Writer) ? {
pub fn cp(mut src Reader, mut dst Writer) ? {
mut buf := []byte{len: io.buf_max_len}
for {
len := src.read(mut buf) or { break }

View File

@ -8,6 +8,6 @@ fn test_cp() ? {
}
mut r := io.new_buffered_reader(reader: f)
mut stdout := os.stdout()
io.cp(r, mut stdout) ?
io.cp(mut r, mut stdout) ?
assert true
}

View File

@ -30,12 +30,12 @@ fn (mut w Writ) write(buf []byte) ?int {
}
fn test_copy() {
src := Buf{
mut src := Buf{
bytes: 'abcdefghij'.repeat(10).bytes()
}
mut dst := Writ{
bytes: []byte{}
}
io.cp(src, mut dst) or { assert false }
io.cp(mut src, mut dst) or { assert false }
assert dst.bytes == src.bytes
}

View File

@ -6,6 +6,7 @@ pub interface Reader {
// them into buf.
// A type that implements this should return
// `none` on end of stream (EOF) instead of just returning 0
mut:
read(mut buf []byte) ?int
}
@ -17,14 +18,15 @@ const (
// ReadAllConfig allows options to be passed for the behaviour
// of read_all
pub struct ReadAllConfig {
reader Reader
read_to_end_of_stream bool
mut:
reader Reader
}
// read_all reads all bytes from a reader until either a 0 length read
// or if read_to_end_of_stream is true then the end of the stream (`none`)
pub fn read_all(config ReadAllConfig) ?[]byte {
r := config.reader
mut r := config.reader
read_till_eof := config.read_to_end_of_stream
mut b := []byte{len: io.read_all_len}
@ -44,7 +46,7 @@ pub fn read_all(config ReadAllConfig) ?[]byte {
// read_any reads any available bytes from a reader
// (until the reader returns a read of 0 length)
pub fn read_any(r Reader) ?[]byte {
pub fn read_any(mut r Reader) ?[]byte {
mut b := []byte{len: io.read_all_len}
mut read := 0
for {

View File

@ -3,6 +3,7 @@ module io
// ReaderWriter represents a stream that can be read from and wrote to
pub interface ReaderWriter {
// from Reader
mut:
read(mut buf []byte) ?int
// from Writer
write(buf []byte) ?int
@ -11,8 +12,8 @@ pub interface ReaderWriter {
// ReaderWriterImpl is a ReaderWriter that can be made from
// a seperate reader and writer (see fn make_readerwriter)
struct ReaderWriterImpl {
r Reader
mut:
r Reader
w Writer
}

View File

@ -2,6 +2,7 @@ module io
// Writer represents a stream of data that can be wrote to
pub interface Writer {
mut:
write(buf []byte) ?int
}

View File

@ -18,6 +18,7 @@ pub enum ServerStatus {
}
interface Handler {
mut:
handle(Request) Response
}
@ -80,7 +81,7 @@ pub fn (s &Server) status() ServerStatus {
return s.state
}
fn (s &Server) parse_and_respond(mut conn net.TcpConn) {
fn (mut s Server) parse_and_respond(mut conn net.TcpConn) {
defer {
conn.close() or { eprintln('close() failed: $err') }
}

View File

@ -48,7 +48,7 @@ pub fn (mut c TcpConn) close() ? {
c.sock.close() ?
}
pub fn (mut c TcpConn) read_ptr(buf_ptr &byte, len int) ?int {
pub fn (c TcpConn) read_ptr(buf_ptr &byte, len int) ?int {
mut res := wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0)) ?
$if trace_tcp ? {
eprintln('<<< TcpConn.read_ptr | c.sock.handle: $c.sock.handle | buf_ptr: ${ptr_str(buf_ptr)} len: $len | res: $res')
@ -80,7 +80,7 @@ pub fn (mut c TcpConn) read_ptr(buf_ptr &byte, len int) ?int {
return none
}
pub fn (mut c TcpConn) read(mut buf []byte) ?int {
pub fn (c TcpConn) read(mut buf []byte) ?int {
return c.read_ptr(buf.data, buf.len)
}
@ -169,7 +169,7 @@ pub fn (mut c TcpConn) set_write_timeout(t time.Duration) {
}
[inline]
pub fn (mut c TcpConn) wait_for_read() ? {
pub fn (c TcpConn) wait_for_read() ? {
return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout)
}

View File

@ -4,6 +4,7 @@ import time
// Backends should provide a `new() ?FdNotifier` function
pub interface FdNotifier {
mut:
add(fd int, events FdEventType, conf ...FdConfigFlags) ?
modify(fd int, events FdEventType, conf ...FdConfigFlags) ?
remove(fd int) ?

View File

@ -27,8 +27,9 @@ fn test_level_trigger() ? {
notifier.add(reader, .read) ?
os.fd_write(writer, 'foobar')
check_read_event(notifier, reader, 'foo')
check_read_event(notifier, reader, 'bar')
mut n := &notifier
check_read_event(mut n, reader, 'foo')
check_read_event(mut n, reader, 'bar')
assert notifier.wait(0).len == 0
}
@ -46,8 +47,10 @@ fn test_edge_trigger() ? {
}
notifier.add(reader, .read, .edge_trigger) ?
mut n := &notifier
os.fd_write(writer, 'foobar')
check_read_event(notifier, reader, 'foo')
check_read_event(mut n, reader, 'foo')
assert notifier.wait(0).len == 0
@ -71,15 +74,17 @@ fn test_one_shot() ? {
}
notifier.add(reader, .read, .one_shot) ?
mut n := &notifier
os.fd_write(writer, 'foobar')
check_read_event(notifier, reader, 'foo')
check_read_event(mut n, reader, 'foo')
os.fd_write(writer, 'baz')
assert notifier.wait(0).len == 0
// rearm
notifier.modify(reader, .read) ?
check_read_event(notifier, reader, 'barbaz')
check_read_event(mut n, reader, 'barbaz')
}
}
@ -148,7 +153,7 @@ fn test_remove() ? {
}
}
fn check_read_event(notifier notify.FdNotifier, reader_fd int, expected string) {
fn check_read_event(mut notifier notify.FdNotifier, reader_fd int, expected string) {
events := notifier.wait(0)
assert events.len == 1
assert events[0].fd == reader_fd

View File

@ -216,7 +216,7 @@ fn (db DB) handle_error_or_result(res voidptr, elabel string) ?[]Row {
// copy_expert execute COPY commands
// https://www.postgresql.org/docs/9.5/libpq-copy.html
pub fn (db DB) copy_expert(query string, file io.ReaderWriter) ?int {
pub fn (db DB) copy_expert(query string, mut file io.ReaderWriter) ?int {
res := C.PQexec(db.conn, query.str)
status := C.PQresultStatus(res)

View File

@ -10,6 +10,7 @@ import rand.wyrand
// modules's API. It defines all the methods that a PRNG (in the vlib or custom made) must
// implement in order to ensure that _all_ functions can be used with the generator.
pub interface PRNG {
mut:
seed(seed_data []u32)
u32() u32
u64() u64

View File

@ -260,11 +260,23 @@ pub fn (t &Table) is_same_method(f &Fn, func &Fn) string {
if f.params.len != func.params.len {
return 'expected $f.params.len parameter(s), not $func.params.len'
}
for i in 1 .. f.params.len {
if f.params[i].typ != func.params[i].typ {
// interface name() other mut name() : error
for i in 0 .. f.params.len {
// don't check receiver for `.typ`
has_unexpected_type := i > 0 && f.params[i].typ != func.params[i].typ
has_unexpected_mutability := !f.params[i].is_mut && func.params[i].is_mut
if has_unexpected_type || has_unexpected_mutability {
exps := t.type_to_str(f.params[i].typ)
gots := t.type_to_str(func.params[i].typ)
return 'expected `$exps`, not `$gots` for parameter $i'
if has_unexpected_type {
return 'expected `$exps`, not `$gots` for parameter $i'
} else {
return 'expected `$exps` which is immutable, not `mut $gots`'
}
}
}
return ''

View File

@ -4,6 +4,7 @@ import v.ast
// Visitor defines a visit method which is invoked by the walker in each node it encounters.
pub interface Visitor {
mut:
visit(node &ast.Node) ?
}
@ -24,14 +25,15 @@ pub fn (i &Inspector) visit(node &ast.Node) ? {
// inspect traverses and checks the AST node on a depth-first order and based on the data given
pub fn inspect(node &ast.Node, data voidptr, inspector_callback InspectorFn) {
walk(Inspector{inspector_callback, data}, node)
mut inspector := Inspector{inspector_callback, data}
walk(mut inspector, node)
}
// walk traverses the AST using the given visitor
pub fn walk(visitor Visitor, node &ast.Node) {
pub fn walk(mut visitor Visitor, node &ast.Node) {
visitor.visit(node) or { return }
children := node.children()
for child_node in children {
walk(visitor, &child_node)
walk(mut visitor, &child_node)
}
}

View File

@ -35,7 +35,7 @@ struct Foo {
mut nbo := NodeByOffset{
pos: 13
}
walker.walk(nbo, file)
walker.walk(mut nbo, file)
assert nbo.node is ast.Stmt
stmt := nbo.node as ast.Stmt
assert stmt is ast.StructDecl

View File

@ -16,7 +16,7 @@ pub fn show(mut table ast.Table, pref &pref.Preferences, ast_files []&ast.File)
// Node14 [shape="box",label="PrivateBase",URL="$classPrivateBase.html"];
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
for afile in ast_files {
walker.walk(mapper, afile)
walker.walk(mut mapper, afile)
}
mapper.dg.finish()
}

View File

@ -3355,8 +3355,10 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to
}
msg := c.table.is_same_method(imethod, method)
if msg.len > 0 {
sig := c.table.fn_signature(imethod, skip_receiver: true)
sig := c.table.fn_signature(imethod, skip_receiver: false)
typ_sig := c.table.fn_signature(method, skip_receiver: false)
c.add_error_detail('$inter_sym.name has `$sig`')
c.add_error_detail(' $typ_sym.name has `$typ_sig`')
c.error('`$styp` incorrectly implements method `$imethod.name` of interface `$inter_sym.name`: $msg',
pos)
return false

View File

@ -4,4 +4,5 @@ vlib/v/checker/tests/unimplemented_interface_b.vv:13:6: error: `Cat` incorrectly
13 | foo(c)
| ^
14 | }
Details: main.Animal has `name() string`
Details: main.Animal has `fn name(x main.Animal) string`
main.Cat has `fn name(c main.Cat)`

View File

@ -1,7 +1,8 @@
vlib/v/checker/tests/unimplemented_interface_c.vv:12:6: error: `Cat` incorrectly implements method `name` of interface `Animal`: expected 1 parameter(s), not 2
10 |
10 |
11 | fn main() {
12 | foo(Cat{})
| ~~~~~
13 | }
Details: main.Animal has `name()`
Details: main.Animal has `fn name(x main.Animal)`
main.Cat has `fn name(c main.Cat, s string)`

View File

@ -1,7 +1,8 @@
vlib/v/checker/tests/unimplemented_interface_d.vv:12:6: error: `Cat` incorrectly implements method `speak` of interface `Animal`: expected 2 parameter(s), not 1
10 |
10 |
11 | fn main() {
12 | foo(Cat{})
| ~~~~~
13 | }
Details: main.Animal has `speak(s string)`
Details: main.Animal has `fn speak(x main.Animal, s string)`
main.Cat has `fn speak(c main.Cat)`

View File

@ -1,15 +1,17 @@
vlib/v/checker/tests/unimplemented_interface_e.vv:12:6: error: `Cat` incorrectly implements method `speak` of interface `Animal`: expected `string`, not `&string` for parameter 1
10 |
10 |
11 | fn main() {
12 | foo(Cat{})
| ~~~~~
13 | _ = Animal(Cat{})
14 | }
Details: main.Animal has `speak(s string)`
Details: main.Animal has `fn speak(x main.Animal, s string)`
main.Cat has `fn speak(c main.Cat, s &string)`
vlib/v/checker/tests/unimplemented_interface_e.vv:13:6: error: `Cat` incorrectly implements method `speak` of interface `Animal`: expected `string`, not `&string` for parameter 1
11 | fn main() {
12 | foo(Cat{})
13 | _ = Animal(Cat{})
| ~~~~~~~~~~~~~
14 | }
Details: main.Animal has `speak(s string)`
Details: main.Animal has `fn speak(x main.Animal, s string)`
main.Cat has `fn speak(c main.Cat, s &string)`

View File

@ -4,4 +4,5 @@ vlib/v/checker/tests/unimplemented_interface_f.vv:11:13: error: `Cat` incorrectl
11 | animals << Cat{}
| ~~~~~
12 | }
Details: main.Animal has `speak(s string)`
Details: main.Animal has `fn speak(x main.Animal, s string)`
main.Cat has `fn speak(c main.Cat)`

View File

@ -0,0 +1,9 @@
vlib/v/parser/tests/interface_mutability_receiver.vv:23:18: error: `Doggo` incorrectly implements method `set_name` of interface `Animal`: expected `Animal` which is immutable, not `mut &Doggo`
21 | dog := Doggo{'Doggo'}
22 | println(dog.name)
23 | set_animal_name(dog, 'Pupper')
| ~~~
24 | println(dog.name)
25 | }
Details: main.Animal has `fn set_name(x main.Animal, name string)`
main.Doggo has `fn set_name(mut d main.Doggo, name string)`

View File

@ -0,0 +1,25 @@
// fixes https://github.com/vlang/v/issues/1081 and https://github.com/vlang/v/issues/7338, code by https://github.com/nedpals
// copied from https://github.com/vlang/v/issues/7338
struct Doggo {
pub mut:
name string
}
fn (mut d Doggo) set_name(name string) {
d.name = name
}
interface Animal {
set_name(name string)
}
fn set_animal_name(a Animal, name string) {
a.set_name(name)
}
fn main() {
dog := Doggo{'Doggo'}
println(dog.name)
set_animal_name(dog, 'Pupper')
println(dog.name)
}

View File

@ -66,6 +66,7 @@ fn test_extract_basic() {
//////
interface Iterator<T> {
mut:
next() ?T
}

View File

@ -1,4 +1,5 @@
interface Iter<T> {
mut:
next() ?T
}
@ -24,7 +25,7 @@ fn iter<T>(arr []T) Iter<T> {
}
fn test_generics_fn_return_generic_interface() {
x := iter([1, 2, 3])
mut x := iter([1, 2, 3])
println(x)
y := x.next() or { 0 }
println(y)

View File

@ -1,4 +1,5 @@
interface Iter<T> {
mut:
next() ?T
}

View File

@ -1,4 +1,5 @@
interface Iter<T> {
mut:
next() ?T
}
@ -39,7 +40,7 @@ fn (mut it SkipIter<T>) next<T>() ?T {
}
fn test_generics_interface_with_multi_generic_structs() {
x := Iter<int>(ArrIter<int>{
mut x := Iter<int>(ArrIter<int>{
data: [1, 2, 3]
})
println(x)

View File

@ -1,4 +1,5 @@
interface Iter<T, U> {
mut:
next() ?(T, U)
}

View File

@ -9,11 +9,13 @@ interface WalkerTalker {
interface Talker {
nspeeches int
mut:
talk(msg string)
}
interface Walker {
nsteps int
mut:
walk(newx int, newy int)
}

View File

@ -196,6 +196,7 @@ interface Speaker2 {
name() string
speak()
return_speaker() Speaker2
mut:
return_speaker2() ?Speaker2
}
@ -250,6 +251,7 @@ interface Animal {
name() string
name_detailed(pet_name string) string
speak(s string)
mut:
set_breed(s string)
}
@ -327,6 +329,31 @@ mut:
my_field int
}
// the opposite example of interface_mutability_receiver.vv
// related to https://github.com/vlang/v/issues/1081 and https://github.com/vlang/v/issues/7338
// test example code by https://github.com/nedpals copied and adapted from https://github.com/vlang/v/issues/7338
// we accept immutable get_name even if the interface specified `mut` as it is consistent with function param behavior
struct Dog2 {
pub mut:
name string
}
fn (d Dog2) get_name() string {
return d.name
}
// mut get_name might be a bad example
// we try to show that an interface can be more liberal in mut than the implementor,
// while our error check is for the opposite case
interface Animal2 {
mut:
get_name() string
}
fn get_animal_name(mut a Animal2) string {
return a.get_name()
}
fn main() {
mut aa := AA{}
mut ii := II(aa)
@ -335,4 +362,7 @@ fn main() {
assert ii.my_field == 123
ii.my_field = 1234
assert aa.my_field == 1234
mut dog := Dog2{'Doggo'}
println(dog.name)
println(get_animal_name(mut dog))
}