# HG changeset patch # User Christian Brabandt # Date 1702732505 -3600 # Node ID 45a50fd59a7380080a2a081c251864ee88245b38 # Parent ba10efba58435285a0e7d6f2a3ce02b96f78aaed patch 9.0.2170: Vim9: no support for const/final class/objects vars Commit: https://github.com/vim/vim/commit/e5437c542709b77ade084f96e60d84d4e847e6d3 Author: Yegappan Lakshmanan Date: Sat Dec 16 14:11:19 2023 +0100 patch 9.0.2170: Vim9: no support for const/final class/objects vars 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 Signed-off-by: Christian Brabandt diff --git a/runtime/doc/tags b/runtime/doc/tags --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -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* diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt --- a/runtime/doc/todo.txt +++ b/runtime/doc/todo.txt @@ -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. diff --git a/runtime/doc/vim9class.txt b/runtime/doc/vim9class.txt --- a/runtime/doc/vim9class.txt +++ b/runtime/doc/vim9class.txt @@ -364,6 +364,78 @@ super class. Depending on the class whe 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 + 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 + 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/ob 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 ~ diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3406,8 +3406,8 @@ EXTERN char e_invalid_class_variable_dec 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_me 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 --- 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 --- a/src/proto/vim9class.pro +++ b/src/proto/vim9class.pro @@ -18,6 +18,8 @@ ocmember_T *member_lookup(class_T *cl, v 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 --- 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 --- 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_f 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 + 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 = [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 = [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 = [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 = [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 = [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 = [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 = [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 + 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 + 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 = [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 = [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 = [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 = [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 = [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 = [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 = [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 = [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 = [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 = [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 --- 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 --- 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 * 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 * 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 * } /* + * 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 --- 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 --- 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