v
v copied to clipboard
V generates a call to ptr_str instead of object_str when called via out parameter
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
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 I apologise if my description of the issue is / was inaccurate.
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 {
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).
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.
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.
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.
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.
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 .
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.
My message above overlapped with your answer.
Fixed by #21753