summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYegappan Lakshmanan <yegappan@yahoo.com>2023-08-27 19:18:23 +0200
committerChristian Brabandt <cb@256bit.org>2023-08-27 19:18:23 +0200
commitcd7293bf6c358bb0e183582a2927fc03566d29f6 (patch)
treed1fe17d7fdd8f5a2414761eab2879a80494e4e27
parent03e44a1d70e914504e6151fe88ad1e574cbf0a59 (diff)
patch 9.0.1804: Vim9: no support for private object methodsv9.0.1804
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>
-rw-r--r--runtime/doc/todo.txt1
-rw-r--r--runtime/doc/vim9class.txt32
-rw-r--r--src/errors.h7
-rw-r--r--src/structs.h5
-rw-r--r--src/testdir/test_vim9_class.vim590
-rw-r--r--src/userfunc.c6
-rw-r--r--src/version.c2
-rw-r--r--src/vim9class.c23
-rw-r--r--src/vim9expr.c30
9 files changed, 691 insertions, 5 deletions
diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt
index 8479e15d3c..6b45aabde7 100644
--- a/runtime/doc/todo.txt
+++ b/runtime/doc/todo.txt
@@ -129,6 +129,7 @@ Further Vim9 improvements, possibly after launch:
or: def _Func()
Perhaps use "private" keyword instead of "_" prefix?
- "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 an object method in a lambda #12417
Define all methods before compiling them?
diff --git a/runtime/doc/vim9class.txt b/runtime/doc/vim9class.txt
index c68288a0c5..926638bad5 100644
--- a/runtime/doc/vim9class.txt
+++ b/runtime/doc/vim9class.txt
@@ -178,6 +178,26 @@ number to the total number of lines: >
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 ~
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
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*
diff --git a/src/errors.h b/src/errors.h
index ed77212d94..cc0af8ad13 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -3484,6 +3484,11 @@ EXTERN char e_warning_pointer_block_corrupted[]
INIT(= N_("E1364: Warning: Pointer block corrupted"));
EXTERN char e_cannot_use_a_return_type_with_new[]
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[]
INIT(= N_("E1400: Cannot mix positional and non-positional arguments: %s"));
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[]
INIT(= N_("E1407: Member \"%s\": type mismatch, expected %s but got %s"));
-// E1366 - E1399 unused
+// E1368 - E1399 unused
diff --git a/src/structs.h b/src/structs.h
index ba712c1d22..b655e555b7 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1489,8 +1489,8 @@ typedef struct {
#define TTFLAG_SUPER 0x40 // object from "super".
typedef enum {
- VIM_ACCESS_PRIVATE, // read/write only inside th class
- VIM_ACCESS_READ, // read everywhere, write only inside th class
+ VIM_ACCESS_PRIVATE, // read/write only inside the class
+ VIM_ACCESS_READ, // read everywhere, write only inside the class
VIM_ACCESS_ALL // read/write everywhere
} omacc_T;
@@ -1790,6 +1790,7 @@ struct ufunc_S
class_T *uf_class; // for object method and constructor; does not
// 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_def_args; // default argument expressions
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index 5f5528aebd..bfe203289c 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -2801,4 +2801,594 @@ def Test_object_lockvar()
v9.CheckScriptSuccess(lines)
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
diff --git a/src/userfunc.c b/src/userfunc.c
index 47c3161512..0ebd61872d 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -4369,10 +4369,14 @@ trans_function_name_ext(
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)
&& (builtin_function(lv.ll_name, len)
|| (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
: e_function_name_must_start_with_capital_or_s_str),
diff --git a/src/version.c b/src/version.c
index 08fcbeb4e9..8b66b3341f 100644
--- a/src/version.c
+++ b/src/version.c
@@ -700,6 +700,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1804,
+/**/
1803,
/**/
1802,
diff --git a/src/vim9class.c b/src/vim9class.c
index bf1b1203c6..36a5d0a358 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -357,6 +357,13 @@ validate_interface_methods(
if (check_type_maybe(if_fp[if_i]->uf_func_type,
cl_fp[cl_i]->uf_func_type, TRUE, where) != OK)
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;
}
}
@@ -1150,7 +1157,9 @@ early_ret:
for (int i = 0; i < fgap->ga_len; ++i)
{
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);
break;
@@ -1162,6 +1171,11 @@ early_ret:
if (is_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;
++fgap->ga_len;
}
@@ -1523,6 +1537,13 @@ class_object_index(
typval_T argvars[MAX_FUNC_ARGS + 1];
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;
int ret = get_func_arguments(&argp, evalarg, 0,
argvars, &argcount);
diff --git a/src/vim9expr.c b/src/vim9expr.c
index 0e466bd02c..1b23b7f199 100644
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -252,6 +252,30 @@ compile_member(int is_slice, int *keeping_dict, cctx_T *cctx)
}
/*
+ * 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.
*/
static int
@@ -348,6 +372,12 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type)
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.
// The object method will know that the object is on the stack, just
// before the arguments.