A Vim9 class/object member variable can have read-only access or a read-write access or no access
to functions outside of the class/object. The following sample code shows how to declare member
variables with these different access controls:
vim9script
class A
# object member: read-only access
this.val1 = 10
# object member: read/write access
public this.val2 = 20
# object member: private (no access)
this._val3 = 30
# class member: read-only access
static val4 = 40
# class member: read/write access
public static val5 = 50
# class member: private (no access)
static _val6 = 60
endclass
This page is created to track the different ways these member variables can be accessed and whether they
are tested and verified to work properly.
Object Member Variable
1.1 Access from script level:
vim9script
class A
this.val = 10
endclass
var a = A.new()
echo a.val # Works
a.val = 20 # Fails with E1335
1.2. Access from a def function:
vim9script
class A
this.val = 10
endclass
def T()
var a = A.new()
echo a.val # Works
a.val = 20 # Fails with E1333
enddef
T()
1.3. Access from a method in the class:
vim9script
class A
this.val = 10
def Foo()
this.val = 20 # Works
echo this.val # Works
enddef
endclass
var a = A.new()
a.Foo()
1.4. Access from a method in a different class:
vim9script
class A
this.val = 10
endclass
class B
def Foo()
var a = A.new()
a.val = 20 # Fails with E1333
echo a.val # Works
enddef
endclass
var b = B.new()
b.Foo()
1.5. Access from child class extending this class:
vim9script
class A
this.val = 10
endclass
class B extends A
def Foo()
this.val = 20 # Works
echo this.val # Works
enddef
endclass
var b = B.new()
b.Foo()
1.6. Access from a class implementing an interface:
vim9script
interface A
this.val: number
endinterface
class B implements A
this.val: number = 10
def Foo()
this.val = 20 # Works
echo this.val # Works
enddef
endclass
var b = B.new()
b.Foo()
2.1. Access from script level:
vim9script
class A
public this.val = 10
endclass
var a = A.new()
a.val = 20 # Works
echo a.val # Works
2.2. Access from a def function:
vim9script
class A
public this.val = 10
endclass
def T()
var a = A.new()
a.val = 20 # Works
echo a.val # Works
enddef
T()
2.3. Access from a method in the class:
vim9script
class A
public this.val = 10
def Foo()
this.val = 20 # Works
echo this.val # Works
enddef
endclass
var a = A.new()
a.Foo()
2.4. Access from a method in a different class:
vim9script
class A
public this.val = 10
endclass
class B
def Foo()
var a = A.new()
a.val = 20 # Works
echo a.val # Works
enddef
endclass
var b = B.new()
b.Foo()
2.5. Access from child class extending this class:
vim9script
class A
public this.val = 10
endclass
class B extends A
def Foo()
this.val = 20 # Works
echo this.val # Works
enddef
endclass
var b = B.new()
b.Foo()
2.6. Access from a class implementing an interface:
vim9script
interface A
public this.val: number
endinterface
class B implements A
public this.val: number = 10
def Foo()
this.val = 20 # Works
echo this.val # Works
enddef
endclass
var b = B.new()
b.Foo()
3.1. Access from script level:
vim9script
class A
this._val = 10
endclass
var a = A.new()
echo a._val # Fails with E1333
a._val = 20 # Fails with E1333
3.2. Access from a def function:
vim9script
class A
this._val = 10
endclass
def T()
var a = A.new()
echo a._val # Fails with E1333
a._val = 20 # Fails with E1333
enddef
T()
3.3. Access from a method in the class:
vim9script
class A
this._val = 10
def Foo()
this._val = 20 # Works
echo this._val # Works
enddef
endclass
var a = A.new()
a.Foo()
3.4. Access from a method in a different class:
vim9script
class A
this._val = 10
endclass
class B
def Foo()
var a = A.new()
a._val = 20 # Fails with E1333
echo a._val # Fails with E1333
enddef
endclass
var b = B.new()
b.Foo()
3.5. Access from child class extending this class:
vim9script
class A
this._val = 10
endclass
class B extends A
def Foo()
this._val = 20 # Works
echo this._val # Works
enddef
endclass
var b = B.new()
b.Foo()
3.6. Access from a class implementing an interface:
vim9script
interface A
this._val: number
endinterface
class B implements A
this._val: number = 10
def Foo()
this._val = 20 # Works
echo this._val # Works
enddef
endclass
var b = B.new()
b.Foo()
Class Member Variable
Read-only access (default)
- Access from script level
- Access from a def function
- Access from a method in the class
- Access from a method in a different class
- Access from child class extending this class
- Access from a class implementing an interface
Read-write access (public)
- Access from script level
- Access from a def function
- Access from a method in the class
- Access from a method in a different class
- Access from child class extending this class
- Access from a class implementing an interface
No access (private)
- Access from script level
- Access from a def function
- Access from a method in the class
- Access from a method in a different class
- Access from child class extending this class
- Access from a class implementing an interface
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
If I may ask, what was the reasoning behind the use of underscore for private member variables and not the use of private keyword?
Was it a matter of aesthetics or inspired by Python pseudo-privacy mechanism?
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
The rationale for using underscore in private class/object member variable names is described in the section titled "Making object members private with an underscore" in https://vimhelp.org/vim9class.txt.html#_rationale
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
For some reason the use of underscore from an aesthetic point of view does not feel "right", but if it works... 😒
Something like the following examples make it more obvious to my tired eyes that they mean private member:
# Version 1
vim9script
class A
this::val = 10 # "::" for private members
endclass
var a = A.new()
echo a::val # Fails with E1333 (expected)
a::val = 20 # Fails with E1333 (expected)
# Version 2
vim9script
class A
this.$val = 10 # "$" for private members
endclass
var a = A.new()
echo a.$val # Fails with E1333 (expected)
a.$val = 20 # Fails with E1333 (expected)
Maybe it's not clear with the aforementioned examples, but with the use of private methods inside classes, it makes it clearer:
# Original before from https://vimhelp.org/vim9class.txt.html#_rationale
class SomeClass
def _Foo(): number
return 10
enddef
def Bar(): number
return this._Foo()
enddef
endclass
# Version 1
class SomeClass
def this::Foo(): number
return 10
enddef
def Bar(): number
return this::Foo()
enddef
endclass
# Version 2
class SomeClass
def $Foo(): number
return 10
enddef
def Bar(): number
return this.$Foo()
enddef
endclass
—
Reply to this email directly, view it on GitHub,.
You are receiving this because you are subscribed to this thread.![]()
whether we use _ or :: or $ for private members seems like bike-shedding. And that decision has already been made 🤷
—
Reply to this email directly, view it on GitHub,.
You are receiving this because you are subscribed to this thread.![]()
Oh well... 🤷
—
Reply to this email directly, view it on GitHub,.
You are receiving this because you are subscribed to this thread.![]()
@yegappan Can you suggest something or related somethings that you'd expect to take less than a day to fix?
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
The test for using the member variables from a class implementing an interface is crashing Vim (you need to comment out the lines that generate error). Can you look into this?
Another one is a private class member variable is accessible from a method in a different class.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
Speaking of interfaces, generally they only define methods, here they also allow members? It looks like another kind of inheritance that makes things much too complex, I'd say either make interface work as in go/c# (no data, just methods) if it's possible (and easy) or drop them.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
That's a different discussion, this one is about access control working, not language spec; let's not hijack this discussion. It's a good discussion to have, I don't see anything in the spec about interface members, maybe they're not needed. BTW, vim9script doesn't support multiple inheritance, so can't just drop them.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
In the documentation there's only one example with a method, but the examples here show you can define members. I can't see the difference from abstract classes this way.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
PLEASE USE #12987 for further discussion of interface members. I've deleted my comment from this discussion.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
This simpler test fails
vim9script
interface A
static ro_class_var: number
endinterface
class B implements A
static ro_class_var = 40
def Foo()
var xxx = A.ro_class_var # Works
enddef
endclass
var b = B.new()
b.Foo()
I'm not sure what the best strategy here is. Thinking it's something not yet implemented rather than a bug. On an interface, class_itf2class needs to be examined. Some thoughts
Examining itf2class might require knowing what extending class we're dealing with.
I'm going to try a class C implements A and see what itf2class looks like and I'll look for an example of class_itf2class used with methods.
Maybe you've got familiarity with this stuff, before I spend too much time looking around. (Read the following and the previous will make more sense...)
It crashes in copy_tv from exec_instructions() doing case ISN_LOAD_CLASSMEMBER:with
(gdb) p iptr->isn_arg.classmember.cm_class.class_members_tv
$7 = (typval_T *) 0x0
and
p *iptr->isn_arg.classmember.cm_class
$10 = {
class_name = 0x555555d31b00 "A",
class_flags = 1,
...
In struct class_S there's
itf2class_T *class_itf2class; // member index lookup tables
and
(gdb) p *iptr->isn_arg.classmember.cm_class.class_itf2class
$12 = {i2c_next = 0x555555d39a70, i2c_class = 0x555555d39960, i2c_is_method = 1}
(gdb) p *iptr->isn_arg.classmember.cm_class.class_itf2class.i2c_next
$13 = {i2c_next = 0x0, i2c_class = 0x555555d39960, i2c_is_method = 0}
found the class
(gdb) p *iptr->isn_arg.classmember.cm_class.class_itf2class.i2c_next.i2c_class
$14 = {class_name = 0x555555d3b330 "B", class_flags = 0,
and the value
(gdb) p *iptr->isn_arg.classmember.cm_class.class_itf2class.i2c_next.i2c_class.class_members_tv
$15 = {v_type = VAR_NUMBER, v_lock = 0 '\000', vval = {v_number = 40,
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
@yegappan Doh! The first thing to look at is how object members are handled as opposed to class members.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
@errael you could start with the simpler (and more important) stuff, like the BUGs here (accessing static class members), since they are probably related, and we didn't agree on interface specification.
vim9script
class A
this.ro_obj_var = 10
public this.rw_obj_var = 20
this._priv_obj_var = 30
static ro_class_var = 40
public static rw_class_var = 50
static _priv_class_var = 60
endclass
def T()
var a = A.new()
echo a.ro_obj_var # Works
a.ro_obj_var = 0 # Fails with E1335 (expected)
echo a.rw_obj_var # Works
a.rw_obj_var = 0 # Works
echo a._priv_obj_var # Fails with E1333 (expected)
a._priv_obj_var = 0 # Fails with E1333 (expected)
echo A.ro_class_var # Works
A.ro_class_var = 0 # Fails with E1089 (BUG)
echo A.rw_class_var # Works
A.rw_class_var = 0 # Fails with E1089 (BUG)
echo A._priv_class_var # Works (BUG)
A._priv_class_var = 0 # Fails with E1089 (BUG)
enddef
T()
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
@errael you could start with the simpler (and more important) stuff, like the BUGs here (accessing static class members), since they are probably related, and we didn't agree on interface specification.
vim9script
class A
this.ro_obj_var = 10
public this.rw_obj_var = 20
this._priv_obj_var = 30
static ro_class_var = 40
public static rw_class_var = 50
static _priv_class_var = 60
endclass
def T()
var a = A.new()
echo a.ro_obj_var # Works
a.ro_obj_var = 0 # Fails with E1335 (expected)
echo a.rw_obj_var # Works
a.rw_obj_var = 0 # Works
echo a._priv_obj_var # Fails with E1333 (expected)
a._priv_obj_var = 0 # Fails with E1333 (expected)
echo A.ro_class_var # Works
A.ro_class_var = 0 # Fails with E1089 (BUG)
echo A.rw_class_var # Works
A.rw_class_var = 0 # Fails with E1089 (BUG)
echo A._priv_class_var # Works (BUG)
A._priv_class_var = 0 # Fails with E1089 (BUG)
enddef
T()
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
I was asked to look at the crash; I'll finish what I'm doing, trying to track down the specifics of the crash, but maybe not a solution. He had something to look at after the crash; so I'll probably do that. As far as I'm concerned there should be a central figure coordinating, to prevent interference.
I do wonder why you felt it necessary to post that in the middle of the thread about the crash, rather than starting a separate thread. It's rude.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
Sorry I didn't mean to be rude, just thought that starting from the hardest problem to solve maybe wasn't ideal. It was just a suggestion.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
just a suggestion.
Which is OK with me. But a separate thread seems more appropriate.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
I think I see the issue, at least partly.
An interface's members have storage (typeval_T) allocated with the object;
for static members, there would presumable only be one copy of the data
and the typeval_T would be allocated with the the interface not the object.
Currently there is storage for the statics allocated with the object
I set up
B implements A
static ro_class_var1 = 40
static ro_class_var2 = 50
C implements A
static ro_class_var2 = 66
static ro_class_var1 = 77
Note the reversed the order of the static declarations in B and C
It crashed after
var b = B.new()
b.Foo()
as expected. Note in the gdb output that the first typval_T in "B" is 40 which
is var1 and in "C" the first is "66" which is var2.
So, as suspected, each class has it's own storage of the statics and in their own order.
Not sure how much it would take to fix this, but one approach would be to
forbid/error static members in an interface until there's a fix. If a
programmer want to share data between objects, there are other simple ways
like a singleton class with the shared data.
Are you OK with giving an error for static in interface for now?
Where are interfaces declared?
(gdb) p *iptr->isn_arg.classmember.cm_class.class_itf2class.i2c_next.i2c_next.i2c_next.i2c_class
$27 = {class_name = 0x555555d30e30 "B"...
(gdb) p *iptr->isn_arg.classmember.cm_class.class_itf2class.i2c_next.i2c_next.i2c_next.i2c_class.class_members_tv
$28 = {v_type = VAR_NUMBER, v_lock = 0 '\000', vval = {v_number = 40,
(gdb) p *iptr->isn_arg.classmember.cm_class.class_itf2class.i2c_next.i2c_class
$29 = {class_name = 0x555555d316e0 "C",...
(gdb) p *iptr->isn_arg.classmember.cm_class.class_itf2class.i2c_next.i2c_class.class_members_tv
$30 = {v_type = VAR_NUMBER, v_lock = 0 '\000', vval = {v_number = 66
Here's the full program I used
vim9script
interface A
static ro_class_var1: number
static ro_class_var2: number
endinterface
class B implements A
static ro_class_var1 = 40
static ro_class_var2 = 50
def Foo()
var xxx = A.ro_class_var1
var yyy = A.ro_class_var2
enddef
endclass
class C implements A
static ro_class_var2 = 66
static ro_class_var1 = 77
def Foo()
var xxx = A.ro_class_var1
var yyy = A.ro_class_var2
enddef
endclass
#disassemble B.Foo
var b = B.new()
b.Foo()
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
@yegappan I posted what I think is a complete analysis of the crash, with a suggestion on next step. Forgot to @ you (I get hit and miss delivery)
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
Thanks for the detailed analysis. Instead of preventing the usage of static members in interfaces, we should fix this crash. Let me spend some time going through your analysis and the code.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
Instead of preventing the usage of static members in interfaces, we should fix this crash.
I agree. But since it looks like a development task, not just a bug fix, preventing it is a fallback while the bugs are rooted out. (hope I'm wrong).
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
So it doesn't makes sense to allow read or write to this variable.
Can you create a PR for this?
(I'm still not entirely comfortable with how statics works, but I understand now)
What sounds like a good error message? How about
Cannot directly access static through interface.
or something like
Only access a static through a class, never an interface
Just thought of something else, and I'm still confused. What about
vim9script
interface I
public static s_var: number
endinterface
class C implements I
public static s_var = 3
endclass
def F(i: I)
var x = i.s_var
enddef
var c = C.new()
F(c)
If that doesn't work, then interfaces don't work.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
BTW, the compilation error is
E1326: Member not found on object "I": s_var
for
def F(i: I)
var x = i.s_var
enddef
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
I may be getting back to the "disallow statics in this release" point of view.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
A member variable in an interface cannot be initialized.
So it doesn't makes sense to allow read or write to this variable.
Can you create a PR for this?
But until the issue of def F(i: I) is resolved, it might not make sense to apply this. Except as a last ditch effort to avoid a crash.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
The member access control issues discussed above are all addressed in the latest version of Vim.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
A member variable in an interface cannot be initialized.
So it doesn't makes sense to allow read or write to this variable.
Can you create a PR for this?
#13007 prevents direct access to interface statics. It also allows a :def outside of class to read statics.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()
Closed #12979 as resolved.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.![]()