0
0
mirror of https://github.com/vim/vim.git synced 2025-08-26 20:03:41 -04:00

patch 9.0.1804: Vim9: no support for private object methods

Problem:  Vim9: no support for private object methods
Solution: Add support for private object/class methods

closes: #12920

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
This commit is contained in:
Yegappan Lakshmanan 2023-08-27 19:18:23 +02:00 committed by Christian Brabandt
parent 03e44a1d70
commit cd7293bf6c
No known key found for this signature in database
GPG Key ID: F3F92DA383FDDE09
9 changed files with 691 additions and 5 deletions

View File

@ -129,6 +129,7 @@ Further Vim9 improvements, possibly after launch:
or: def _Func() or: def _Func()
Perhaps use "private" keyword instead of "_" prefix? Perhaps use "private" keyword instead of "_" prefix?
- "final" object members - can only be set in the constructor. - "final" object members - can only be set in the constructor.
- Support export/import of classes and interfaces.
- Cannot use class type of itself in the method (Issue #12369) - Cannot use class type of itself in the method (Issue #12369)
- Cannot use an object method in a lambda #12417 - Cannot use an object method in a lambda #12417
Define all methods before compiling them? Define all methods before compiling them?

View File

@ -178,6 +178,26 @@ number to the total number of lines: >
enddef enddef
Private methods ~
If you want object methods to be accessible only from other methods of the
same class and not used from outside the class, then you can make them
private. This is done by prefixing the method name with an underscore: >
class SomeClass
def _Foo(): number
return 10
enddef
def Bar(): number
return this._Foo()
enddef
endclass
<
Accessing a private method outside the class will result in an error (using
the above class): >
var a = SomeClass.new()
a._Foo()
<
Simplifying the new() method ~ Simplifying the new() method ~
Many constructors take values for the object members. Thus you very often see Many constructors take values for the object members. Thus you very often see
@ -284,6 +304,18 @@ object members, they cannot use the "this" keyword. >
Inside the class the function can be called by name directly, outside the Inside the class the function can be called by name directly, outside the
class the class name must be prefixed: `OtherThing.ClearTotalSize()`. class the class name must be prefixed: `OtherThing.ClearTotalSize()`.
Just like object methods the access can be made private by using an underscore
as the first character in the method name: >
class OtherThing
static def _Foo()
echo "Foo"
enddef
def Bar()
OtherThing._Foo()
enddef
endclass
============================================================================== ==============================================================================
4. Using an abstract class *Vim9-abstract-class* 4. Using an abstract class *Vim9-abstract-class*

View File

@ -3484,6 +3484,11 @@ EXTERN char e_warning_pointer_block_corrupted[]
INIT(= N_("E1364: Warning: Pointer block corrupted")); INIT(= N_("E1364: Warning: Pointer block corrupted"));
EXTERN char e_cannot_use_a_return_type_with_new[] EXTERN char e_cannot_use_a_return_type_with_new[]
INIT(= N_("E1365: Cannot use a return type with the \"new\" function")); INIT(= N_("E1365: Cannot use a return type with the \"new\" function"));
EXTERN char e_cannot_access_private_method_str[]
INIT(= N_("E1366: Cannot access private method: %s"));
EXTERN char e_interface_str_and_class_str_function_access_not_same[]
INIT(= N_("E1367: Access type of class method %s differs from interface method %s"));
EXTERN char e_cannot_mix_positional_and_non_positional_str[] EXTERN char e_cannot_mix_positional_and_non_positional_str[]
INIT(= N_("E1400: Cannot mix positional and non-positional arguments: %s")); INIT(= N_("E1400: Cannot mix positional and non-positional arguments: %s"));
EXTERN char e_fmt_arg_nr_unused_str[] EXTERN char e_fmt_arg_nr_unused_str[]
@ -3501,4 +3506,4 @@ EXTERN char e_member_str_type_mismatch_expected_str_but_got_str[]
EXTERN char e_method_str_type_mismatch_expected_str_but_got_str[] EXTERN char e_method_str_type_mismatch_expected_str_but_got_str[]
INIT(= N_("E1407: Member \"%s\": type mismatch, expected %s but got %s")); INIT(= N_("E1407: Member \"%s\": type mismatch, expected %s but got %s"));
// E1366 - E1399 unused // E1368 - E1399 unused

View File

@ -1489,8 +1489,8 @@ typedef struct {
#define TTFLAG_SUPER 0x40 // object from "super". #define TTFLAG_SUPER 0x40 // object from "super".
typedef enum { typedef enum {
VIM_ACCESS_PRIVATE, // read/write only inside th class VIM_ACCESS_PRIVATE, // read/write only inside the class
VIM_ACCESS_READ, // read everywhere, write only inside th class VIM_ACCESS_READ, // read everywhere, write only inside the class
VIM_ACCESS_ALL // read/write everywhere VIM_ACCESS_ALL // read/write everywhere
} omacc_T; } omacc_T;
@ -1790,6 +1790,7 @@ struct ufunc_S
class_T *uf_class; // for object method and constructor; does not class_T *uf_class; // for object method and constructor; does not
// count for class_refcount // count for class_refcount
int uf_private; // TRUE if class or object private method
garray_T uf_args; // arguments, including optional arguments garray_T uf_args; // arguments, including optional arguments
garray_T uf_def_args; // default argument expressions garray_T uf_def_args; // default argument expressions

View File

@ -2801,4 +2801,594 @@ def Test_object_lockvar()
v9.CheckScriptSuccess(lines) v9.CheckScriptSuccess(lines)
enddef enddef
" Test for a private object method
def Test_private_object_method()
# Try calling a private method using an object (at the script level)
var lines =<< trim END
vim9script
class A
def _Foo(): number
return 1234
enddef
endclass
var a = A.new()
a._Foo()
END
v9.CheckScriptFailure(lines, 'E1366: Cannot access private method: _Foo()')
# Try calling a private method using an object (from a def function)
lines =<< trim END
vim9script
class A
def _Foo(): number
return 1234
enddef
endclass
def T()
var a = A.new()
a._Foo()
enddef
T()
END
v9.CheckScriptFailure(lines, 'E1366: Cannot access private method: _Foo()')
# Use a private method from another object method (in script context)
lines =<< trim END
vim9script
class A
def _Foo(): number
return 1234
enddef
def Bar(): number
return this._Foo()
enddef
endclass
var a = A.new()
assert_equal(1234, a.Bar())
END
v9.CheckScriptSuccess(lines)
# Use a private method from another object method (def function context)
lines =<< trim END
vim9script
class A
def _Foo(): number
return 1234
enddef
def Bar(): number
return this._Foo()
enddef
endclass
def T()
var a = A.new()
assert_equal(1234, a.Bar())
enddef
T()
END
v9.CheckScriptSuccess(lines)
# Try calling a private method without the "this" prefix
lines =<< trim END
vim9script
class A
def _Foo(): number
return 1234
enddef
def Bar(): number
return _Foo()
enddef
endclass
var a = A.new()
a.Bar()
END
v9.CheckScriptFailure(lines, 'E117: Unknown function: _Foo')
# Try calling a private method using the class name
lines =<< trim END
vim9script
class A
def _Foo(): number
return 1234
enddef
endclass
A._Foo()
END
v9.CheckScriptFailure(lines, 'E1325: Method not found on class "A": _Foo()')
# Try to use "public" keyword when defining a private method
lines =<< trim END
vim9script
class A
public def _Foo()
enddef
endclass
var a = A.new()
a._Foo()
END
v9.CheckScriptFailure(lines, 'E1331: Public must be followed by "this" or "static"')
# Define two private methods with the same name
lines =<< trim END
vim9script
class A
def _Foo()
enddef
def _Foo()
enddef
endclass
var a = A.new()
END
v9.CheckScriptFailure(lines, 'E1355: Duplicate function: _Foo')
# Define a private method and a object method with the same name
lines =<< trim END
vim9script
class A
def _Foo()
enddef
def Foo()
enddef
endclass
var a = A.new()
END
v9.CheckScriptFailure(lines, 'E1355: Duplicate function: Foo')
# Define an object method and a private method with the same name
lines =<< trim END
vim9script
class A
def Foo()
enddef
def _Foo()
enddef
endclass
var a = A.new()
END
v9.CheckScriptFailure(lines, 'E1355: Duplicate function: _Foo')
# Call a public method and a private method from a private method
lines =<< trim END
vim9script
class A
def Foo(): number
return 100
enddef
def _Bar(): number
return 200
enddef
def _Baz()
assert_equal(100, this.Foo())
assert_equal(200, this._Bar())
enddef
def T()
this._Baz()
enddef
endclass
var a = A.new()
a.T()
END
v9.CheckScriptSuccess(lines)
# Try calling a private method from another class
lines =<< trim END
vim9script
class A
def _Foo(): number
return 100
enddef
endclass
class B
def Foo(): number
var a = A.new()
a._Foo()
enddef
endclass
var b = B.new()
b.Foo()
END
v9.CheckScriptFailure(lines, 'E1366: Cannot access private method: _Foo()')
# Call a private object method from a child class object method
lines =<< trim END
vim9script
class A
def _Foo(): number
return 1234
enddef
endclass
class B extends A
def Bar()
enddef
endclass
class C extends B
def Baz(): number
return this._Foo()
enddef
endclass
var c = C.new()
assert_equal(1234, c.Baz())
END
v9.CheckScriptSuccess(lines)
# Call a private object method from a child class object
lines =<< trim END
vim9script
class A
def _Foo(): number
return 1234
enddef
endclass
class B extends A
def Bar()
enddef
endclass
class C extends B
def Baz(): number
enddef
endclass
var c = C.new()
assert_equal(1234, c._Foo())
END
v9.CheckScriptFailure(lines, 'E1366: Cannot access private method: _Foo()')
# Using "_" prefix in a method name should fail outside of a class
lines =<< trim END
vim9script
def _Foo(): number
return 1234
enddef
var a = _Foo()
END
v9.CheckScriptFailure(lines, 'E1267: Function name must start with a capital: _Foo(): number')
enddef
" Test for an private class method
def Test_private_class_method()
# Try calling a class private method (at the script level)
var lines =<< trim END
vim9script
class A
static def _Foo(): number
return 1234
enddef
endclass
A._Foo()
END
v9.CheckScriptFailure(lines, 'E1366: Cannot access private method: _Foo()')
# Try calling a class private method (from a def function)
lines =<< trim END
vim9script
class A
static def _Foo(): number
return 1234
enddef
endclass
def T()
A._Foo()
enddef
T()
END
v9.CheckScriptFailure(lines, 'E1366: Cannot access private method: _Foo()')
# Try calling a class private method using an object (at the script level)
lines =<< trim END
vim9script
class A
static def _Foo(): number
return 1234
enddef
endclass
var a = A.new()
a._Foo()
END
v9.CheckScriptFailure(lines, 'E1325: Method not found on class "A": _Foo()')
# Try calling a class private method using an object (from a def function)
lines =<< trim END
vim9script
class A
static def _Foo(): number
return 1234
enddef
endclass
def T()
var a = A.new()
a._Foo()
enddef
T()
END
v9.CheckScriptFailure(lines, 'E1325: Method not found on class "A": _Foo()')
# Use a class private method from an object method
lines =<< trim END
vim9script
class A
static def _Foo(): number
return 1234
enddef
def Bar()
assert_equal(1234, A._Foo())
enddef
endclass
var a = A.new()
a.Bar()
END
v9.CheckScriptSuccess(lines)
# Use a class private method from another class private method
lines =<< trim END
vim9script
class A
static def _Foo1(): number
return 1234
enddef
static def _Foo2()
assert_equal(1234, A._Foo1())
enddef
def Bar()
A._Foo2()
enddef
endclass
var a = A.new()
a.Bar()
END
v9.CheckScriptSuccess(lines)
# Declare a class method and a class private method with the same name
lines =<< trim END
vim9script
class A
static def _Foo()
enddef
static def Foo()
enddef
endclass
var a = A.new()
END
v9.CheckScriptFailure(lines, 'E1355: Duplicate function: Foo')
# Try calling a class private method from another class
lines =<< trim END
vim9script
class A
static def _Foo(): number
return 1234
enddef
endclass
class B
def Foo(): number
return A._Foo()
enddef
endclass
var b = B.new()
assert_equal(1234, b.Foo())
END
v9.CheckScriptFailure(lines, 'E1366: Cannot access private method: _Foo()')
# Call a private class method from a child class object method
lines =<< trim END
vim9script
class A
static def _Foo(): number
return 1234
enddef
endclass
class B extends A
def Bar()
enddef
endclass
class C extends B
def Baz(): number
return A._Foo()
enddef
endclass
var c = C.new()
assert_equal(1234, c.Baz())
END
v9.CheckScriptSuccess(lines)
# Call a private class method from a child class private class method
lines =<< trim END
vim9script
class A
static def _Foo(): number
return 1234
enddef
endclass
class B extends A
def Bar()
enddef
endclass
class C extends B
static def Baz(): number
return A._Foo()
enddef
endclass
assert_equal(1234, C.Baz())
END
v9.CheckScriptSuccess(lines)
# Call a private class method from a child class object
lines =<< trim END
vim9script
class A
static def _Foo(): number
return 1234
enddef
endclass
class B extends A
def Bar()
enddef
endclass
class C extends B
def Baz(): number
enddef
endclass
var c = C.new()
assert_equal(1234, C._Foo())
END
v9.CheckScriptFailure(lines, 'E1366: Cannot access private method: _Foo()')
enddef
" Test for an interface private object_method
def Test_interface_private_object_method()
# Implement an interface private method and use it from a public method
var lines =<< trim END
vim9script
interface Intf
def _Foo(): number
endinterface
class A implements Intf
def _Foo(): number
return 1234
enddef
def Bar(): number
return this._Foo()
enddef
endclass
var a = A.new()
assert_equal(1234, a.Bar())
END
v9.CheckScriptSuccess(lines)
# Call an interface private class method (script context)
lines =<< trim END
vim9script
interface Intf
def _Foo(): number
endinterface
class A implements Intf
def _Foo(): number
return 1234
enddef
endclass
var a = A.new()
assert_equal(1234, a._Foo())
END
v9.CheckScriptFailure(lines, 'E1366: Cannot access private method: _Foo()')
# Call an interface private class method (def context)
lines =<< trim END
vim9script
interface Intf
def _Foo(): number
endinterface
class A implements Intf
def _Foo(): number
return 1234
enddef
endclass
def T()
var a = A.new()
assert_equal(1234, a._Foo())
enddef
T()
END
v9.CheckScriptFailure(lines, 'E1366: Cannot access private method: _Foo()')
# Implement an interface private object method as a private class method
lines =<< trim END
vim9script
interface Intf
def _Foo(): number
endinterface
class A implements Intf
static def _Foo(): number
return 1234
enddef
endclass
END
v9.CheckScriptFailure(lines, 'E1349: Function "_Foo" of interface "Intf" not implemented')
enddef
" Test for an interface private class method
def Test_interface_private_class_method()
# Implement an interface private class method and use it from a public method
var lines =<< trim END
vim9script
interface Intf
static def _Foo(): number
endinterface
class A implements Intf
static def _Foo(): number
return 1234
enddef
def Bar(): number
return A._Foo()
enddef
endclass
var a = A.new()
assert_equal(1234, a.Bar())
END
v9.CheckScriptSuccess(lines)
# Call an interface private class method (script context)
lines =<< trim END
vim9script
interface Intf
static def _Foo(): number
endinterface
class A implements Intf
static def _Foo(): number
return 1234
enddef
endclass
assert_equal(1234, A._Foo())
END
v9.CheckScriptFailure(lines, 'E1366: Cannot access private method: _Foo())')
# Call an interface private class method (def context)
lines =<< trim END
vim9script
interface Intf
static def _Foo(): number
endinterface
class A implements Intf
static def _Foo(): number
return 1234
enddef
endclass
def T()
assert_equal(1234, A._Foo())
enddef
T()
END
v9.CheckScriptFailure(lines, 'E1366: Cannot access private method: _Foo())')
# Implement an interface private class method as a private object method
lines =<< trim END
vim9script
interface Intf
static def _Foo(): number
endinterface
class A implements Intf
def _Foo(): number
return 1234
enddef
endclass
END
v9.CheckScriptFailure(lines, 'E1349: Function "_Foo" of interface "Intf" not implemented')
enddef
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker

View File

@ -4369,10 +4369,14 @@ trans_function_name_ext(
lead += (int)STRLEN(sid_buf); lead += (int)STRLEN(sid_buf);
} }
} }
// The function name must start with an upper case letter (unless it is a
// Vim9 class new() function or a Vim9 class private method)
else if (!(flags & TFN_INT) else if (!(flags & TFN_INT)
&& (builtin_function(lv.ll_name, len) && (builtin_function(lv.ll_name, len)
|| (vim9script && *lv.ll_name == '_')) || (vim9script && *lv.ll_name == '_'))
&& !((flags & TFN_IN_CLASS) && STRNCMP(lv.ll_name, "new", 3) == 0)) && !((flags & TFN_IN_CLASS)
&& (STRNCMP(lv.ll_name, "new", 3) == 0
|| (*lv.ll_name == '_'))))
{ {
semsg(_(vim9script ? e_function_name_must_start_with_capital_str semsg(_(vim9script ? e_function_name_must_start_with_capital_str
: e_function_name_must_start_with_capital_or_s_str), : e_function_name_must_start_with_capital_or_s_str),

View File

@ -699,6 +699,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1804,
/**/ /**/
1803, 1803,
/**/ /**/

View File

@ -357,6 +357,13 @@ validate_interface_methods(
if (check_type_maybe(if_fp[if_i]->uf_func_type, if (check_type_maybe(if_fp[if_i]->uf_func_type,
cl_fp[cl_i]->uf_func_type, TRUE, where) != OK) cl_fp[cl_i]->uf_func_type, TRUE, where) != OK)
success = FALSE; success = FALSE;
// Ensure the public/private access level is matching.
if (if_fp[if_i]->uf_private != cl_fp[cl_i]->uf_private)
{
semsg(_(e_interface_str_and_class_str_function_access_not_same),
cl_name, if_name);
success = FALSE;
}
break; break;
} }
} }
@ -1150,7 +1157,9 @@ early_ret:
for (int i = 0; i < fgap->ga_len; ++i) for (int i = 0; i < fgap->ga_len; ++i)
{ {
char_u *n = ((ufunc_T **)fgap->ga_data)[i]->uf_name; char_u *n = ((ufunc_T **)fgap->ga_data)[i]->uf_name;
if (STRCMP(name, n) == 0) char_u *pstr = *name == '_' ? name + 1 : name;
char_u *qstr = *n == '_' ? n + 1 : n;
if (STRCMP(pstr, qstr) == 0)
{ {
semsg(_(e_duplicate_function_str), name); semsg(_(e_duplicate_function_str), name);
break; break;
@ -1162,6 +1171,11 @@ early_ret:
if (is_new) if (is_new)
uf->uf_flags |= FC_NEW; uf->uf_flags |= FC_NEW;
// If the method name starts with '_', then it a private
// method.
if (*name == '_')
uf->uf_private = TRUE;
((ufunc_T **)fgap->ga_data)[fgap->ga_len] = uf; ((ufunc_T **)fgap->ga_data)[fgap->ga_len] = uf;
++fgap->ga_len; ++fgap->ga_len;
} }
@ -1523,6 +1537,13 @@ class_object_index(
typval_T argvars[MAX_FUNC_ARGS + 1]; typval_T argvars[MAX_FUNC_ARGS + 1];
int argcount = 0; int argcount = 0;
if (fp->uf_private)
{
// Cannot access a private method outside of a class
semsg(_(e_cannot_access_private_method_str), name);
return FAIL;
}
char_u *argp = name_end; char_u *argp = name_end;
int ret = get_func_arguments(&argp, evalarg, 0, int ret = get_func_arguments(&argp, evalarg, 0,
argvars, &argcount); argvars, &argcount);

View File

@ -251,6 +251,30 @@ compile_member(int is_slice, int *keeping_dict, cctx_T *cctx)
return OK; return OK;
} }
/*
* Returns TRUE if the current function is inside the class "cl" or one of the
* parent classes.
*/
static int
inside_class_hierarchy(cctx_T *cctx_arg, class_T *cl)
{
for (cctx_T *cctx = cctx_arg; cctx != NULL; cctx = cctx->ctx_outer)
{
if (cctx->ctx_ufunc != NULL && cctx->ctx_ufunc->uf_class != NULL)
{
class_T *clp = cctx->ctx_ufunc->uf_class;
while (clp != NULL)
{
if (clp == cl)
return TRUE;
clp = clp->class_extends;
}
}
}
return FALSE;
}
/* /*
* Compile ".member" coming after an object or class. * Compile ".member" coming after an object or class.
*/ */
@ -348,6 +372,12 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type)
return FAIL; return FAIL;
} }
if (ufunc->uf_private && !inside_class_hierarchy(cctx, cl))
{
semsg(_(e_cannot_access_private_method_str), name);
return FAIL;
}
// Compile the arguments and call the class function or object method. // Compile the arguments and call the class function or object method.
// The object method will know that the object is on the stack, just // The object method will know that the object is on the stack, just
// before the arguments. // before the arguments.