summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorYegappan Lakshmanan <yegappan@yahoo.com>2023-12-16 14:11:19 +0100
committerChristian Brabandt <cb@256bit.org>2023-12-16 14:11:19 +0100
commite5437c542709b77ade084f96e60d84d4e847e6d3 (patch)
tree3740cd5c1dea8039f504fcbfb10b07086bdf08bf /src
parentd8bf87c9fbd92fd6b837446e886d47e557adadbc (diff)
patch 9.0.2170: Vim9: no support for const/final class/objects varsv9.0.2170
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>
Diffstat (limited to 'src')
-rw-r--r--src/errors.h18
-rw-r--r--src/eval.c7
-rw-r--r--src/proto/vim9class.pro2
-rw-r--r--src/structs.h6
-rw-r--r--src/testdir/test_vim9_class.vim608
-rw-r--r--src/version.c2
-rw-r--r--src/vim9class.c114
-rw-r--r--src/vim9compile.c12
-rw-r--r--src/vim9execute.c6
9 files changed, 757 insertions, 18 deletions
diff --git a/src/errors.h b/src/errors.h
index b6abf4a1d8..5eac961237 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -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[]
diff --git a/src/eval.c b/src/eval.c
index 0579a85f69..877a20ff28 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -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)
diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro
index 3000f57a38..99a14ccdc2 100644
--- a/src/proto/vim9class.pro
+++ b/src/proto/vim9class.pro
@@ -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);
diff --git a/src/structs.h b/src/structs.h
index de02bc6645..3b51e0c8f1 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -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;
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index bb806cce91..c28716aa1b 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -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
diff --git a/src/version.c b/src/version.c
index 771612024d..281124e203 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 2170,
+/**/
2169,
/**/
2168,
diff --git a/src/vim9class.c b/src/vim9class.c
index 9d17140fd1..a1f4aa2732 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -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;
@@ -2780,6 +2842,40 @@ inside_class(cctx_T *cctx_arg, class_T *cl)
}
/*
+ * 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.
*/
void
diff --git a/src/vim9compile.c b/src/vim9compile.c
index b2d8fa093e..7bf25526d8 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -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
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 882b13c61a..3bcdce410d 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -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