diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 147b4ffcd9480f..ca7418ccc93feb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/vinix-kernel.yml b/.github/workflows/vinix-kernel.yml index 593a40c58dc649..b6336f90327acb 100644 --- a/.github/workflows/vinix-kernel.yml +++ b/.github/workflows/vinix-kernel.yml @@ -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 diff --git a/vlib/context/README.md b/vlib/context/README.md index 43f640399d18f7..193badc40912d5 100644 --- a/vlib/context/README.md +++ b/vlib/context/README.md @@ -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() } diff --git a/vlib/context/_context.v b/vlib/context/_context.v index 61e820cd9276bb..8750fcc348e00f 100644 --- a/vlib/context/_context.v +++ b/vlib/context/_context.v @@ -43,6 +43,22 @@ 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 + // 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. + // + // Use context values only for request-scoped data that transits + // processes and API boundaries, not for passing optional parameters to + // functions. + // + // A key identifies a specific value in a Context. Functions that wish + // to store values in Context typically allocate a key in a global + // variable then use that key as the argument to context.with_value and + // Context.Value. A key can be any type that supports equality; + // packages should define keys as an unexported type to avoid + // 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. @@ -53,6 +69,7 @@ pub interface Context { // 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: @@ -60,22 +77,6 @@ pub interface Context { // 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. - // - // Use context values only for request-scoped data that transits - // processes and API boundaries, not for passing optional parameters to - // functions. - // - // A key identifies a specific value in a Context. Functions that wish - // to store values in Context typically allocate a key in a global - // variable then use that key as the argument to context.with_value and - // Context.Value. A key can be any type that supports equality; - // packages should define keys as an unexported type to avoid - // collisions. - value(key Key) ?Any - str() string } // background returns an empty Context. It is never canceled, has no diff --git a/vlib/context/cancel.v b/vlib/context/cancel.v index 497f17c89b707e..323df2e92dcbed 100644 --- a/vlib/context/cancel.v +++ b/vlib/context/cancel.v @@ -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) } diff --git a/vlib/context/cancel_test.v b/vlib/context/cancel_test.v index 314e2da5c7e8e3..6e9baad72c6a98 100644 --- a/vlib/context/cancel_test.v +++ b/vlib/context/cancel_test.v @@ -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 diff --git a/vlib/context/deadline.v b/vlib/context/deadline.v index a402277fc2932e..10f6c68f5f2202 100644 --- a/vlib/context/deadline.v +++ b/vlib/context/deadline.v @@ -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) } } diff --git a/vlib/context/deadline_test.v b/vlib/context/deadline_test.v index 92dd46483086a5..2763c1cb809ca6 100644 --- a/vlib/context/deadline_test.v +++ b/vlib/context/deadline_test.v @@ -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() } diff --git a/vlib/context/value.v b/vlib/context/value.v index f4a3bcd28949ec..0b5599c88be191 100644 --- a/vlib/context/value.v +++ b/vlib/context/value.v @@ -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() } diff --git a/vlib/io/io.v b/vlib/io/io.v index c07e074b7161e4..7164bd7c36ea9f 100644 --- a/vlib/io/io.v +++ b/vlib/io/io.v @@ -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 } diff --git a/vlib/io/io_cp_test.v b/vlib/io/io_cp_test.v index 21c743a189349b..0a6863a40ad5c0 100644 --- a/vlib/io/io_cp_test.v +++ b/vlib/io/io_cp_test.v @@ -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 } diff --git a/vlib/io/io_test.v b/vlib/io/io_test.v index fca17c2d0398c3..20c949eca7a1fb 100644 --- a/vlib/io/io_test.v +++ b/vlib/io/io_test.v @@ -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 } diff --git a/vlib/io/reader.v b/vlib/io/reader.v index 61f566f46c8c79..c555cfadd5babc 100644 --- a/vlib/io/reader.v +++ b/vlib/io/reader.v @@ -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 { diff --git a/vlib/io/readerwriter.v b/vlib/io/readerwriter.v index ba44172eade894..5d2de159465ea7 100644 --- a/vlib/io/readerwriter.v +++ b/vlib/io/readerwriter.v @@ -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 } diff --git a/vlib/io/writer.v b/vlib/io/writer.v index 322dd45e63e2e3..7f70b244dca7ff 100644 --- a/vlib/io/writer.v +++ b/vlib/io/writer.v @@ -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 } diff --git a/vlib/net/http/server.v b/vlib/net/http/server.v index 7a9660da7a1ae8..4df665e58bfb1f 100644 --- a/vlib/net/http/server.v +++ b/vlib/net/http/server.v @@ -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') } } diff --git a/vlib/net/tcp.v b/vlib/net/tcp.v index c2a45a2643bb12..efcd58d1fe2261 100644 --- a/vlib/net/tcp.v +++ b/vlib/net/tcp.v @@ -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) } diff --git a/vlib/os/notify/notify.v b/vlib/os/notify/notify.v index b49dad3fdb2610..613e2b4547bc2b 100644 --- a/vlib/os/notify/notify.v +++ b/vlib/os/notify/notify.v @@ -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) ? diff --git a/vlib/os/notify/notify_test.v b/vlib/os/notify/notify_test.v index c5049b7d4027c0..4791b45853c92a 100644 --- a/vlib/os/notify/notify_test.v +++ b/vlib/os/notify/notify_test.v @@ -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 := ¬ifier + 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 := ¬ifier + 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 := ¬ifier + 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 diff --git a/vlib/pg/pg.v b/vlib/pg/pg.v index 321a77593bc336..0956932e6a6bc7 100644 --- a/vlib/pg/pg.v +++ b/vlib/pg/pg.v @@ -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) diff --git a/vlib/rand/rand.v b/vlib/rand/rand.v index cac98b8c743bc7..974dfadb7627a4 100644 --- a/vlib/rand/rand.v +++ b/vlib/rand/rand.v @@ -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 diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 3f77c785bfa8ed..13ca5f3d086fe0 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -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 '' diff --git a/vlib/v/ast/walker/walker.v b/vlib/v/ast/walker/walker.v index 75e6c17799093f..6cd2c103ee3944 100644 --- a/vlib/v/ast/walker/walker.v +++ b/vlib/v/ast/walker/walker.v @@ -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) } } diff --git a/vlib/v/ast/walker/walker_test.v b/vlib/v/ast/walker/walker_test.v index 6d90fe9e5c9fdf..ace681a3a86668 100644 --- a/vlib/v/ast/walker/walker_test.v +++ b/vlib/v/ast/walker/walker_test.v @@ -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 diff --git a/vlib/v/callgraph/callgraph.v b/vlib/v/callgraph/callgraph.v index da57bc7fc23175..8069cd10d3ba52 100644 --- a/vlib/v/callgraph/callgraph.v +++ b/vlib/v/callgraph/callgraph.v @@ -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() } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index eeee14c1b60d5f..563bb0e885e441 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -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 diff --git a/vlib/v/checker/tests/unimplemented_interface_b.out b/vlib/v/checker/tests/unimplemented_interface_b.out index edb00d0eaf7115..27837bdc659579 100644 --- a/vlib/v/checker/tests/unimplemented_interface_b.out +++ b/vlib/v/checker/tests/unimplemented_interface_b.out @@ -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)` \ No newline at end of file diff --git a/vlib/v/checker/tests/unimplemented_interface_c.out b/vlib/v/checker/tests/unimplemented_interface_c.out index 43060d4cfc515d..53392f58ff15be 100644 --- a/vlib/v/checker/tests/unimplemented_interface_c.out +++ b/vlib/v/checker/tests/unimplemented_interface_c.out @@ -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)` diff --git a/vlib/v/checker/tests/unimplemented_interface_d.out b/vlib/v/checker/tests/unimplemented_interface_d.out index 5e39c5eff35472..08ead64b113c1d 100644 --- a/vlib/v/checker/tests/unimplemented_interface_d.out +++ b/vlib/v/checker/tests/unimplemented_interface_d.out @@ -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)` diff --git a/vlib/v/checker/tests/unimplemented_interface_e.out b/vlib/v/checker/tests/unimplemented_interface_e.out index f388cb2d6f1b50..8181dbb4dcdf78 100644 --- a/vlib/v/checker/tests/unimplemented_interface_e.out +++ b/vlib/v/checker/tests/unimplemented_interface_e.out @@ -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)` diff --git a/vlib/v/checker/tests/unimplemented_interface_f.out b/vlib/v/checker/tests/unimplemented_interface_f.out index 3bdc2aca06294b..6747445b8e9122 100644 --- a/vlib/v/checker/tests/unimplemented_interface_f.out +++ b/vlib/v/checker/tests/unimplemented_interface_f.out @@ -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)` diff --git a/vlib/v/parser/tests/interface_mutability_receiver.out b/vlib/v/parser/tests/interface_mutability_receiver.out new file mode 100644 index 00000000000000..67c5aeb5eea1a0 --- /dev/null +++ b/vlib/v/parser/tests/interface_mutability_receiver.out @@ -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)` diff --git a/vlib/v/parser/tests/interface_mutability_receiver.vv b/vlib/v/parser/tests/interface_mutability_receiver.vv new file mode 100644 index 00000000000000..13649d836be35b --- /dev/null +++ b/vlib/v/parser/tests/interface_mutability_receiver.vv @@ -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) +} diff --git a/vlib/v/tests/generic_interface_test.v b/vlib/v/tests/generic_interface_test.v index 059b1af7c72ba2..409a0635864169 100644 --- a/vlib/v/tests/generic_interface_test.v +++ b/vlib/v/tests/generic_interface_test.v @@ -66,6 +66,7 @@ fn test_extract_basic() { ////// interface Iterator { +mut: next() ?T } diff --git a/vlib/v/tests/generics_fn_return_generic_interface_test.v b/vlib/v/tests/generics_fn_return_generic_interface_test.v index b11bff70f3c306..a19a4a2f0049cd 100644 --- a/vlib/v/tests/generics_fn_return_generic_interface_test.v +++ b/vlib/v/tests/generics_fn_return_generic_interface_test.v @@ -1,4 +1,5 @@ interface Iter { +mut: next() ?T } @@ -24,7 +25,7 @@ fn iter(arr []T) Iter { } 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) diff --git a/vlib/v/tests/generics_interface_method_test.v b/vlib/v/tests/generics_interface_method_test.v index bf31883393e7e3..7f2b966339efbc 100644 --- a/vlib/v/tests/generics_interface_method_test.v +++ b/vlib/v/tests/generics_interface_method_test.v @@ -1,4 +1,5 @@ interface Iter { +mut: next() ?T } diff --git a/vlib/v/tests/generics_interface_with_multi_generic_structs_test.v b/vlib/v/tests/generics_interface_with_multi_generic_structs_test.v index 24f27e17bd3670..6f8b8fa9390465 100644 --- a/vlib/v/tests/generics_interface_with_multi_generic_structs_test.v +++ b/vlib/v/tests/generics_interface_with_multi_generic_structs_test.v @@ -1,4 +1,5 @@ interface Iter { +mut: next() ?T } @@ -39,7 +40,7 @@ fn (mut it SkipIter) next() ?T { } fn test_generics_interface_with_multi_generic_structs() { - x := Iter(ArrIter{ + mut x := Iter(ArrIter{ data: [1, 2, 3] }) println(x) diff --git a/vlib/v/tests/generics_interface_with_multi_generic_types_test.v b/vlib/v/tests/generics_interface_with_multi_generic_types_test.v index d6e7f591514cfc..368a0584a4a609 100644 --- a/vlib/v/tests/generics_interface_with_multi_generic_types_test.v +++ b/vlib/v/tests/generics_interface_with_multi_generic_types_test.v @@ -1,4 +1,5 @@ interface Iter { +mut: next() ?(T, U) } diff --git a/vlib/v/tests/interface_embedding_test.v b/vlib/v/tests/interface_embedding_test.v index 5f9052554e1f55..fd4b23ce03d935 100644 --- a/vlib/v/tests/interface_embedding_test.v +++ b/vlib/v/tests/interface_embedding_test.v @@ -9,11 +9,13 @@ interface WalkerTalker { interface Talker { nspeeches int +mut: talk(msg string) } interface Walker { nsteps int +mut: walk(newx int, newy int) } diff --git a/vlib/v/tests/interface_test.v b/vlib/v/tests/interface_test.v index 388fd555ad84a6..ad741359b58438 100644 --- a/vlib/v/tests/interface_test.v +++ b/vlib/v/tests/interface_test.v @@ -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)) }