v icon indicating copy to clipboard operation
v copied to clipboard

V generates a call to ptr_str instead of object_str when called via out parameter

Open thomas-mangin opened this issue 2 years ago • 11 comments

Describe the bug

when an struct has a function str() string and is called with a mut object, it will generate call to ptr_str and not the object own str function when the data was passed via mut (can be a function call) or if a function of the object is mut.

Expected Behavior

the right function is called

Current Behavior

it generates invalid C code for the expected behaviour

Reproduction Steps

module main

struct St {
	i int
mut:
	a []u8
}

pub fn (s St) str() string {
	return s.a.bytestr()
}

fn confuser (mut s St) {
	s.a << `a`	
	s.a << `b`	
	s.a << `c`
	println(s.a.bytestr())
	println(s.str())
}

fn main() {
	mut s := St{
		i: 0
		a: []u8{}
	}
	confuser(mut s)
	println(s.str())
}

v -printfn main__confuser run bug-array.v
VV_LOCAL_SYMBOL void main__confuser(main__St* s) {
        array_push_noscan((array*)&s->a, _MOV((u8[]){ 'a' }));
        array_push_noscan((array*)&s->a, _MOV((u8[]){ 'b' }));
        array_push_noscan((array*)&s->a, _MOV((u8[]){ 'c' }));
        println(Array_u8_bytestr(s->a));
        println(ptr_str(s));
}

abc
16ba19e18
abc


❯ v -printfn main__main run bug-array.v
VV_LOCAL_SYMBOL void main__main(void) {
        main__St s = ((main__St){.i = 0,.a = __new_array_with_default_noscan(0, 0, sizeof(u8), 0),});
        main__confuser((voidptr)&/*qq*/s);
        println(main__St_str(s));
}

abc
16bc29e18
abc

Possible Solution

rename the function from str() to something else.

Additional Information/Context

No response

V version

V 0.3.3 93a2ffa

Environment details (OS name and version, etc.)

V full version: V 0.3.3 3197ec1.93a2ffa
OS: macos, macOS, 13.2.1, 22D68
Processor: 10 cpus, 64bit, little endian, Apple M1 Max

getwd: /Users/thomas/Code/github.com/ze-core/network/bugs
vexe: /Users/thomas/Unix/local/v/v
vexe mtime: 2023-03-15 19:34:49

vroot: OK, value: /Users/thomas/Unix/local/v
VMODULES: NOT writable, value: /Users/thomas/Unix/data/v/modules
VTMP: OK, value: /tmp/v_501

Git version: git version 2.39.2
Git vroot status: weekly.2023.11-9-g93a2ffa9-dirty
.git/config present: true

CC version: Apple clang version 14.0.0 (clang-1400.0.29.202)
thirdparty/tcc status: thirdparty-macos-arm64 a668e5a0

thomas-mangin avatar Mar 15 '23 23:03 thomas-mangin

It seems ok to me.

pub fn (mut s St) str() string { and pub fn (s &St) str() string {

got called, but pub fn (s St) str() string { does not.

@spytheman @medvednikov is it ok?

felipensp avatar Mar 16 '23 00:03 felipensp

@felipensp I apologise if my description of the issue is / was inaccurate.

thomas-mangin avatar Mar 16 '23 00:03 thomas-mangin

My comment still same. If you want the mut one to call the .str(), it must be declared as pub fn (s &St) str() string {

felipensp avatar Mar 16 '23 01:03 felipensp

From my understanding, semantics on fn (mut o Obect) str() string and fn (o &Obect) str() string have their root in go, where you need to pass a pointer when you want to modify the object (as otherwise, you get to modify a copy - which does nothing). The V compiler is "auto converting" the object to a pointer", like C++ auto converts pointer indirection with "." when C differentiates "->" and ".".

V invites programmers to use mut instead of & but under the hood mut is implemented as a pointer indirection (with differences - the subject of another discussion - when I understand why).

Here, The str function does not modify the underlying content and therefore does not need to de declated as mutable. I can not (rightly) declare both a mutable and non mutable version of the same function and therefore declared it non mutable.

If V differentiate mutable and non mutable parameters, then the last println in main should fail: I am calling str on a mutable object, but it works.

passing a mutable object to a function taking a mutable object, should not change behaviour. I would expect the code to work the same in both cases.

fn main() {
	mut s := St{
		i: 0
		a: []u8{}
	}
	confuser(mut s)
	println(s.str()) // if what you propose is what should happen - this should fail
}

Calling confuser does not change s, it was and is still mut. But then, the SAME call to s.str() behaves differently.

This is inconsistent and therefore not what a programmer using the language would expect (irrespectively of what the behaviour ends being).

thomas-mangin avatar Mar 16 '23 08:03 thomas-mangin

If an object x has .str() method, it should be called with println(x).

How the receiver is declared does not matter semantically (it does for what code is generated, because of C, and the automatic (de)referencing that V does (which to me, is one more reason to dislike all that automatic guess work, but given that V does it, it should be consistent about it at least)).

What does matter, is that the object has .str() method - a custom method, should always take priority over the automatically generated ones.

spytheman avatar Mar 16 '23 09:03 spytheman

If one wants the ptr_str() behaviour, he can always do that explicitly: println(ptr_str(s))

println(s) and println(s.str() should always generate code for calling the custom .str() method.

spytheman avatar Mar 16 '23 09:03 spytheman

For me, with latest V:

string main__St_str(main__St s) {
    string _t1 = Array_u8_bytestr(s.a);
    return _t1;
}

VV_LOCAL_SYMBOL void main__confuser(main__St* s) {
    array_push_noscan((array*)&s->a, _MOV((u8[]){ 'a' }));
    array_push_noscan((array*)&s->a, _MOV((u8[]){ 'b' }));
    array_push_noscan((array*)&s->a, _MOV((u8[]){ 'c' }));
    eprintln(_SLIT("confuser -> s.a.bytestr():"));
    println(Array_u8_bytestr(s->a));
    eprintln(_SLIT("confuser -> s.str():"));
    println(ptr_str(s));
}

VV_LOCAL_SYMBOL void main__main(void) {
    main__St s = ((main__St){.i = 0,.a = __new_array_with_default_noscan(0, 0, sizeof(u8), 0),});
    main__confuser((voidptr)&/*qq*/s);
    eprintln(_SLIT("main -> s.str():"));
    println(main__St_str(s));
}

As you can see, main__confuser has: println(ptr_str(s)); while in main__main it is the correct one: println(main__St_str(s));

The main version is the correct one. In main__confuser, it should have been: println(main__St_str(*s));, because there main__St* s i.e. s is passed as a reference, and thus it needs the * to denote dereferencing that pointer in C.

spytheman avatar Mar 16 '23 09:03 spytheman

I am calling str on a mutable object, but it works.

Whether the receiver is mutable or not, only matters if you want to modify the receiver inside the body of a method. If you do not modify it (and most .str() methods do not modify their receivers, as the intention is just to produce a string representation), then mut x Type and x &Type is/should be the exact same thing.

spytheman avatar Mar 16 '23 09:03 spytheman

Calling confuser does not change s, it was and is still mut. But then, the SAME call to s.str() behaves differently.

I agree - it should have the same behavior, and print in both cases abc .

spytheman avatar Mar 16 '23 09:03 spytheman

Thank you @spytheman.

If I understand your comments correctly, you also think that .str() should have called the object string representation object.

And you are right, I should have written:

fn confuser (mut s St) {
	println(s)
}

which works correctly, all the time. Language beginners do silly things ! (and therefore find bugs 😉 )

The example I gave earlier can be simplified:

module main

struct St {
mut:
	a []u8
}

pub fn (s St) str() string {
	return s.a.bytestr()
}

fn confuser (mut s St) {
	println(s.str())
}

fn main() {
	mut s := St{
		a: []u8{}
	}
	s.a << `a`
	confuser(mut s)
	println(s.str())
}

Here, like for you, the code generated for main__main is correct, but it is not within the confuser function, it generates a call to prt_str.

VV_LOCAL_SYMBOL void main__main(void) {
        main__St s = ((main__St){.a = __new_array_with_default_noscan(0, 0, sizeof(u8), 0),});
        array_push_noscan((array*)&s.a, _MOV((u8[]){ 'a' }));
        main__confuser((voidptr)&/*qq*/s);
        println(main__St_str(s));   // it does what I expected
}

16f2dde20
a

It is only when called that the lookup for the object str function failed.

VV_LOCAL_SYMBOL void main__confuser(main__St* s) {
        println(ptr_str(s));  // it does not do what I expected
}

@felipensp @spytheman - Thank you for your time and help.

thomas-mangin avatar Mar 16 '23 09:03 thomas-mangin

My message above overlapped with your answer.

thomas-mangin avatar Mar 16 '23 11:03 thomas-mangin

Fixed by #21753

yuyi98 avatar Jun 28 '24 14:06 yuyi98