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

@ -20,7 +20,8 @@ jobs:
- name: Build V - name: Build V
run: make run: make
- name: Clone current Vinix - 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 - name: Clone current mlibc
run: git clone https://github.com/managarm/mlibc.git --depth=1 run: git clone https://github.com/managarm/mlibc.git --depth=1
- name: Patch mlibc for Vinix - 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 // The callers of gen need to cancel the context once
// they are done consuming generated integers not to leak // they are done consuming generated integers not to leak
// the internal routine started by gen. // the internal routine started by gen.
gen := fn (ctx context.Context) chan int { gen := fn (mut ctx context.Context) chan int {
dst := 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 mut v := 0
ch := ctx.done() ch := ctx.done()
for { for {
@ -55,16 +55,20 @@ fn example_with_cancel() {
} }
} }
} }
}(ctx, dst) }(mut ctx, dst)
return 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 { defer {
cancel() cancel()
} }
ch := gen(ctx) mut mut_ctx := ctx
mut ctx2 := &mut_ctx
ch := gen(mut ctx2)
for i in 0 .. 5 { for i in 0 .. 5 {
v := <-ch v := <-ch
assert i == v assert i == v
@ -87,7 +91,9 @@ const (
// function that it should abandon its work as soon as it gets to it. // function that it should abandon its work as soon as it gets to it.
fn example_with_deadline() { fn example_with_deadline() {
dur := time.now().add(short_duration) 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 { defer {
// Even though ctx will be expired, it is good practice to call its // Even though ctx will be expired, it is good practice to call its
@ -122,7 +128,9 @@ const (
fn example_with_timeout() { fn example_with_timeout() {
// Pass a context with a timeout to tell a blocking function that it // Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses. // 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 { defer {
cancel() cancel()
} }

View File

@ -43,23 +43,6 @@ pub interface Context {
// should be canceled. deadline returns none when no deadline is // should be canceled. deadline returns none when no deadline is
// set. Successive calls to deadline return the same results. // set. Successive calls to deadline return the same results.
deadline() ?time.Time 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 // Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with // if no value is associated with key. Successive calls to Value with
// the same key returns the same result. // the same key returns the same result.
@ -76,6 +59,24 @@ pub interface Context {
// collisions. // collisions.
value(key Key) ?Any value(key Key) ?Any
str() string 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 // background returns an empty Context. It is never canceled, has no

View File

@ -12,12 +12,13 @@ pub type CancelFn = fn ()
pub interface Canceler { pub interface Canceler {
id string id string
mut:
cancel(remove_from_parent bool, err IError) cancel(remove_from_parent bool, err IError)
done() chan int done() chan int
} }
[deprecated] [deprecated]
pub fn cancel(ctx Context) { pub fn cancel(mut ctx Context) {
match mut ctx { match mut ctx {
CancelContext { CancelContext {
ctx.cancel(true, canceled) ctx.cancel(true, canceled)
@ -47,9 +48,9 @@ mut:
// //
// Canceling this context releases resources associated with it, so code should // Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete. // 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) mut c := new_cancel_context(parent)
propagate_cancel(parent, c) propagate_cancel(mut parent, mut c)
return Context(c), fn [mut c] () { return Context(c), fn [mut c] () {
c.cancel(true, canceled) c.cancel(true, canceled)
} }
@ -116,18 +117,20 @@ fn (mut ctx CancelContext) cancel(remove_from_parent bool, err IError) {
for _, child in ctx.children { for _, child in ctx.children {
// NOTE: acquiring the child's lock while holding parent's lock. // 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.children = map[string]Canceler{}
ctx.mutex.unlock() ctx.mutex.unlock()
if remove_from_parent { 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() done := parent.done()
select { select {
_ := <-done { _ := <-done {
@ -136,15 +139,15 @@ fn propagate_cancel(parent Context, child Canceler) {
return return
} }
} }
mut p := parent_cancel_context(parent) or { mut p := parent_cancel_context(mut parent) or {
go fn (parent Context, child Canceler) { go fn (mut parent Context, mut child Canceler) {
pdone := parent.done() pdone := parent.done()
select { select {
_ := <-pdone { _ := <-pdone {
child.cancel(false, parent.err()) child.cancel(false, parent.err())
} }
} }
}(parent, child) }(mut parent, mut child)
return return
} }
@ -162,7 +165,7 @@ fn propagate_cancel(parent Context, child Canceler) {
// parent.done() matches that CancelContext. (If not, the CancelContext // parent.done() matches that CancelContext. (If not, the CancelContext
// has been wrapped in a custom implementation providing a // has been wrapped in a custom implementation providing a
// different done channel, in which case we should not bypass it.) // 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() done := parent.done()
if done.closed { if done.closed {
return none return none
@ -181,7 +184,7 @@ fn parent_cancel_context(parent Context) ?&CancelContext {
} }
// remove_child removes a context from its parent. // remove_child removes a context from its parent.
fn remove_child(parent Context, child Canceler) { fn remove_child(mut parent Context, child Canceler) {
mut p := parent_cancel_context(parent) or { return } mut p := parent_cancel_context(mut parent) or { return }
p.children.delete(child.id) 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 // The callers of gen need to cancel the context once
// they are done consuming generated integers not to leak // they are done consuming generated integers not to leak
// the internal routine started by gen. // the internal routine started by gen.
gen := fn (ctx context.Context) chan int { gen := fn (mut ctx context.Context) chan int {
dst := 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 mut v := 0
ch := ctx.done() ch := ctx.done()
for { for {
@ -26,16 +26,20 @@ fn test_with_cancel() {
} }
} }
} }
}(ctx, dst) }(mut ctx, dst)
return 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 { defer {
cancel() cancel()
} }
ch := gen(ctx) mut mut_ctx := ctx
mut ctx2 := &mut_ctx
ch := gen(mut ctx2)
for i in 0 .. 5 { for i in 0 .. 5 {
v := <-ch v := <-ch
assert i == v assert i == v

View File

@ -26,12 +26,12 @@ mut:
// //
// Canceling this context releases resources associated with it, so code should // Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete. // 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() id := rand.uuid_v4()
if cur := parent.deadline() { if cur := parent.deadline() {
if cur < d { if cur < d {
// The current deadline is already sooner than the new one. // 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) cancel_ctx := new_cancel_context(parent)
@ -40,7 +40,7 @@ pub fn with_deadline(parent Context, d time.Time) (Context, CancelFn) {
deadline: d deadline: d
id: id id: id
} }
propagate_cancel(parent, ctx) propagate_cancel(mut parent, mut ctx)
dur := d - time.now() dur := d - time.now()
if dur.nanoseconds() <= 0 { if dur.nanoseconds() <= 0 {
ctx.cancel(true, deadline_exceeded) // deadline has already passed 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 // Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete // call cancel as soon as the operations running in this Context complete
pub fn with_timeout(parent Context, timeout time.Duration) (Context, CancelFn) { pub fn with_timeout(mut parent Context, timeout time.Duration) (Context, CancelFn) {
return with_deadline(parent, time.now().add(timeout)) return with_deadline(mut parent, time.now().add(timeout))
} }
pub fn (ctx &TimerContext) deadline() ?time.Time { 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) ctx.cancel_ctx.cancel(false, err)
if remove_from_parent { if remove_from_parent {
// Remove this TimerContext from its parent CancelContext's children. // 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. // function that it should abandon its work as soon as it gets to it.
fn test_with_deadline() { fn test_with_deadline() {
dur := time.now().add(short_duration) 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 { defer {
// Even though ctx will be expired, it is good practice to call its // 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() { fn test_with_timeout() {
// Pass a context with a timeout to tell a blocking function that it // Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses. // 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 { defer {
cancel() cancel()
} }

View File

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

View File

@ -4,7 +4,7 @@ const (
buf_max_len = 1024 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} mut buf := []byte{len: io.buf_max_len}
for { for {
len := src.read(mut buf) or { break } 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 r := io.new_buffered_reader(reader: f)
mut stdout := os.stdout() mut stdout := os.stdout()
io.cp(r, mut stdout) ? io.cp(mut r, mut stdout) ?
assert true assert true
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -48,7 +48,7 @@ pub fn (mut c TcpConn) close() ? {
c.sock.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)) ? mut res := wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0)) ?
$if trace_tcp ? { $if trace_tcp ? {
eprintln('<<< TcpConn.read_ptr | c.sock.handle: $c.sock.handle | buf_ptr: ${ptr_str(buf_ptr)} len: $len | res: $res') 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 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) return c.read_ptr(buf.data, buf.len)
} }
@ -169,7 +169,7 @@ pub fn (mut c TcpConn) set_write_timeout(t time.Duration) {
} }
[inline] [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) 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 // Backends should provide a `new() ?FdNotifier` function
pub interface FdNotifier { pub interface FdNotifier {
mut:
add(fd int, events FdEventType, conf ...FdConfigFlags) ? add(fd int, events FdEventType, conf ...FdConfigFlags) ?
modify(fd int, events FdEventType, conf ...FdConfigFlags) ? modify(fd int, events FdEventType, conf ...FdConfigFlags) ?
remove(fd int) ? remove(fd int) ?

View File

@ -27,8 +27,9 @@ fn test_level_trigger() ? {
notifier.add(reader, .read) ? notifier.add(reader, .read) ?
os.fd_write(writer, 'foobar') os.fd_write(writer, 'foobar')
check_read_event(notifier, reader, 'foo') mut n := &notifier
check_read_event(notifier, reader, 'bar') check_read_event(mut n, reader, 'foo')
check_read_event(mut n, reader, 'bar')
assert notifier.wait(0).len == 0 assert notifier.wait(0).len == 0
} }
@ -46,8 +47,10 @@ fn test_edge_trigger() ? {
} }
notifier.add(reader, .read, .edge_trigger) ? notifier.add(reader, .read, .edge_trigger) ?
mut n := &notifier
os.fd_write(writer, 'foobar') os.fd_write(writer, 'foobar')
check_read_event(notifier, reader, 'foo') check_read_event(mut n, reader, 'foo')
assert notifier.wait(0).len == 0 assert notifier.wait(0).len == 0
@ -71,15 +74,17 @@ fn test_one_shot() ? {
} }
notifier.add(reader, .read, .one_shot) ? notifier.add(reader, .read, .one_shot) ?
mut n := &notifier
os.fd_write(writer, 'foobar') os.fd_write(writer, 'foobar')
check_read_event(notifier, reader, 'foo') check_read_event(mut n, reader, 'foo')
os.fd_write(writer, 'baz') os.fd_write(writer, 'baz')
assert notifier.wait(0).len == 0 assert notifier.wait(0).len == 0
// rearm // rearm
notifier.modify(reader, .read) ? 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) events := notifier.wait(0)
assert events.len == 1 assert events.len == 1
assert events[0].fd == reader_fd 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 // copy_expert execute COPY commands
// https://www.postgresql.org/docs/9.5/libpq-copy.html // 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) res := C.PQexec(db.conn, query.str)
status := C.PQresultStatus(res) 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 // 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. // implement in order to ensure that _all_ functions can be used with the generator.
pub interface PRNG { pub interface PRNG {
mut:
seed(seed_data []u32) seed(seed_data []u32)
u32() u32 u32() u32
u64() u64 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 { if f.params.len != func.params.len {
return 'expected $f.params.len parameter(s), not $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) exps := t.type_to_str(f.params[i].typ)
gots := t.type_to_str(func.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 '' 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. // Visitor defines a visit method which is invoked by the walker in each node it encounters.
pub interface Visitor { pub interface Visitor {
mut:
visit(node &ast.Node) ? 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 // 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) { 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 // 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 } visitor.visit(node) or { return }
children := node.children() children := node.children()
for child_node in 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{ mut nbo := NodeByOffset{
pos: 13 pos: 13
} }
walker.walk(nbo, file) walker.walk(mut nbo, file)
assert nbo.node is ast.Stmt assert nbo.node is ast.Stmt
stmt := nbo.node as ast.Stmt stmt := nbo.node as ast.Stmt
assert stmt is ast.StructDecl 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"]; // Node14 [shape="box",label="PrivateBase",URL="$classPrivateBase.html"];
// Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"]; // Node15 -> Node9 [dir=back,color="midnightblue",fontsize=10,style="solid"];
for afile in ast_files { for afile in ast_files {
walker.walk(mapper, afile) walker.walk(mut mapper, afile)
} }
mapper.dg.finish() 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) msg := c.table.is_same_method(imethod, method)
if msg.len > 0 { 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('$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', c.error('`$styp` incorrectly implements method `$imethod.name` of interface `$inter_sym.name`: $msg',
pos) pos)
return false return false

View File

@ -4,4 +4,5 @@ vlib/v/checker/tests/unimplemented_interface_b.vv:13:6: error: `Cat` incorrectly
13 | foo(c) 13 | foo(c)
| ^ | ^
14 | } 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

@ -4,4 +4,5 @@ vlib/v/checker/tests/unimplemented_interface_c.vv:12:6: error: `Cat` incorrectly
12 | foo(Cat{}) 12 | foo(Cat{})
| ~~~~~ | ~~~~~
13 | } 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

@ -4,4 +4,5 @@ vlib/v/checker/tests/unimplemented_interface_d.vv:12:6: error: `Cat` incorrectly
12 | foo(Cat{}) 12 | foo(Cat{})
| ~~~~~ | ~~~~~
13 | } 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

@ -5,11 +5,13 @@ vlib/v/checker/tests/unimplemented_interface_e.vv:12:6: error: `Cat` incorrectly
| ~~~~~ | ~~~~~
13 | _ = Animal(Cat{}) 13 | _ = Animal(Cat{})
14 | } 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 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() { 11 | fn main() {
12 | foo(Cat{}) 12 | foo(Cat{})
13 | _ = Animal(Cat{}) 13 | _ = Animal(Cat{})
| ~~~~~~~~~~~~~ | ~~~~~~~~~~~~~
14 | } 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{} 11 | animals << Cat{}
| ~~~~~ | ~~~~~
12 | } 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> { interface Iterator<T> {
mut:
next() ?T next() ?T
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -196,6 +196,7 @@ interface Speaker2 {
name() string name() string
speak() speak()
return_speaker() Speaker2 return_speaker() Speaker2
mut:
return_speaker2() ?Speaker2 return_speaker2() ?Speaker2
} }
@ -250,6 +251,7 @@ interface Animal {
name() string name() string
name_detailed(pet_name string) string name_detailed(pet_name string) string
speak(s string) speak(s string)
mut:
set_breed(s string) set_breed(s string)
} }
@ -327,6 +329,31 @@ mut:
my_field int 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() { fn main() {
mut aa := AA{} mut aa := AA{}
mut ii := II(aa) mut ii := II(aa)
@ -335,4 +362,7 @@ fn main() {
assert ii.my_field == 123 assert ii.my_field == 123
ii.my_field = 1234 ii.my_field = 1234
assert aa.my_field == 1234 assert aa.my_field == 1234
mut dog := Dog2{'Doggo'}
println(dog.name)
println(get_animal_name(mut dog))
} }