]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.0.2170: Vim9: no support for const/final class/objects vars v9.0.2170
authorYegappan Lakshmanan <yegappan@yahoo.com>
Sat, 16 Dec 2023 13:11:19 +0000 (14:11 +0100)
committerChristian Brabandt <cb@256bit.org>
Sat, 16 Dec 2023 13:11:19 +0000 (14:11 +0100)
Problem:  Vim9: no support for const/final class/objects vars
Solution: Support final and const object and class variables

closes: #13655

Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
12 files changed:
runtime/doc/tags
runtime/doc/todo.txt
runtime/doc/vim9class.txt
src/errors.h
src/eval.c
src/proto/vim9class.pro
src/structs.h
src/testdir/test_vim9_class.vim
src/version.c
src/vim9class.c
src/vim9compile.c
src/vim9execute.c

index 964789c316eb255b07d2d0837b48e0ea72bc64a5..0b9cdffaa4675b10021a772024c13e9b38bdb5ec 100644 (file)
@@ -4505,7 +4505,10 @@ E1401    vim9class.txt   /*E1401*
 E1402  vim9class.txt   /*E1402*
 E1403  vim9class.txt   /*E1403*
 E1407  vim9class.txt   /*E1407*
+E1408  vim9class.txt   /*E1408*
+E1409  vim9class.txt   /*E1409*
 E141   message.txt     /*E141*
+E1410  vim9class.txt   /*E1410*
 E142   message.txt     /*E142*
 E143   autocmd.txt     /*E143*
 E144   various.txt     /*E144*
@@ -9096,6 +9099,8 @@ o_V       motion.txt      /*o_V*
 o_object-select        motion.txt      /*o_object-select*
 o_v    motion.txt      /*o_v*
 object vim9class.txt   /*object*
+object-const-variable  vim9class.txt   /*object-const-variable*
+object-final-variable  vim9class.txt   /*object-final-variable*
 object-motions motion.txt      /*object-motions*
 object-select  motion.txt      /*object-select*
 objects        index.txt       /*objects*
index 5e844a0b0376ce00f7f38d084da5deb1a6193bcb..afe5aed5396f280627fd4586a8770f0e5a305b23 100644 (file)
@@ -1,4 +1,4 @@
-*todo.txt*      For Vim version 9.0.  Last change: 2023 Jun 08
+*todo.txt*      For Vim version 9.0.  Last change: 2023 Dec 14
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -122,7 +122,6 @@ Upcoming larger works:
 
 Further Vim9 improvements:
 - Classes and Interfaces. See |vim9-classes|
-  - "final" object members - can only be set in the constructor.
   - Cannot use class type of itself in the method (Issue #12369)
   - Getting member of variable with "any" type should be handled at runtime.
     Remove temporary solution from #12096 / patch 9.0.1375.
index 97813f0ee2cc2ba3e8dd6b939affc3c69f915705..4dc67bd8723fea6e0b5ac77cca0228e36ff5834b 100644 (file)
@@ -364,6 +364,78 @@ super class.  Depending on the class where the member is used the
 corresponding class member will be used.  The type of the class member in a
 child class can be different from that in the super class.
 
+                                       *object-final-variable* *E1409*
+The |:final| keyword can be used to make a class or object variable a
+constant.  Examples: >
+
+    class A
+       final v1 = [1, 2]               # final object variable
+       public final v2 = {x: 1}        # final object variable
+       static final v3 = 'abc'         # final class variable
+       public static final v4 = 0z10   # final class variable
+    endclass
+<
+A final variable can be changed only from a constructor function.  Example: >
+
+    class A
+       final v1: list<number>
+       def new()
+           this.v1 = [1, 2]
+       enddef
+    endclass
+    var a = A.new()
+    echo a.v1
+<
+Note that the value of a final variable can be changed.  Example: >
+
+    class A
+       public final v1 = [1, 2]
+    endclass
+    var a = A.new()
+    a.v1[0] = 6                        # OK
+    a.v1->add(3)               # OK
+    a.v1 = [3, 4]              # Error
+<
+                                                       *E1408*
+Final variables are not supported in an interface.  A class or object method
+cannot be final.
+
+                                       *object-const-variable*
+The |:const| keyword can be used to make a class or object variable and the
+value a constant.  Examples: >
+
+    class A
+       const v1 = [1, 2]               # const object variable
+       public const v2 = {x: 1}        # const object variable
+       static const v3 = 'abc'         # const class variable
+       public static const v4 = 0z10   # const class variable
+    endclass
+<
+A const variable can be changed only from a constructor function. Example: >
+
+    class A
+       const v1: list<number>
+       def new()
+           this.v1 = [1, 2]
+       enddef
+    endclass
+    var a = A.new()
+    echo a.v1
+<
+A const variable and its value cannot be changed.  Example: >
+
+    class A
+       public const v1 = [1, 2]
+    endclass
+    var a = A.new()
+    a.v1[0] = 6                        # Error
+    a.v1->add(3)               # Error
+    a.v1 = [3, 4]              # Error
+<
+                                                        *E1410*
+Const variables are not supported in an interface.  A class or object method
+cannot be a const.
+
 ==============================================================================
 
 4.  Using an abstract class                    *Vim9-abstract-class*
@@ -982,8 +1054,6 @@ function declaration syntax for class/object variables and methods.  Vim9 also
 reuses the general function declaration syntax for methods.  So, for the sake
 of consistency, we require "var" in these declarations.
 
-This also allows for a natural use of "final" and "const" in the future.
-
 
 Using "ClassName.new()" to construct an object ~
 
index b6abf4a1d8b648607ae8671740f1e47ea240485f..5eac961237a9d3babdc2025cca20cbe011b68ec8 100644 (file)
@@ -3406,8 +3406,8 @@ EXTERN char e_invalid_class_variable_declaration_str[]
        INIT(= N_("E1329: Invalid class variable declaration: %s"));
 EXTERN char e_invalid_type_for_object_variable_str[]
        INIT(= N_("E1330: Invalid type for object variable: %s"));
-EXTERN char e_public_must_be_followed_by_var_or_static[]
-       INIT(= N_("E1331: Public must be followed by \"var\" or \"static\""));
+EXTERN char e_public_must_be_followed_by_var_static_final_or_const[]
+       INIT(= N_("E1331: Public must be followed by \"var\" or \"static\" or \"final\" or \"const\""));
 EXTERN char e_public_variable_name_cannot_start_with_underscore_str[]
        INIT(= N_("E1332: Public variable name cannot start with underscore: %s"));
 EXTERN char e_cannot_access_protected_variable_str[]
@@ -3488,8 +3488,8 @@ EXTERN char e_cannot_access_protected_method_str[]
        INIT(= N_("E1366: Cannot access protected method: %s"));
 EXTERN char e_variable_str_of_interface_str_has_different_access[]
        INIT(= N_("E1367: Access level of variable \"%s\" of interface \"%s\" is different"));
-EXTERN char e_static_must_be_followed_by_var_or_def[]
-       INIT(= N_("E1368: Static must be followed by \"var\" or \"def\""));
+EXTERN char e_static_must_be_followed_by_var_def_final_or_const[]
+       INIT(= N_("E1368: Static must be followed by \"var\" or \"def\" or \"final\" or \"const\""));
 EXTERN char e_duplicate_variable_str[]
        INIT(= N_("E1369: Duplicate variable: %s"));
 EXTERN char e_cannot_define_new_method_as_static[]
@@ -3568,8 +3568,14 @@ EXTERN char e_using_class_as_var_val[]
        INIT(= N_("E1406: Cannot use a Class as a variable or value"));
 EXTERN char e_using_typealias_as_var_val[]
        INIT(= N_("E1407: Cannot use a Typealias as a variable or value"));
-#endif
-// E1408 - E1499 unused (reserved for Vim9 class support)
+EXTERN char e_final_variable_not_supported_in_interface[]
+       INIT(= N_("E1408: Final variable not supported in an interface"));
+EXTERN char e_cannot_change_readonly_variable_str_in_class_str[]
+       INIT(= N_("E1409: Cannot change read-only variable \"%s\" in class \"%s\""));
+EXTERN char e_const_variable_not_supported_in_interface[]
+       INIT(= N_("E1410: Const variable not supported in an interface"));
+#endif
+// E1411 - E1499 unused (reserved for Vim9 class support)
 EXTERN char e_cannot_mix_positional_and_non_positional_str[]
        INIT(= N_("E1500: Cannot mix positional and non-positional arguments: %s"));
 EXTERN char e_fmt_arg_nr_unused_str[]
index 0579a85f69e976ec3aa742404bb9e984f642c34c..877a20ff28da77186b9f5f41f6e7ccc3aabf0d48 100644 (file)
@@ -1778,6 +1778,13 @@ get_lval(
                                                          p, flags) == FAIL)
                            return NULL;
 
+                       // When lhs is used to modify the variable, check it is
+                       // not a read-only variable.
+                       if ((flags & GLV_READ_ONLY) == 0
+                               && (*p != '.' && *p != '[')
+                               && oc_var_check_ro(cl, om))
+                           return NULL;
+
                        lp->ll_valtype = om->ocm_type;
 
                        if (v_type == VAR_OBJECT)
index 3000f57a38116e4242e2a200f48a07d128ea475d..99a14ccdc2534256af69e416e4316af290e95c71 100644 (file)
@@ -18,6 +18,8 @@ ocmember_T *member_lookup(class_T *cl, vartype_T v_type, char_u *name, size_t na
 void emsg_var_cl_define(char *msg, char_u *name, size_t len, class_T *cl);
 ufunc_T *method_lookup(class_T *cl, vartype_T v_type, char_u *name, size_t namelen, int *idx);
 int inside_class(cctx_T *cctx_arg, class_T *cl);
+int oc_var_check_ro(class_T *cl, ocmember_T *m);
+void obj_lock_const_vars(object_T *obj);
 void copy_object(typval_T *from, typval_T *to);
 void copy_class(typval_T *from, typval_T *to);
 void class_unref(class_T *cl);
index de02bc6645a8866b67d02b62db8575698b6f36f7..3b51e0c8f18b7f8297a359eeaba87441f115028b 100644 (file)
@@ -1523,14 +1523,18 @@ typedef enum {
     VIM_ACCESS_ALL     // read/write everywhere
 } omacc_T;
 
+#define OCMFLAG_HAS_TYPE       0x01    // type specified explicitly
+#define OCMFLAG_FINAL          0x02    // "final" object/class member
+#define OCMFLAG_CONST          0x04    // "const" object/class member
+
 /*
  * Entry for an object or class member variable.
  */
 typedef struct {
     char_u     *ocm_name;      // allocated
     omacc_T    ocm_access;
-    int                ocm_has_type;   // type specified explicitly
     type_T     *ocm_type;
+    int                ocm_flags;
     char_u     *ocm_init;      // allocated
 } ocmember_T;
 
index bb806cce91938117d11330a974ea696eb91806a6..c28716aa1bd57361485c2e24dbf65843108dacd1 100644 (file)
@@ -9051,4 +9051,612 @@ def Test_compile_many_def_functions_in_funcref_instr()
   assert_equal(0, v:shell_error)
 enddef
 
+" Test for 'final' class and object variables
+def Test_final_class_object_variable()
+  # Test for changing a final object variable from an object function
+  var lines =<< trim END
+    vim9script
+    class A
+      final foo: string = "abc"
+      def Foo()
+        this.foo = "def"
+      enddef
+    endclass
+    defcompile A.Foo
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "foo" in class "A"', 1)
+
+  # Test for changing a final object variable from the 'new' function
+  lines =<< trim END
+    vim9script
+    class A
+      final s1: string
+      final s2: string
+      def new(this.s1)
+        this.s2 = 'def'
+      enddef
+    endclass
+    var a = A.new('abc')
+    assert_equal('abc', a.s1)
+    assert_equal('def', a.s2)
+  END
+  v9.CheckSourceSuccess(lines)
+
+  # Test for a final class variable
+  lines =<< trim END
+    vim9script
+    class A
+      static final s1: string = "abc"
+    endclass
+    assert_equal('abc', A.s1)
+  END
+  v9.CheckSourceSuccess(lines)
+
+  # Test for changing a final class variable from a class function
+  lines =<< trim END
+    vim9script
+    class A
+      static final s1: string = "abc"
+      static def Foo()
+        s1 = "def"
+      enddef
+    endclass
+    A.Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
+
+  # Test for changing a public final class variable at script level
+  lines =<< trim END
+    vim9script
+    class A
+      public static final s1: string = "abc"
+    endclass
+    assert_equal('abc', A.s1)
+    A.s1 = 'def'
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 6)
+
+  # Test for changing a public final class variable from a class function
+  lines =<< trim END
+    vim9script
+    class A
+      public static final s1: string = "abc"
+      static def Foo()
+        s1 = "def"
+      enddef
+    endclass
+    A.Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
+
+  # Test for changing a public final class variable from a function
+  lines =<< trim END
+    vim9script
+    class A
+      public static final s1: string = "abc"
+    endclass
+    def Foo()
+      A.s1 = 'def'
+    enddef
+    defcompile
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
+
+  # Test for using a final variable of composite type
+  lines =<< trim END
+    vim9script
+    class A
+      public final l: list<number>
+      def new()
+        this.l = [1, 2]
+      enddef
+      def Foo()
+        this.l[0] = 3
+        this.l->add(4)
+      enddef
+    endclass
+    var a = A.new()
+    assert_equal([1, 2], a.l)
+    a.Foo()
+    assert_equal([3, 2, 4], a.l)
+  END
+  v9.CheckSourceSuccess(lines)
+
+  # Test for changing a final variable of composite type from another object
+  # function
+  lines =<< trim END
+    vim9script
+    class A
+      public final l: list<number> = [1, 2]
+      def Foo()
+        this.l = [3, 4]
+      enddef
+    endclass
+    var a = A.new()
+    a.Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 1)
+
+  # Test for modifying a final variable of composite type at script level
+  lines =<< trim END
+    vim9script
+    class A
+      public final l: list<number> = [1, 2]
+    endclass
+    var a = A.new()
+    a.l[0] = 3
+    a.l->add(4)
+    assert_equal([3, 2, 4], a.l)
+  END
+  v9.CheckSourceSuccess(lines)
+
+  # Test for modifying a final variable of composite type from a function
+  lines =<< trim END
+    vim9script
+    class A
+      public final l: list<number> = [1, 2]
+    endclass
+    def Foo()
+      var a = A.new()
+      a.l[0] = 3
+      a.l->add(4)
+      assert_equal([3, 2, 4], a.l)
+    enddef
+    Foo()
+  END
+  v9.CheckSourceSuccess(lines)
+
+  # Test for modifying a final variable of composite type from another object
+  # function
+  lines =<< trim END
+    vim9script
+    class A
+      public final l: list<number> = [1, 2]
+      def Foo()
+        this.l[0] = 3
+        this.l->add(4)
+      enddef
+    endclass
+    var a = A.new()
+    a.Foo()
+    assert_equal([3, 2, 4], a.l)
+  END
+  v9.CheckSourceSuccess(lines)
+
+  # Test for assigning a new value to a final variable of composite type at
+  # script level
+  lines =<< trim END
+    vim9script
+    class A
+      public final l: list<number> = [1, 2]
+    endclass
+    var a = A.new()
+    a.l = [3, 4]
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 6)
+
+  # Test for assigning a new value to a final variable of composite type from
+  # another object function
+  lines =<< trim END
+    vim9script
+    class A
+      public final l: list<number> = [1, 2]
+      def Foo()
+        this.l = [3, 4]
+      enddef
+    endclass
+    var a = A.new()
+    a.Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 1)
+
+  # Test for assigning a new value to a final variable of composite type from
+  # another function
+  lines =<< trim END
+    vim9script
+    class A
+      public final l: list<number> = [1, 2]
+    endclass
+    def Foo()
+      var a = A.new()
+      a.l = [3, 4]
+    enddef
+    Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 2)
+
+  # Error case: Use 'final' with just a variable name
+  lines =<< trim END
+    vim9script
+    class A
+      final foo
+    endclass
+    var a = A.new()
+  END
+  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
+
+  # Error case: Use 'final' followed by 'public'
+  lines =<< trim END
+    vim9script
+    class A
+      final public foo: number
+    endclass
+    var a = A.new()
+  END
+  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
+
+  # Error case: Use 'final' followed by 'static'
+  lines =<< trim END
+    vim9script
+    class A
+      final static foo: number
+    endclass
+    var a = A.new()
+  END
+  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
+
+  # Error case: 'final' cannot be used in an interface
+  lines =<< trim END
+    vim9script
+    interface A
+      final foo: number = 10
+    endinterface
+  END
+  v9.CheckSourceFailure(lines, 'E1408: Final variable not supported in an interface', 3)
+
+  # Error case: 'final' not supported for an object method
+  lines =<< trim END
+    vim9script
+    class A
+      final def Foo()
+      enddef
+    endclass
+  END
+  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
+
+  # Error case: 'final' not supported for a class method
+  lines =<< trim END
+    vim9script
+    class A
+      static final def Foo()
+      enddef
+    endclass
+  END
+  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
+enddef
+
+" Test for 'const' class and object variables
+def Test_const_class_object_variable()
+  # Test for changing a const object variable from an object function
+  var lines =<< trim END
+    vim9script
+    class A
+      const foo: string = "abc"
+      def Foo()
+        this.foo = "def"
+      enddef
+    endclass
+    defcompile A.Foo
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "foo" in class "A"', 1)
+
+  # Test for changing a const object variable from the 'new' function
+  lines =<< trim END
+    vim9script
+    class A
+      const s1: string
+      const s2: string
+      def new(this.s1)
+        this.s2 = 'def'
+      enddef
+    endclass
+    var a = A.new('abc')
+    assert_equal('abc', a.s1)
+    assert_equal('def', a.s2)
+  END
+  v9.CheckSourceSuccess(lines)
+
+  # Test for changing a const object variable from an object method called from
+  # the 'new' function
+  lines =<< trim END
+    vim9script
+    class A
+      const s1: string = 'abc'
+      def new()
+        this.ChangeStr()
+      enddef
+      def ChangeStr()
+        this.s1 = 'def'
+      enddef
+    endclass
+    var a = A.new()
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
+
+  # Test for a const class variable
+  lines =<< trim END
+    vim9script
+    class A
+      static const s1: string = "abc"
+    endclass
+    assert_equal('abc', A.s1)
+  END
+  v9.CheckSourceSuccess(lines)
+
+  # Test for changing a const class variable from a class function
+  lines =<< trim END
+    vim9script
+    class A
+      static const s1: string = "abc"
+      static def Foo()
+        s1 = "def"
+      enddef
+    endclass
+    A.Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
+
+  # Test for changing a public const class variable at script level
+  lines =<< trim END
+    vim9script
+    class A
+      public static const s1: string = "abc"
+    endclass
+    assert_equal('abc', A.s1)
+    A.s1 = 'def'
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 6)
+
+  # Test for changing a public const class variable from a class function
+  lines =<< trim END
+    vim9script
+    class A
+      public static const s1: string = "abc"
+      static def Foo()
+        s1 = "def"
+      enddef
+    endclass
+    A.Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
+
+  # Test for changing a public const class variable from a function
+  lines =<< trim END
+    vim9script
+    class A
+      public static const s1: string = "abc"
+    endclass
+    def Foo()
+      A.s1 = 'def'
+    enddef
+    defcompile
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
+
+  # Test for changing a const List item from an object function
+  lines =<< trim END
+    vim9script
+    class A
+      public const l: list<number>
+      def new()
+        this.l = [1, 2]
+      enddef
+      def Foo()
+        this.l[0] = 3
+      enddef
+    endclass
+    var a = A.new()
+    assert_equal([1, 2], a.l)
+    a.Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E1119: Cannot change locked list item', 1)
+
+  # Test for adding a value to a const List from an object function
+  lines =<< trim END
+    vim9script
+    class A
+      public const l: list<number>
+      def new()
+        this.l = [1, 2]
+      enddef
+      def Foo()
+        this.l->add(3)
+      enddef
+    endclass
+    var a = A.new()
+    a.Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E741: Value is locked: add() argument', 1)
+
+  # Test for reassigning a const List from an object function
+  lines =<< trim END
+    vim9script
+    class A
+      public const l: list<number> = [1, 2]
+      def Foo()
+        this.l = [3, 4]
+      enddef
+    endclass
+    var a = A.new()
+    a.Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 1)
+
+  # Test for changing a const List item at script level
+  lines =<< trim END
+    vim9script
+    class A
+      public const l: list<number> = [1, 2]
+    endclass
+    var a = A.new()
+    a.l[0] = 3
+  END
+  v9.CheckSourceFailure(lines, 'E741: Value is locked:',  6)
+
+  # Test for adding a value to a const List item at script level
+  lines =<< trim END
+    vim9script
+    class A
+      public const l: list<number> = [1, 2]
+    endclass
+    var a = A.new()
+    a.l->add(4)
+  END
+  v9.CheckSourceFailure(lines, 'E741: Value is locked:', 6)
+
+  # Test for changing a const List item from a function
+  lines =<< trim END
+    vim9script
+    class A
+      public const l: list<number> = [1, 2]
+    endclass
+    def Foo()
+      var a = A.new()
+      a.l[0] = 3
+    enddef
+    Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E1119: Cannot change locked list item', 2)
+
+  # Test for adding a value to a const List item from a function
+  lines =<< trim END
+    vim9script
+    class A
+      public const l: list<number> = [1, 2]
+    endclass
+    def Foo()
+      var a = A.new()
+      a.l->add(4)
+    enddef
+    Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E741: Value is locked: add() argument', 2)
+
+  # Test for changing a const List item from an object method
+  lines =<< trim END
+    vim9script
+    class A
+      public const l: list<number> = [1, 2]
+      def Foo()
+        this.l[0] = 3
+      enddef
+    endclass
+    var a = A.new()
+    a.Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E1119: Cannot change locked list item', 1)
+
+  # Test for adding a value to a const List item from an object method
+  lines =<< trim END
+    vim9script
+    class A
+      public const l: list<number> = [1, 2]
+      def Foo()
+        this.l->add(4)
+      enddef
+    endclass
+    var a = A.new()
+    a.Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E741: Value is locked: add() argument', 1)
+
+  # Test for reassigning a const List object variable at script level
+  lines =<< trim END
+    vim9script
+    class A
+      public const l: list<number> = [1, 2]
+    endclass
+    var a = A.new()
+    a.l = [3, 4]
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 6)
+
+  # Test for reassigning a const List object variable from an object method
+  lines =<< trim END
+    vim9script
+    class A
+      public const l: list<number> = [1, 2]
+      def Foo()
+        this.l = [3, 4]
+      enddef
+    endclass
+    var a = A.new()
+    a.Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 1)
+
+  # Test for reassigning a const List object variable from another function
+  lines =<< trim END
+    vim9script
+    class A
+      public const l: list<number> = [1, 2]
+    endclass
+    def Foo()
+      var a = A.new()
+      a.l = [3, 4]
+    enddef
+    Foo()
+  END
+  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 2)
+
+  # Error case: Use 'const' with just a variable name
+  lines =<< trim END
+    vim9script
+    class A
+      const foo
+    endclass
+    var a = A.new()
+  END
+  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
+
+  # Error case: Use 'const' followed by 'public'
+  lines =<< trim END
+    vim9script
+    class A
+      const public foo: number
+    endclass
+    var a = A.new()
+  END
+  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
+
+  # Error case: Use 'const' followed by 'static'
+  lines =<< trim END
+    vim9script
+    class A
+      const static foo: number
+    endclass
+    var a = A.new()
+  END
+  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
+
+  # Error case: 'const' cannot be used in an interface
+  lines =<< trim END
+    vim9script
+    interface A
+      const foo: number = 10
+    endinterface
+  END
+  v9.CheckSourceFailure(lines, 'E1410: Const variable not supported in an interface', 3)
+
+  # Error case: 'const' not supported for an object method
+  lines =<< trim END
+    vim9script
+    class A
+      const def Foo()
+      enddef
+    endclass
+  END
+  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
+
+  # Error case: 'const' not supported for a class method
+  lines =<< trim END
+    vim9script
+    class A
+      static const def Foo()
+      enddef
+    endclass
+  END
+  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
+enddef
+
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
index 771612024d53634b61e634c6357b14e04e958488..281124e2031f3e68213094cef4f9d10e5e2f26d0 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2170,
 /**/
     2169,
 /**/
index 9d17140fd12e91b423942432a55f548ba4e8e8b5..a1f4aa27324f03fdd0a76331e9f513ffd63cff69 100644 (file)
@@ -152,6 +152,19 @@ parse_member(
     return OK;
 }
 
+typedef struct oc_newmember_S oc_newmember_T;
+struct oc_newmember_S
+{
+    garray_T   *gap;
+    char_u     *varname;
+    char_u     *varname_end;
+    int                has_public;
+    int                has_final;
+    int                has_type;
+    type_T     *type;
+    char_u     *init_expr;
+};
+
 /*
  * Add a member to an object or a class.
  * Returns OK when successful, "init_expr" will be consumed then.
@@ -163,6 +176,8 @@ add_member(
     char_u     *varname,
     char_u     *varname_end,
     int                has_public,
+    int                has_final,
+    int                has_const,
     int                has_type,
     type_T     *type,
     char_u     *init_expr)
@@ -173,7 +188,12 @@ add_member(
     m->ocm_name = vim_strnsave(varname, varname_end - varname);
     m->ocm_access = has_public ? VIM_ACCESS_ALL
                      : *varname == '_' ? VIM_ACCESS_PRIVATE : VIM_ACCESS_READ;
-    m->ocm_has_type = has_type;
+    if (has_final)
+       m->ocm_flags |= OCMFLAG_FINAL;
+    if (has_const)
+       m->ocm_flags |= OCMFLAG_CONST;
+    if (has_type)
+       m->ocm_flags |= OCMFLAG_HAS_TYPE;
     m->ocm_type = type;
     if (init_expr != NULL)
        m->ocm_init = init_expr;
@@ -1132,7 +1152,7 @@ add_class_members(class_T *cl, exarg_T *eap, garray_T *type_list_gap)
            if (etv != NULL)
            {
                if (m->ocm_type->tt_type == VAR_ANY
-                       && !m->ocm_has_type
+                       && !(m->ocm_flags & OCMFLAG_HAS_TYPE)
                        && etv->v_type != VAR_SPECIAL)
                    // If the member variable type is not yet set, then use
                    // the initialization expression type.
@@ -1149,6 +1169,8 @@ add_class_members(class_T *cl, exarg_T *eap, garray_T *type_list_gap)
            tv->v_type = m->ocm_type->tt_type;
            tv->vval.v_string = NULL;
        }
+       if (m->ocm_flags & OCMFLAG_CONST)
+           item_lock(tv, DICT_MAXNEST, TRUE, TRUE);
     }
 }
 
@@ -1558,9 +1580,10 @@ early_ret:
            has_public = TRUE;
            p = skipwhite(line + 6);
 
-           if (STRNCMP(p, "var", 3) != 0 && STRNCMP(p, "static", 6) != 0)
+           if (STRNCMP(p, "var", 3) != 0 && STRNCMP(p, "static", 6) != 0
+               && STRNCMP(p, "final", 5) != 0 && STRNCMP(p, "const", 5) != 0)
            {
-               emsg(_(e_public_must_be_followed_by_var_or_static));
+               emsg(_(e_public_must_be_followed_by_var_static_final_or_const));
                break;
            }
        }
@@ -1616,22 +1639,60 @@ early_ret:
            has_static = TRUE;
            p = skipwhite(ps + 6);
 
-           if (STRNCMP(p, "var", 3) != 0 && STRNCMP(p, "def", 3) != 0)
+           if (STRNCMP(p, "var", 3) != 0 && STRNCMP(p, "def", 3) != 0
+               && STRNCMP(p, "final", 5) != 0 && STRNCMP(p, "const", 5) != 0)
            {
-               emsg(_(e_static_must_be_followed_by_var_or_def));
+               emsg(_(e_static_must_be_followed_by_var_def_final_or_const));
                break;
            }
        }
 
+       int has_final = FALSE;
+       int has_var = FALSE;
+       int has_const = FALSE;
+       if (checkforcmd(&p, "var", 3))
+           has_var = TRUE;
+       else if (checkforcmd(&p, "final", 5))
+       {
+           if (!is_class)
+           {
+               emsg(_(e_final_variable_not_supported_in_interface));
+               break;
+           }
+           has_final = TRUE;
+       }
+       else if (checkforcmd(&p, "const", 5))
+       {
+           if (!is_class)
+           {
+               emsg(_(e_const_variable_not_supported_in_interface));
+               break;
+           }
+           has_const = TRUE;
+       }
+       p = skipwhite(p);
+
        // object members (public, read access, private):
        //      "var _varname"
        //      "var varname"
        //      "public var varname"
+       //      "final _varname"
+       //      "final varname"
+       //      "public final varname"
+       //      "const _varname"
+       //      "const varname"
+       //      "public const varname"
        // class members (public, read access, private):
        //      "static var _varname"
        //      "static var varname"
        //      "public static var varname"
-       if (checkforcmd(&p, "var", 3))
+       //      "static final _varname"
+       //      "static final varname"
+       //      "public static final varname"
+       //      "static const _varname"
+       //      "static const varname"
+       //      "public static const varname"
+       if (has_var || has_final || has_const)
        {
            char_u *varname = p;
            char_u *varname_end = NULL;
@@ -1671,8 +1732,9 @@ early_ret:
                vim_free(init_expr);
                break;
            }
-           if (add_member(has_static ? &classmembers : &objmembers, varname, varname_end,
-                               has_public, has_type, type, init_expr) == FAIL)
+           if (add_member(has_static ? &classmembers : &objmembers, varname,
+                               varname_end, has_public, has_final, has_const,
+                               has_type, type, init_expr) == FAIL)
            {
                vim_free(init_expr);
                break;
@@ -2779,6 +2841,40 @@ inside_class(cctx_T *cctx_arg, class_T *cl)
     return FALSE;
 }
 
+/*
+ * Return TRUE if object/class variable "m" is read-only.
+ * Also give an error message.
+ */
+    int
+oc_var_check_ro(class_T *cl, ocmember_T *m)
+{
+    if (m->ocm_flags & (OCMFLAG_FINAL | OCMFLAG_CONST))
+    {
+       semsg(_(e_cannot_change_readonly_variable_str_in_class_str),
+               m->ocm_name, cl->class_name);
+       return TRUE;
+    }
+    return FALSE;
+}
+
+/*
+ * Lock all the constant object variables.  Called after creating and
+ * initializing a new object.
+ */
+    void
+obj_lock_const_vars(object_T *obj)
+{
+    for (int i = 0; i < obj->obj_class->class_obj_member_count; i++)
+    {
+       ocmember_T *ocm = &obj->obj_class->class_obj_members[i];
+       if (ocm->ocm_flags & OCMFLAG_CONST)
+       {
+           typval_T *mtv = ((typval_T *)(obj + 1)) + i;
+           item_lock(mtv, DICT_MAXNEST, TRUE, TRUE);
+       }
+    }
+}
+
 /*
  * Make a copy of an object.
  */
index b2d8fa093ec3fe20aed3ae4906c5d9fa2c6e6b85..7bf25526d8ad4eab6cd928d597a0901400b0f11e 100644 (file)
@@ -1770,6 +1770,12 @@ compile_lhs(
                                                                lhs->lhs_name);
                    return FAIL;
                }
+
+               ocmember_T      *m =
+                       &defcl->class_class_members[lhs->lhs_classmember_idx];
+               if (oc_var_check_ro(defcl, m))
+                   return FAIL;
+
                lhs->lhs_dest = dest_class_member;
                lhs->lhs_class = cctx->ctx_ufunc->uf_class;
                lhs->lhs_type =
@@ -2040,6 +2046,10 @@ compile_lhs(
                return FAIL;
            }
 
+           if (!IS_CONSTRUCTOR_METHOD(cctx->ctx_ufunc)
+                                               && oc_var_check_ro(cl, m))
+               return FAIL;
+
            lhs->lhs_member_type = m->ocm_type;
        }
        else
@@ -3356,7 +3366,7 @@ compile_def_function(
 
                    type_T      *type = get_type_on_stack(&cctx, 0);
                    if (m->ocm_type->tt_type == VAR_ANY
-                           && !m->ocm_has_type
+                           && !(m->ocm_flags & OCMFLAG_HAS_TYPE)
                            && type->tt_type != VAR_SPECIAL)
                    {
                        // If the member variable type is not yet set, then use
index 882b13c61aa5c4f460a9476d74438019ecb10a20..3bcdce410d978ac357de96abca77cc1ac74acc62 100644 (file)
@@ -4455,7 +4455,11 @@ exec_instructions(ectx_T *ectx)
                else
                {
                    *tv = *STACK_TV_VAR(0);
-                   ++tv->vval.v_object->obj_refcount;
+                   object_T *obj = tv->vval.v_object;
+                   ++obj->obj_refcount;
+
+                   // Lock all the constant object variables
+                   obj_lock_const_vars(obj);
                }
                // FALLTHROUGH