diff options
author | Yegappan Lakshmanan <yegappan@yahoo.com> | 2023-12-16 14:11:19 +0100 |
---|---|---|
committer | Christian Brabandt <cb@256bit.org> | 2023-12-16 14:11:19 +0100 |
commit | e5437c542709b77ade084f96e60d84d4e847e6d3 (patch) | |
tree | 3740cd5c1dea8039f504fcbfb10b07086bdf08bf /src | |
parent | d8bf87c9fbd92fd6b837446e886d47e557adadbc (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.h | 18 | ||||
-rw-r--r-- | src/eval.c | 7 | ||||
-rw-r--r-- | src/proto/vim9class.pro | 2 | ||||
-rw-r--r-- | src/structs.h | 6 | ||||
-rw-r--r-- | src/testdir/test_vim9_class.vim | 608 | ||||
-rw-r--r-- | src/version.c | 2 | ||||
-rw-r--r-- | src/vim9class.c | 114 | ||||
-rw-r--r-- | src/vim9compile.c | 12 | ||||
-rw-r--r-- | src/vim9execute.c | 6 |
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 |