]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.0.1804: Vim9: no support for private object methods v9.0.1804
authorYegappan Lakshmanan <yegappan@yahoo.com>
Sun, 27 Aug 2023 17:18:23 +0000 (19:18 +0200)
committerChristian Brabandt <cb@256bit.org>
Sun, 27 Aug 2023 17:18:23 +0000 (19:18 +0200)
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>
runtime/doc/todo.txt
runtime/doc/vim9class.txt
src/errors.h
src/structs.h
src/testdir/test_vim9_class.vim
src/userfunc.c
src/version.c
src/vim9class.c
src/vim9expr.c

index 8479e15d3cae079be0e0f2e98190b3202d4ab805..6b45aabde7000f240fc744fc02fc6408c183a58a 100644 (file)
@@ -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?
index c68288a0c5d5f9e6b6165ab93d4b5a48f88ea84e..926638bad5c81bd49e5bcdf0f1c34c4682c525e3 100644 (file)
@@ -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*
index ed77212d94eb828fdce3fb80e0cf16924bebdc1e..cc0af8ad139310a3240f743c8e7f95759ab410a4 100644 (file)
@@ -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
index ba712c1d22abb33c2ef66ae20665386bce8066cf..b655e555b7dcf5921be0ce9f0acf0f1dbdc0167d 100644 (file)
@@ -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
index 5f5528aebd1d3f04cb9d35c63a64d978977371a5..bfe203289c3a25e2e62e08c19e27f2303b4eb6f6 100644 (file)
@@ -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
index 47c3161512190b39d5acbc306fd6177f37b1fbf2..0ebd61872de87387d8016d561a4ac7ca5e19c185 100644 (file)
@@ -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),
index 08fcbeb4e9887ddb5619e9d4d4daaa4a9117fc5a..8b66b3341fb712bf11804d952df96ae6b90c4a61 100644 (file)
@@ -699,6 +699,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1804,
 /**/
     1803,
 /**/
index bf1b1203c6f34cebd128d44221222bea92bf0efd..36a5d0a3584e3861f1b9374c08cdbf385aa19f55 100644 (file)
@@ -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);
index 0e466bd02cd2da41c87713dbf4c94581c2a27b00..1b23b7f1991238286dc4dc14e88b3fdad0a3ef39 100644 (file)
@@ -251,6 +251,30 @@ compile_member(int is_slice, int *keeping_dict, cctx_T *cctx)
     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.
  */
@@ -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.